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