1import { css } from '@emotion/react';
2import { borderRadius, breakpoints, shadows, spacing, theme, typography } from '@expo/styleguide';
3import type { ComponentProps, ComponentType } from 'react';
4import { Fragment } from 'react';
5import ReactMarkdown from 'react-markdown';
6import remarkGfm from 'remark-gfm';
7
8import { APIDataType } from './APIDataType';
9
10import { HeadingType } from '~/common/headingManager';
11import { Code as PrismCodeBlock } from '~/components/base/code';
12import {
13  CommentContentData,
14  CommentData,
15  MethodDefinitionData,
16  MethodParamData,
17  MethodSignatureData,
18  PropData,
19  TypeDefinitionData,
20  TypePropertyDataFlags,
21  TypeSignaturesData,
22} from '~/components/plugins/api/APIDataTypes';
23import { APISectionPlatformTags } from '~/components/plugins/api/APISectionPlatformTags';
24import { Callout } from '~/ui/components/Callout';
25import { Cell, HeaderCell, Row, Table, TableHead } from '~/ui/components/Table';
26import { tableWrapperStyle } from '~/ui/components/Table/Table';
27import { Tag } from '~/ui/components/Tag';
28import {
29  A,
30  BOLD,
31  CODE,
32  H4,
33  LI,
34  OL,
35  P,
36  RawH3,
37  RawH4,
38  UL,
39  createPermalinkedComponent,
40} from '~/ui/components/Text';
41
42const isDev = process.env.NODE_ENV === 'development';
43
44export enum TypeDocKind {
45  Namespace = 4,
46  Enum = 8,
47  Variable = 32,
48  Function = 64,
49  Class = 128,
50  Interface = 256,
51  Property = 1024,
52  Method = 2048,
53  Parameter = 32768,
54  Accessor = 262144,
55  TypeAlias = 4194304,
56}
57
58export type MDComponents = ComponentProps<typeof ReactMarkdown>['components'];
59
60const getInvalidLinkMessage = (href: string) =>
61  `Using "../" when linking other packages in doc comments produce a broken link! Please use "./" instead. Problematic link:\n\t${href}`;
62
63export const mdComponents: MDComponents = {
64  blockquote: ({ children }) => <Callout>{children}</Callout>,
65  code: ({ children, className }) =>
66    className ? (
67      <PrismCodeBlock className={className}>{children}</PrismCodeBlock>
68    ) : (
69      <CODE css={css({ display: 'inline' })}>{children}</CODE>
70    ),
71  h1: ({ children }) => <H4>{children}</H4>,
72  ul: ({ children }) => <UL css={STYLES_ELEMENT_SPACING}>{children}</UL>,
73  ol: ({ children }) => <OL css={STYLES_ELEMENT_SPACING}>{children}</OL>,
74  li: ({ children }) => <LI>{children}</LI>,
75  a: ({ href, children }) => {
76    if (
77      href?.startsWith('../') &&
78      !href?.startsWith('../..') &&
79      !href?.startsWith('../react-native')
80    ) {
81      if (isDev) {
82        throw new Error(getInvalidLinkMessage(href));
83      } else {
84        console.warn(getInvalidLinkMessage(href));
85      }
86    }
87    return <A href={href}>{children}</A>;
88  },
89  p: ({ children }) => (children ? <P css={STYLES_ELEMENT_SPACING}>{children}</P> : null),
90  strong: ({ children }) => <BOLD>{children}</BOLD>,
91  span: ({ children }) => (children ? <span>{children}</span> : null),
92  table: ({ children }) => <Table>{children}</Table>,
93  thead: ({ children }) => <TableHead>{children}</TableHead>,
94  tr: ({ children }) => <Row>{children}</Row>,
95  th: ({ children }) => <HeaderCell>{children}</HeaderCell>,
96  td: ({ children }) => <Cell>{children}</Cell>,
97};
98
99export const mdComponentsNoValidation: MDComponents = {
100  ...mdComponents,
101  a: ({ href, children }) => <A href={href}>{children}</A>,
102};
103
104const nonLinkableTypes = [
105  'ColorValue',
106  'Component',
107  'ComponentClass',
108  'PureComponent',
109  'E',
110  'EventSubscription',
111  'Listener',
112  'NativeSyntheticEvent',
113  'ParsedQs',
114  'ServiceActionResult',
115  'T',
116  'TaskOptions',
117  'Uint8Array',
118  // React & React Native
119  'React.FC',
120  'ForwardRefExoticComponent',
121  'StyleProp',
122  // Cross-package permissions management
123  'RequestPermissionMethod',
124  'GetPermissionMethod',
125  'Options',
126  'PermissionHookBehavior',
127];
128
129/**
130 * List of type names that should not be visible in the docs.
131 */
132const omittableTypes = [
133  // Internal React type that adds `ref` prop to the component
134  'RefAttributes',
135];
136
137/**
138 * Map of internal names/type names that should be replaced with something more developer-friendly.
139 */
140const replaceableTypes: Partial<Record<string, string>> = {
141  ForwardRefExoticComponent: 'Component',
142};
143
144const hardcodedTypeLinks: Record<string, string> = {
145  AVPlaybackSource: '/versions/latest/sdk/av/#avplaybacksource',
146  AVPlaybackStatus: '/versions/latest/sdk/av/#avplaybackstatus',
147  AVPlaybackStatusToSet: '/versions/latest/sdk/av/#avplaybackstatustoset',
148  Blob: 'https://developer.mozilla.org/en-US/docs/Web/API/Blob',
149  Date: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date',
150  DeviceSensor: '/versions/latest/sdk/sensors',
151  Element: 'https://www.typescriptlang.org/docs/handbook/jsx.html#function-component',
152  Error: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error',
153  ExpoConfig:
154    'https://github.com/expo/expo/blob/main/packages/%40expo/config-types/src/ExpoConfig.ts',
155  File: 'https://developer.mozilla.org/en-US/docs/Web/API/File',
156  FileList: 'https://developer.mozilla.org/en-US/docs/Web/API/FileList',
157  MessageEvent: 'https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent',
158  Omit: 'https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys',
159  Pick: 'https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys',
160  Partial: 'https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype',
161  Promise:
162    'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise',
163  View: 'https://reactnative.dev/docs/view',
164  ViewProps: 'https://reactnative.dev/docs/view#props',
165  ViewStyle: 'https://reactnative.dev/docs/view-style-props',
166  WebGL2RenderingContext: 'https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext',
167  WebGLFramebuffer: 'https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer',
168};
169
170const renderWithLink = (name: string, type?: string) => {
171  const replacedName = replaceableTypes[name] ?? name;
172
173  if (name.includes('.')) return name;
174
175  return nonLinkableTypes.includes(replacedName) ? (
176    replacedName + (type === 'array' ? '[]' : '')
177  ) : (
178    <A
179      href={hardcodedTypeLinks[replacedName] || `#${replacedName.toLowerCase()}`}
180      key={`type-link-${replacedName}`}>
181      {replacedName}
182      {type === 'array' && '[]'}
183    </A>
184  );
185};
186
187const renderUnion = (types: TypeDefinitionData[]) =>
188  types
189    .map(type => resolveTypeName(type))
190    .map((valueToRender, index) => (
191      <span key={`union-type-${index}`}>
192        {valueToRender}
193        {index + 1 !== types.length && ' | '}
194      </span>
195    ));
196
197export const resolveTypeName = (
198  typeDefinition: TypeDefinitionData
199): string | JSX.Element | (string | JSX.Element)[] => {
200  if (!typeDefinition) {
201    return 'undefined';
202  }
203
204  const {
205    elements,
206    elementType,
207    name,
208    type,
209    types,
210    typeArguments,
211    declaration,
212    value,
213    queryType,
214    operator,
215    objectType,
216    indexType,
217  } = typeDefinition;
218
219  try {
220    if (name) {
221      if (type === 'reference') {
222        if (typeArguments) {
223          if (name === 'Record' || name === 'React.ComponentProps') {
224            return (
225              <>
226                {name}&lt;
227                {typeArguments.map((type, index) => (
228                  <span key={`record-type-${index}`}>
229                    {resolveTypeName(type)}
230                    {index !== typeArguments.length - 1 ? ', ' : null}
231                  </span>
232                ))}
233                &gt;
234              </>
235            );
236          } else {
237            return (
238              <>
239                {renderWithLink(name)}
240                &lt;
241                {typeArguments.map((type, index) => (
242                  <span key={`${name}-nested-type-${index}`}>
243                    {resolveTypeName(type)}
244                    {index !== typeArguments.length - 1 ? ', ' : null}
245                  </span>
246                ))}
247                &gt;
248              </>
249            );
250          }
251        } else {
252          return renderWithLink(name);
253        }
254      } else {
255        return name;
256      }
257    } else if (elementType?.name) {
258      if (elementType.type === 'reference') {
259        return renderWithLink(elementType.name, type);
260      } else if (type === 'array') {
261        return elementType.name + '[]';
262      }
263      return elementType.name + type;
264    } else if (elementType?.declaration) {
265      if (type === 'array') {
266        const { parameters, type: paramType } = elementType.declaration.indexSignature || {};
267        if (parameters && paramType) {
268          return `{ [${listParams(parameters)}]: ${resolveTypeName(paramType)} }`;
269        }
270      }
271      return elementType.name + type;
272    } else if (type === 'union' && types?.length) {
273      return renderUnion(types);
274    } else if (elementType && elementType.type === 'union' && elementType?.types?.length) {
275      const unionTypes = elementType?.types || [];
276      return (
277        <>
278          ({renderUnion(unionTypes)}){type === 'array' && '[]'}
279        </>
280      );
281    } else if (declaration?.signatures) {
282      const baseSignature = declaration.signatures[0];
283      if (baseSignature?.parameters?.length) {
284        return (
285          <>
286            (
287            {baseSignature.parameters?.map((param, index) => (
288              <span key={`param-${index}-${param.name}`}>
289                {param.name}: {resolveTypeName(param.type)}
290                {index + 1 !== baseSignature.parameters?.length && ', '}
291              </span>
292            ))}
293            ) {'=>'} {resolveTypeName(baseSignature.type)}
294          </>
295        );
296      } else {
297        return (
298          <>
299            {'() =>'} {resolveTypeName(baseSignature.type)}
300          </>
301        );
302      }
303    } else if (type === 'reflection' && declaration?.children) {
304      return (
305        <>
306          {'{\n'}
307          {declaration?.children.map((child: PropData, i) => (
308            <span key={`reflection-${name}-${i}`}>
309              {'  '}
310              {child.name + ': '}
311              {resolveTypeName(child.type)}
312              {i + 1 !== declaration?.children?.length ? ', ' : null}
313              {'\n'}
314            </span>
315          ))}
316          {'}'}
317        </>
318      );
319    } else if (type === 'tuple' && elements) {
320      return (
321        <>
322          [
323          {elements.map((elem, i) => (
324            <span key={`tuple-${name}-${i}`}>
325              {resolveTypeName(elem)}
326              {i + 1 !== elements.length ? ', ' : null}
327            </span>
328          ))}
329          ]
330        </>
331      );
332    } else if (type === 'query' && queryType) {
333      return queryType.name;
334    } else if (type === 'literal' && typeof value === 'boolean') {
335      return `${value}`;
336    } else if (type === 'literal' && (value || (typeof value === 'number' && value === 0))) {
337      return `'${value}'`;
338    } else if (type === 'intersection' && types) {
339      return types
340        .filter(({ name }) => !omittableTypes.includes(name ?? ''))
341        .map((value, index, array) => (
342          <span key={`intersection-${name}-${index}`}>
343            {resolveTypeName(value)}
344            {index + 1 !== array.length && ' & '}
345          </span>
346        ));
347    } else if (type === 'indexedAccess') {
348      return `${objectType?.name}['${indexType?.value}']`;
349    } else if (type === 'typeOperator') {
350      return operator || 'undefined';
351    } else if (type === 'intrinsic') {
352      return name || 'undefined';
353    } else if (value === null) {
354      return 'null';
355    }
356    return 'undefined';
357  } catch (e) {
358    console.warn('Type resolve has failed!', e);
359    return 'undefined';
360  }
361};
362
363export const parseParamName = (name: string) => (name.startsWith('__') ? name.substr(2) : name);
364
365export const renderParamRow = ({
366  comment,
367  name,
368  type,
369  flags,
370  defaultValue,
371}: MethodParamData): JSX.Element => {
372  const defaultData = getTagData('default', comment);
373  const initValue = parseCommentContent(
374    defaultValue || (defaultData ? getCommentContent(defaultData.content) : '')
375  );
376  return (
377    <Row key={`param-${name}`}>
378      <Cell>
379        <BOLD>{parseParamName(name)}</BOLD>
380        {renderFlags(flags, initValue)}
381      </Cell>
382      <Cell>
383        <APIDataType typeDefinition={type} />
384      </Cell>
385      <Cell>
386        <CommentTextBlock
387          comment={comment}
388          afterContent={renderDefaultValue(initValue)}
389          emptyCommentFallback="-"
390        />
391      </Cell>
392    </Row>
393  );
394};
395
396export const ParamsTableHeadRow = () => (
397  <TableHead>
398    <Row>
399      <HeaderCell>Name</HeaderCell>
400      <HeaderCell>Type</HeaderCell>
401      <HeaderCell>Description</HeaderCell>
402    </Row>
403  </TableHead>
404);
405
406export const renderParams = (parameters: MethodParamData[]) => (
407  <Table>
408    <ParamsTableHeadRow />
409    <tbody>{parameters?.map(renderParamRow)}</tbody>
410  </Table>
411);
412
413export const listParams = (parameters: MethodParamData[]) =>
414  parameters ? parameters?.map(param => parseParamName(param.name)).join(', ') : '';
415
416export const renderDefaultValue = (defaultValue?: string) =>
417  defaultValue && defaultValue !== '...' ? (
418    <div css={defaultValueContainerStyle}>
419      <BOLD>Default:</BOLD> <CODE>{defaultValue}</CODE>
420    </div>
421  ) : undefined;
422
423export const renderTypeOrSignatureType = (
424  type?: TypeDefinitionData,
425  signatures?: MethodSignatureData[] | TypeSignaturesData[],
426  allowBlock: boolean = false
427) => {
428  if (signatures && signatures.length) {
429    return (
430      <CODE key={`signature-type-${signatures[0].name}`}>
431        (
432        {signatures?.map(({ parameters }) =>
433          parameters?.map(param => (
434            <span key={`signature-param-${param.name}`}>
435              {param.name}
436              {param.flags?.isOptional && '?'}: {resolveTypeName(param.type)}
437            </span>
438          ))
439        )}
440        ) =&gt; {signatures[0].type ? resolveTypeName(signatures[0].type) : 'void'}
441      </CODE>
442    );
443  } else if (type) {
444    if (allowBlock) {
445      return <APIDataType typeDefinition={type} />;
446    }
447    return <CODE key={`signature-type-${type.name}`}>{resolveTypeName(type)}</CODE>;
448  }
449  return undefined;
450};
451
452export const renderFlags = (flags?: TypePropertyDataFlags, defaultValue?: string) =>
453  (flags?.isOptional || defaultValue) && (
454    <>
455      <br />
456      <span css={STYLES_OPTIONAL}>(optional)</span>
457    </>
458  );
459
460export const renderIndexSignature = (kind: TypeDocKind) =>
461  kind === TypeDocKind.Parameter && (
462    <>
463      <br />
464      <A
465        css={STYLES_OPTIONAL}
466        href="https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures"
467        openInNewTab
468        isStyled>
469        (index signature)
470      </A>
471    </>
472  );
473
474export type CommentTextBlockProps = {
475  comment?: CommentData;
476  components?: MDComponents;
477  beforeContent?: JSX.Element;
478  afterContent?: JSX.Element;
479  includePlatforms?: boolean;
480  inlineHeaders?: boolean;
481  emptyCommentFallback?: string;
482};
483
484export const parseCommentContent = (content?: string): string =>
485  content && content.length ? content.replace(/&ast;/g, '*').replace(/\t/g, '') : '';
486
487export const getCommentOrSignatureComment = (
488  comment?: CommentData,
489  signatures?: MethodSignatureData[] | TypeSignaturesData[]
490) => comment || (signatures && signatures[0]?.comment);
491
492export const getTagData = (tagName: string, comment?: CommentData) =>
493  getAllTagData(tagName, comment)?.[0];
494
495export const getAllTagData = (tagName: string, comment?: CommentData) =>
496  comment?.blockTags?.filter(tag => tag.tag.substring(1) === tagName);
497
498export const getTagNamesList = (comment?: CommentData) =>
499  comment && [
500    ...(getAllTagData('platform', comment)?.map(platformData =>
501      getCommentContent(platformData.content)
502    ) || []),
503    ...(getTagData('deprecated', comment) ? ['deprecated'] : []),
504    ...(getTagData('experimental', comment) ? ['experimental'] : []),
505  ];
506
507export const getMethodName = (
508  method: MethodDefinitionData,
509  apiName?: string,
510  name?: string,
511  parameters?: MethodParamData[]
512) => {
513  const isProperty = method.kind === TypeDocKind.Property && !parameters?.length;
514  const methodName = ((apiName && `${apiName}.`) ?? '') + (method.name || name);
515  if (!isProperty) {
516    return `${methodName}(${parameters ? listParams(parameters) : ''})`;
517  }
518
519  return methodName;
520};
521
522export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
523
524const PARAM_TAGS_REGEX = /@tag-\S*/g;
525
526const getParamTags = (shortText?: string) => {
527  if (!shortText || !shortText.includes('@tag-')) {
528    return undefined;
529  }
530  return Array.from(shortText.matchAll(PARAM_TAGS_REGEX), match => match[0]);
531};
532
533export const getCommentContent = (content: CommentContentData[]) => {
534  return content
535    .map(entry => entry.text)
536    .join('')
537    .trim();
538};
539
540export const CommentTextBlock = ({
541  comment,
542  beforeContent,
543  afterContent,
544  includePlatforms = true,
545  inlineHeaders = false,
546  emptyCommentFallback,
547}: CommentTextBlockProps) => {
548  const content = comment && comment.summary ? getCommentContent(comment.summary) : undefined;
549
550  if (emptyCommentFallback && (!comment || !content || !content.length)) {
551    return <>{emptyCommentFallback}</>;
552  }
553
554  const paramTags = content ? getParamTags(content) : undefined;
555  const parsedContent = (
556    <ReactMarkdown components={mdComponents} remarkPlugins={[remarkGfm]}>
557      {parseCommentContent(paramTags ? content?.replaceAll(PARAM_TAGS_REGEX, '') : content)}
558    </ReactMarkdown>
559  );
560
561  const examples = getAllTagData('example', comment);
562  const exampleText = examples?.map((example, index) => (
563    <Fragment key={'example-' + index}>
564      {inlineHeaders ? (
565        <div css={STYLES_EXAMPLE_IN_TABLE}>
566          <BOLD>Example</BOLD>
567        </div>
568      ) : (
569        <div css={STYLES_NESTED_SECTION_HEADER}>
570          <RawH4>Example</RawH4>
571        </div>
572      )}
573      <ReactMarkdown components={mdComponents}>{getCommentContent(example.content)}</ReactMarkdown>
574    </Fragment>
575  ));
576
577  const see = getTagData('see', comment);
578  const seeText = see && (
579    <Callout>
580      <ReactMarkdown components={mdComponents}>
581        {`**See:** ` + getCommentContent(see.content)}
582      </ReactMarkdown>
583    </Callout>
584  );
585
586  const hasPlatforms = (getAllTagData('platform', comment)?.length || 0) > 0;
587
588  return (
589    <>
590      {includePlatforms && hasPlatforms && (
591        <APISectionPlatformTags comment={comment} prefix="Only for:" />
592      )}
593      {paramTags && (
594        <>
595          <BOLD>Only for:&ensp;</BOLD>
596          {paramTags.map(tag => (
597            <Tag key={tag} name={tag.split('-')[1]} />
598          ))}
599        </>
600      )}
601      {beforeContent}
602      {parsedContent}
603      {afterContent}
604      {seeText}
605      {exampleText}
606    </>
607  );
608};
609
610export const getAPISectionHeader = (exposeInSidebar?: boolean) =>
611  exposeInSidebar ? createPermalinkedComponent(RawH4, { baseNestingLevel: 2 }) : H4;
612
613const getMonospaceHeader = (element: ComponentType<any>) => {
614  const level = parseInt(element?.displayName?.replace(/\D/g, '') ?? '0', 10);
615  return createPermalinkedComponent(element, {
616    baseNestingLevel: level !== 0 ? level : undefined,
617    sidebarType: HeadingType.InlineCode,
618  });
619};
620
621export const H3Code = getMonospaceHeader(RawH3);
622export const H4Code = getMonospaceHeader(RawH4);
623
624export const getComponentName = (name?: string, children: PropData[] = []) => {
625  if (name && name !== 'default') return name;
626  const ctor = children.filter((child: PropData) => child.name === 'constructor')[0];
627  return ctor?.signatures?.[0]?.type?.name ?? 'default';
628};
629
630export const STYLES_APIBOX = css({
631  borderRadius: borderRadius.md,
632  borderWidth: 1,
633  borderStyle: 'solid',
634  borderColor: theme.border.default,
635  padding: spacing[5],
636  boxShadow: shadows.xs,
637  marginBottom: spacing[6],
638  overflowX: 'hidden',
639
640  h3: {
641    marginBottom: spacing[2],
642  },
643
644  'h2, h3, h4': {
645    marginTop: 0,
646  },
647
648  th: {
649    color: theme.text.secondary,
650    padding: `${spacing[3]}px ${spacing[4]}px`,
651  },
652
653  li: {
654    marginBottom: 0,
655  },
656
657  [`.css-${tableWrapperStyle.name}`]: {
658    boxShadow: 'none',
659    marginBottom: 0,
660  },
661
662  [`@media screen and (max-width: ${breakpoints.medium + 124}px)`]: {
663    padding: `0 ${spacing[4]}px`,
664  },
665});
666
667export const STYLES_APIBOX_NESTED = css({
668  boxShadow: 'none',
669  marginBottom: spacing[4],
670  padding: `${spacing[4]}px ${spacing[5]}px 0`,
671
672  h4: {
673    marginTop: 0,
674  },
675});
676
677export const STYLES_APIBOX_WRAPPER = css({
678  marginBottom: spacing[4],
679  padding: `${spacing[4]}px ${spacing[5]}px 0`,
680
681  [`.css-${tableWrapperStyle.name}:last-child`]: {
682    marginBottom: spacing[4],
683  },
684});
685
686export const STYLE_APIBOX_NO_SPACING = css({ marginBottom: -spacing[5] });
687
688export const STYLES_NESTED_SECTION_HEADER = css({
689  display: 'flex',
690  borderTop: `1px solid ${theme.border.default}`,
691  borderBottom: `1px solid ${theme.border.default}`,
692  margin: `${spacing[4]}px -${spacing[5]}px ${spacing[4]}px`,
693  padding: `${spacing[2.5]}px ${spacing[5]}px`,
694  backgroundColor: theme.background.subtle,
695
696  h4: {
697    ...typography.fontSizes[16],
698    fontWeight: 600,
699    marginBottom: 0,
700    marginTop: 0,
701    color: theme.text.secondary,
702  },
703});
704
705export const STYLES_NOT_EXPOSED_HEADER = css({
706  marginBottom: spacing[1],
707  display: 'inline-block',
708
709  code: {
710    marginBottom: 0,
711  },
712});
713
714export const STYLES_OPTIONAL = css({
715  color: theme.text.secondary,
716  fontSize: '90%',
717  paddingTop: 22,
718});
719
720export const STYLES_SECONDARY = css({
721  color: theme.text.secondary,
722  fontSize: '90%',
723  fontWeight: 600,
724});
725
726const defaultValueContainerStyle = css({
727  marginTop: spacing[2],
728  marginBottom: spacing[2],
729
730  '&:last-child': {
731    marginBottom: 0,
732  },
733});
734
735const STYLES_EXAMPLE_IN_TABLE = css({
736  margin: `${spacing[2]}px 0`,
737});
738
739export const STYLES_ELEMENT_SPACING = css({
740  marginBottom: spacing[4],
741});
742