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  MediaTrackSettings: 'https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings',
158  MessageEvent: 'https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent',
159  Omit: 'https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys',
160  Pick: 'https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys',
161  Partial: 'https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype',
162  Promise:
163    'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise',
164  View: 'https://reactnative.dev/docs/view',
165  ViewProps: 'https://reactnative.dev/docs/view#props',
166  ViewStyle: 'https://reactnative.dev/docs/view-style-props',
167  WebGL2RenderingContext: 'https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext',
168  WebGLFramebuffer: 'https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer',
169};
170
171const renderWithLink = (name: string, type?: string) => {
172  const replacedName = replaceableTypes[name] ?? name;
173
174  if (name.includes('.')) return name;
175
176  return nonLinkableTypes.includes(replacedName) ? (
177    replacedName + (type === 'array' ? '[]' : '')
178  ) : (
179    <A
180      href={hardcodedTypeLinks[replacedName] || `#${replacedName.toLowerCase()}`}
181      key={`type-link-${replacedName}`}>
182      {replacedName}
183      {type === 'array' && '[]'}
184    </A>
185  );
186};
187
188const renderUnion = (types: TypeDefinitionData[]) =>
189  types
190    .map(type => resolveTypeName(type))
191    .map((valueToRender, index) => (
192      <span key={`union-type-${index}`}>
193        {valueToRender}
194        {index + 1 !== types.length && ' | '}
195      </span>
196    ));
197
198export const resolveTypeName = (
199  typeDefinition: TypeDefinitionData
200): string | JSX.Element | (string | JSX.Element)[] => {
201  if (!typeDefinition) {
202    return 'undefined';
203  }
204
205  const {
206    elements,
207    elementType,
208    name,
209    type,
210    types,
211    typeArguments,
212    declaration,
213    value,
214    queryType,
215    operator,
216    objectType,
217    indexType,
218  } = typeDefinition;
219
220  try {
221    if (name) {
222      if (type === 'reference') {
223        if (typeArguments) {
224          if (name === 'Record' || name === 'React.ComponentProps') {
225            return (
226              <>
227                {name}&lt;
228                {typeArguments.map((type, index) => (
229                  <span key={`record-type-${index}`}>
230                    {resolveTypeName(type)}
231                    {index !== typeArguments.length - 1 ? ', ' : null}
232                  </span>
233                ))}
234                &gt;
235              </>
236            );
237          } else {
238            return (
239              <>
240                {renderWithLink(name)}
241                &lt;
242                {typeArguments.map((type, index) => (
243                  <span key={`${name}-nested-type-${index}`}>
244                    {resolveTypeName(type)}
245                    {index !== typeArguments.length - 1 ? ', ' : null}
246                  </span>
247                ))}
248                &gt;
249              </>
250            );
251          }
252        } else {
253          return renderWithLink(name);
254        }
255      } else {
256        return name;
257      }
258    } else if (elementType?.name) {
259      if (elementType.type === 'reference') {
260        return renderWithLink(elementType.name, type);
261      } else if (type === 'array') {
262        return elementType.name + '[]';
263      }
264      return elementType.name + type;
265    } else if (elementType?.declaration) {
266      if (type === 'array') {
267        const { parameters, type: paramType } = elementType.declaration.indexSignature || {};
268        if (parameters && paramType) {
269          return `{ [${listParams(parameters)}]: ${resolveTypeName(paramType)} }`;
270        }
271      }
272      return elementType.name + type;
273    } else if (type === 'union' && types?.length) {
274      return renderUnion(types);
275    } else if (elementType && elementType.type === 'union' && elementType?.types?.length) {
276      const unionTypes = elementType?.types || [];
277      return (
278        <>
279          ({renderUnion(unionTypes)}){type === 'array' && '[]'}
280        </>
281      );
282    } else if (declaration?.signatures) {
283      const baseSignature = declaration.signatures[0];
284      if (baseSignature?.parameters?.length) {
285        return (
286          <>
287            (
288            {baseSignature.parameters?.map((param, index) => (
289              <span key={`param-${index}-${param.name}`}>
290                {param.name}: {resolveTypeName(param.type)}
291                {index + 1 !== baseSignature.parameters?.length && ', '}
292              </span>
293            ))}
294            ) {'=>'} {resolveTypeName(baseSignature.type)}
295          </>
296        );
297      } else {
298        return (
299          <>
300            {'() =>'} {resolveTypeName(baseSignature.type)}
301          </>
302        );
303      }
304    } else if (type === 'reflection' && declaration?.children) {
305      return (
306        <>
307          {'{\n'}
308          {declaration?.children.map((child: PropData, i) => (
309            <span key={`reflection-${name}-${i}`}>
310              {'  '}
311              {child.name + ': '}
312              {resolveTypeName(child.type)}
313              {i + 1 !== declaration?.children?.length ? ', ' : null}
314              {'\n'}
315            </span>
316          ))}
317          {'}'}
318        </>
319      );
320    } else if (type === 'tuple' && elements) {
321      return (
322        <>
323          [
324          {elements.map((elem, i) => (
325            <span key={`tuple-${name}-${i}`}>
326              {resolveTypeName(elem)}
327              {i + 1 !== elements.length ? ', ' : null}
328            </span>
329          ))}
330          ]
331        </>
332      );
333    } else if (type === 'query' && queryType) {
334      return queryType.name;
335    } else if (type === 'literal' && typeof value === 'boolean') {
336      return `${value}`;
337    } else if (type === 'literal' && (value || (typeof value === 'number' && value === 0))) {
338      return `'${value}'`;
339    } else if (type === 'intersection' && types) {
340      return types
341        .filter(({ name }) => !omittableTypes.includes(name ?? ''))
342        .map((value, index, array) => (
343          <span key={`intersection-${name}-${index}`}>
344            {resolveTypeName(value)}
345            {index + 1 !== array.length && ' & '}
346          </span>
347        ));
348    } else if (type === 'indexedAccess') {
349      return `${objectType?.name}['${indexType?.value}']`;
350    } else if (type === 'typeOperator') {
351      return operator || 'undefined';
352    } else if (type === 'intrinsic') {
353      return name || 'undefined';
354    } else if (value === null) {
355      return 'null';
356    }
357    return 'undefined';
358  } catch (e) {
359    console.warn('Type resolve has failed!', e);
360    return 'undefined';
361  }
362};
363
364export const parseParamName = (name: string) => (name.startsWith('__') ? name.substr(2) : name);
365
366export const renderParamRow = ({
367  comment,
368  name,
369  type,
370  flags,
371  defaultValue,
372}: MethodParamData): JSX.Element => {
373  const defaultData = getTagData('default', comment);
374  const initValue = parseCommentContent(
375    defaultValue || (defaultData ? getCommentContent(defaultData.content) : '')
376  );
377  return (
378    <Row key={`param-${name}`}>
379      <Cell>
380        <BOLD>{parseParamName(name)}</BOLD>
381        {renderFlags(flags, initValue)}
382      </Cell>
383      <Cell>
384        <APIDataType typeDefinition={type} />
385      </Cell>
386      <Cell>
387        <CommentTextBlock
388          comment={comment}
389          afterContent={renderDefaultValue(initValue)}
390          emptyCommentFallback="-"
391        />
392      </Cell>
393    </Row>
394  );
395};
396
397export const ParamsTableHeadRow = () => (
398  <TableHead>
399    <Row>
400      <HeaderCell>Name</HeaderCell>
401      <HeaderCell>Type</HeaderCell>
402      <HeaderCell>Description</HeaderCell>
403    </Row>
404  </TableHead>
405);
406
407export const renderParams = (parameters: MethodParamData[]) => (
408  <Table>
409    <ParamsTableHeadRow />
410    <tbody>{parameters?.map(renderParamRow)}</tbody>
411  </Table>
412);
413
414export const listParams = (parameters: MethodParamData[]) =>
415  parameters ? parameters?.map(param => parseParamName(param.name)).join(', ') : '';
416
417export const renderDefaultValue = (defaultValue?: string) =>
418  defaultValue && defaultValue !== '...' ? (
419    <div css={defaultValueContainerStyle}>
420      <BOLD>Default:</BOLD> <CODE>{defaultValue}</CODE>
421    </div>
422  ) : undefined;
423
424export const renderTypeOrSignatureType = (
425  type?: TypeDefinitionData,
426  signatures?: MethodSignatureData[] | TypeSignaturesData[],
427  allowBlock: boolean = false
428) => {
429  if (signatures && signatures.length) {
430    return (
431      <CODE key={`signature-type-${signatures[0].name}`}>
432        (
433        {signatures?.map(({ parameters }) =>
434          parameters?.map(param => (
435            <span key={`signature-param-${param.name}`}>
436              {param.name}
437              {param.flags?.isOptional && '?'}: {resolveTypeName(param.type)}
438            </span>
439          ))
440        )}
441        ) =&gt; {signatures[0].type ? resolveTypeName(signatures[0].type) : 'void'}
442      </CODE>
443    );
444  } else if (type) {
445    if (allowBlock) {
446      return <APIDataType typeDefinition={type} />;
447    }
448    return <CODE key={`signature-type-${type.name}`}>{resolveTypeName(type)}</CODE>;
449  }
450  return undefined;
451};
452
453export const renderFlags = (flags?: TypePropertyDataFlags, defaultValue?: string) =>
454  (flags?.isOptional || defaultValue) && (
455    <>
456      <br />
457      <span css={STYLES_OPTIONAL}>(optional)</span>
458    </>
459  );
460
461export const renderIndexSignature = (kind: TypeDocKind) =>
462  kind === TypeDocKind.Parameter && (
463    <>
464      <br />
465      <A
466        css={STYLES_OPTIONAL}
467        href="https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures"
468        openInNewTab
469        isStyled>
470        (index signature)
471      </A>
472    </>
473  );
474
475export type CommentTextBlockProps = {
476  comment?: CommentData;
477  components?: MDComponents;
478  beforeContent?: JSX.Element;
479  afterContent?: JSX.Element;
480  includePlatforms?: boolean;
481  inlineHeaders?: boolean;
482  emptyCommentFallback?: string;
483};
484
485export const parseCommentContent = (content?: string): string =>
486  content && content.length ? content.replace(/&ast;/g, '*').replace(/\t/g, '') : '';
487
488export const getCommentOrSignatureComment = (
489  comment?: CommentData,
490  signatures?: MethodSignatureData[] | TypeSignaturesData[]
491) => comment || (signatures && signatures[0]?.comment);
492
493export const getTagData = (tagName: string, comment?: CommentData) =>
494  getAllTagData(tagName, comment)?.[0];
495
496export const getAllTagData = (tagName: string, comment?: CommentData) =>
497  comment?.blockTags?.filter(tag => tag.tag.substring(1) === tagName);
498
499export const getTagNamesList = (comment?: CommentData) =>
500  comment && [
501    ...(getAllTagData('platform', comment)?.map(platformData =>
502      getCommentContent(platformData.content)
503    ) || []),
504    ...(getTagData('deprecated', comment) ? ['deprecated'] : []),
505    ...(getTagData('experimental', comment) ? ['experimental'] : []),
506  ];
507
508export const getMethodName = (
509  method: MethodDefinitionData,
510  apiName?: string,
511  name?: string,
512  parameters?: MethodParamData[]
513) => {
514  const isProperty = method.kind === TypeDocKind.Property && !parameters?.length;
515  const methodName = ((apiName && `${apiName}.`) ?? '') + (method.name || name);
516  if (!isProperty) {
517    return `${methodName}(${parameters ? listParams(parameters) : ''})`;
518  }
519
520  return methodName;
521};
522
523export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
524
525const PARAM_TAGS_REGEX = /@tag-\S*/g;
526
527const getParamTags = (shortText?: string) => {
528  if (!shortText || !shortText.includes('@tag-')) {
529    return undefined;
530  }
531  return Array.from(shortText.matchAll(PARAM_TAGS_REGEX), match => match[0]);
532};
533
534export const getCommentContent = (content: CommentContentData[]) => {
535  return content
536    .map(entry => entry.text)
537    .join('')
538    .trim();
539};
540
541export const CommentTextBlock = ({
542  comment,
543  beforeContent,
544  afterContent,
545  includePlatforms = true,
546  inlineHeaders = false,
547  emptyCommentFallback,
548}: CommentTextBlockProps) => {
549  const content = comment && comment.summary ? getCommentContent(comment.summary) : undefined;
550
551  if (emptyCommentFallback && (!comment || !content || !content.length)) {
552    return <>{emptyCommentFallback}</>;
553  }
554
555  const paramTags = content ? getParamTags(content) : undefined;
556  const parsedContent = (
557    <ReactMarkdown components={mdComponents} remarkPlugins={[remarkGfm]}>
558      {parseCommentContent(paramTags ? content?.replaceAll(PARAM_TAGS_REGEX, '') : content)}
559    </ReactMarkdown>
560  );
561
562  const examples = getAllTagData('example', comment);
563  const exampleText = examples?.map((example, index) => (
564    <Fragment key={'example-' + index}>
565      {inlineHeaders ? (
566        <div css={STYLES_EXAMPLE_IN_TABLE}>
567          <BOLD>Example</BOLD>
568        </div>
569      ) : (
570        <div css={STYLES_NESTED_SECTION_HEADER}>
571          <RawH4>Example</RawH4>
572        </div>
573      )}
574      <ReactMarkdown components={mdComponents}>{getCommentContent(example.content)}</ReactMarkdown>
575    </Fragment>
576  ));
577
578  const see = getTagData('see', comment);
579  const seeText = see && (
580    <Callout>
581      <ReactMarkdown components={mdComponents}>
582        {`**See:** ` + getCommentContent(see.content)}
583      </ReactMarkdown>
584    </Callout>
585  );
586
587  const hasPlatforms = (getAllTagData('platform', comment)?.length || 0) > 0;
588
589  return (
590    <>
591      {includePlatforms && hasPlatforms && (
592        <APISectionPlatformTags comment={comment} prefix="Only for:" />
593      )}
594      {paramTags && (
595        <>
596          <BOLD>Only for:&ensp;</BOLD>
597          {paramTags.map(tag => (
598            <Tag key={tag} name={tag.split('-')[1]} />
599          ))}
600        </>
601      )}
602      {beforeContent}
603      {parsedContent}
604      {afterContent}
605      {seeText}
606      {exampleText}
607    </>
608  );
609};
610
611export const getAPISectionHeader = (exposeInSidebar?: boolean) =>
612  exposeInSidebar ? createPermalinkedComponent(RawH4, { baseNestingLevel: 2 }) : H4;
613
614const getMonospaceHeader = (element: ComponentType<any>) => {
615  const level = parseInt(element?.displayName?.replace(/\D/g, '') ?? '0', 10);
616  return createPermalinkedComponent(element, {
617    baseNestingLevel: level !== 0 ? level : undefined,
618    sidebarType: HeadingType.InlineCode,
619  });
620};
621
622export const H3Code = getMonospaceHeader(RawH3);
623export const H4Code = getMonospaceHeader(RawH4);
624
625export const getComponentName = (name?: string, children: PropData[] = []) => {
626  if (name && name !== 'default') return name;
627  const ctor = children.filter((child: PropData) => child.name === 'constructor')[0];
628  return ctor?.signatures?.[0]?.type?.name ?? 'default';
629};
630
631export const STYLES_APIBOX = css({
632  borderRadius: borderRadius.md,
633  borderWidth: 1,
634  borderStyle: 'solid',
635  borderColor: theme.border.default,
636  padding: spacing[5],
637  boxShadow: shadows.xs,
638  marginBottom: spacing[6],
639  overflowX: 'hidden',
640
641  h3: {
642    marginBottom: spacing[2],
643  },
644
645  'h2, h3, h4': {
646    marginTop: 0,
647  },
648
649  th: {
650    color: theme.text.secondary,
651    padding: `${spacing[3]}px ${spacing[4]}px`,
652  },
653
654  li: {
655    marginBottom: 0,
656  },
657
658  [`.css-${tableWrapperStyle.name}`]: {
659    boxShadow: 'none',
660    marginBottom: 0,
661  },
662
663  [`@media screen and (max-width: ${breakpoints.medium + 124}px)`]: {
664    paddingInline: spacing[4],
665  },
666});
667
668export const STYLES_APIBOX_NESTED = css({
669  boxShadow: 'none',
670  marginBottom: spacing[4],
671  padding: `${spacing[4]}px ${spacing[5]}px 0`,
672
673  h4: {
674    marginTop: 0,
675  },
676});
677
678export const STYLES_APIBOX_WRAPPER = css({
679  marginBottom: spacing[4],
680  padding: `${spacing[4]}px ${spacing[5]}px 0`,
681
682  [`.css-${tableWrapperStyle.name}:last-child`]: {
683    marginBottom: spacing[4],
684  },
685});
686
687export const STYLE_APIBOX_NO_SPACING = css({ marginBottom: -spacing[5] });
688
689export const STYLES_NESTED_SECTION_HEADER = css({
690  display: 'flex',
691  borderTop: `1px solid ${theme.border.default}`,
692  borderBottom: `1px solid ${theme.border.default}`,
693  margin: `${spacing[4]}px -${spacing[5]}px ${spacing[4]}px`,
694  padding: `${spacing[2.5]}px ${spacing[5]}px`,
695  backgroundColor: theme.background.subtle,
696
697  h4: {
698    ...typography.fontSizes[16],
699    fontWeight: 600,
700    marginBottom: 0,
701    marginTop: 0,
702    color: theme.text.secondary,
703  },
704});
705
706export const STYLES_NOT_EXPOSED_HEADER = css({
707  marginBottom: spacing[1],
708  display: 'inline-block',
709
710  code: {
711    marginBottom: 0,
712  },
713});
714
715export const STYLES_OPTIONAL = css({
716  color: theme.text.secondary,
717  fontSize: '90%',
718  paddingTop: 22,
719});
720
721export const STYLES_SECONDARY = css({
722  color: theme.text.secondary,
723  fontSize: '90%',
724  fontWeight: 600,
725});
726
727const defaultValueContainerStyle = css({
728  marginTop: spacing[2],
729  marginBottom: spacing[2],
730
731  '&:last-child': {
732    marginBottom: 0,
733  },
734});
735
736const STYLES_EXAMPLE_IN_TABLE = css({
737  margin: `${spacing[2]}px 0`,
738});
739
740export const STYLES_ELEMENT_SPACING = css({
741  marginBottom: spacing[4],
742});
743