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 mdInlineComponents: MDComponents = {
100  ...mdComponents,
101  p: ({ children }) => (children ? <span>{children}</span> : null),
102};
103
104export const mdInlineComponentsNoValidation: MDComponents = {
105  ...mdInlineComponents,
106  a: ({ href, children }) => <A href={href}>{children}</A>,
107};
108
109const nonLinkableTypes = [
110  'ColorValue',
111  'Component',
112  'ComponentClass',
113  'PureComponent',
114  'E',
115  'EventSubscription',
116  'Listener',
117  'NativeSyntheticEvent',
118  'ParsedQs',
119  'ServiceActionResult',
120  'T',
121  'TaskOptions',
122  'Uint8Array',
123  // React & React Native
124  'React.FC',
125  'ForwardRefExoticComponent',
126  'StyleProp',
127  // Cross-package permissions management
128  'RequestPermissionMethod',
129  'GetPermissionMethod',
130  'Options',
131  'PermissionHookBehavior',
132];
133
134/**
135 * List of type names that should not be visible in the docs.
136 */
137const omittableTypes = [
138  // Internal React type that adds `ref` prop to the component
139  'RefAttributes',
140];
141
142/**
143 * Map of internal names/type names that should be replaced with something more developer-friendly.
144 */
145const replaceableTypes: Partial<Record<string, string>> = {
146  ForwardRefExoticComponent: 'Component',
147};
148
149const hardcodedTypeLinks: Record<string, string> = {
150  AVPlaybackSource: '/versions/latest/sdk/av/#avplaybacksource',
151  AVPlaybackStatus: '/versions/latest/sdk/av/#avplaybackstatus',
152  AVPlaybackStatusToSet: '/versions/latest/sdk/av/#avplaybackstatustoset',
153  Blob: 'https://developer.mozilla.org/en-US/docs/Web/API/Blob',
154  Date: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date',
155  DeviceSensor: '/versions/latest/sdk/sensors',
156  Element: 'https://www.typescriptlang.org/docs/handbook/jsx.html#function-component',
157  Error: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error',
158  ExpoConfig:
159    'https://github.com/expo/expo/blob/main/packages/%40expo/config-types/src/ExpoConfig.ts',
160  File: 'https://developer.mozilla.org/en-US/docs/Web/API/File',
161  FileList: 'https://developer.mozilla.org/en-US/docs/Web/API/FileList',
162  MessageEvent: 'https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent',
163  Omit: 'https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys',
164  Pick: 'https://www.typescriptlang.org/docs/handbook/utility-types.html#picktype-keys',
165  Partial: 'https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype',
166  Promise:
167    'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise',
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  withDash?: boolean;
483  beforeContent?: JSX.Element;
484  afterContent?: JSX.Element;
485  includePlatforms?: 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  components = mdComponents,
548  withDash,
549  beforeContent,
550  afterContent,
551  includePlatforms = true,
552  emptyCommentFallback,
553}: CommentTextBlockProps) => {
554  const content = comment && comment.summary ? getCommentContent(comment.summary) : undefined;
555
556  if (emptyCommentFallback && (!comment || !content || !content.length)) {
557    return <>{emptyCommentFallback}</>;
558  }
559
560  const paramTags = content ? getParamTags(content) : undefined;
561  const parsedContent = (
562    <ReactMarkdown components={components} remarkPlugins={[remarkGfm]}>
563      {parseCommentContent(paramTags ? content?.replaceAll(PARAM_TAGS_REGEX, '') : content)}
564    </ReactMarkdown>
565  );
566
567  const examples = getAllTagData('example', comment);
568  const exampleText = examples?.map((example, index) => (
569    <Fragment key={'example-' + index}>
570      {components !== mdComponents ? (
571        <div css={STYLES_EXAMPLE_IN_TABLE}>
572          <BOLD>Example</BOLD>
573        </div>
574      ) : (
575        <div css={STYLES_NESTED_SECTION_HEADER}>
576          <RawH4>Example</RawH4>
577        </div>
578      )}
579      <ReactMarkdown components={components}>{getCommentContent(example.content)}</ReactMarkdown>
580    </Fragment>
581  ));
582
583  const see = getTagData('see', comment);
584  const seeText = see && (
585    <Callout>
586      <BOLD>See: </BOLD>
587      <ReactMarkdown components={mdInlineComponents}>
588        {getCommentContent(see.content)}
589      </ReactMarkdown>
590    </Callout>
591  );
592
593  const hasPlatforms = (getAllTagData('platform', comment)?.length || 0) > 0;
594
595  return (
596    <>
597      {!withDash && includePlatforms && hasPlatforms && (
598        <APISectionPlatformTags comment={comment} prefix="Only for:" />
599      )}
600      {paramTags && (
601        <>
602          <BOLD>Only for:&ensp;</BOLD>
603          {paramTags.map(tag => (
604            <Tag key={tag} name={tag.split('-')[1]} />
605          ))}
606        </>
607      )}
608      {beforeContent}
609      {withDash && parsedContent && ' - '}
610      {withDash && includePlatforms && <APISectionPlatformTags comment={comment} />}
611      {parsedContent}
612      {afterContent}
613      {seeText}
614      {exampleText}
615    </>
616  );
617};
618
619export const getAPISectionHeader = (exposeInSidebar?: boolean) =>
620  exposeInSidebar ? createPermalinkedComponent(RawH4, { baseNestingLevel: 2 }) : H4;
621
622const getMonospaceHeader = (element: ComponentType<any>) => {
623  const level = parseInt(element?.displayName?.replace(/\D/g, '') ?? '0', 10);
624  return createPermalinkedComponent(element, {
625    baseNestingLevel: level !== 0 ? level : undefined,
626    sidebarType: HeadingType.InlineCode,
627  });
628};
629
630export const H3Code = getMonospaceHeader(RawH3);
631export const H4Code = getMonospaceHeader(RawH4);
632
633export const getComponentName = (name?: string, children: PropData[] = []) => {
634  if (name && name !== 'default') return name;
635  const ctor = children.filter((child: PropData) => child.name === 'constructor')[0];
636  return ctor?.signatures?.[0]?.type?.name ?? 'default';
637};
638
639export const STYLES_APIBOX = css({
640  borderRadius: borderRadius.medium,
641  borderWidth: 1,
642  borderStyle: 'solid',
643  borderColor: theme.border.default,
644  padding: spacing[5],
645  boxShadow: shadows.micro,
646  marginBottom: spacing[6],
647  overflowX: 'hidden',
648
649  h3: {
650    marginBottom: spacing[2],
651  },
652
653  'h2, h3, h4': {
654    marginTop: 0,
655  },
656
657  th: {
658    color: theme.text.secondary,
659    padding: `${spacing[3]}px ${spacing[4]}px`,
660  },
661
662  li: {
663    marginBottom: 0,
664  },
665
666  [`.css-${tableWrapperStyle.name}`]: {
667    boxShadow: 'none',
668    marginBottom: 0,
669  },
670
671  [`@media screen and (max-width: ${breakpoints.medium + 124}px)`]: {
672    padding: `0 ${spacing[4]}px`,
673  },
674});
675
676export const STYLES_APIBOX_NESTED = css({
677  boxShadow: 'none',
678  marginBottom: spacing[4],
679  padding: `${spacing[4]}px ${spacing[5]}px 0`,
680
681  h4: {
682    marginTop: 0,
683  },
684});
685
686export const STYLES_APIBOX_WRAPPER = css({
687  marginBottom: spacing[4],
688  padding: `${spacing[4]}px ${spacing[5]}px 0`,
689
690  [`.css-${tableWrapperStyle.name}:last-child`]: {
691    marginBottom: spacing[4],
692  },
693});
694
695export const STYLE_APIBOX_NO_SPACING = css({ marginBottom: -spacing[5] });
696
697export const STYLES_NESTED_SECTION_HEADER = css({
698  display: 'flex',
699  borderTop: `1px solid ${theme.border.default}`,
700  borderBottom: `1px solid ${theme.border.default}`,
701  margin: `${spacing[4]}px -${spacing[5]}px ${spacing[4]}px`,
702  padding: `${spacing[2]}px ${spacing[5]}px`,
703  backgroundColor: theme.background.secondary,
704
705  h4: {
706    ...typography.fontSizes[16],
707    fontWeight: 600,
708    marginBottom: 0,
709    marginTop: 0,
710    color: theme.text.secondary,
711  },
712});
713
714export const STYLES_NOT_EXPOSED_HEADER = css({
715  marginBottom: spacing[1],
716  display: 'inline-block',
717
718  code: {
719    marginBottom: 0,
720  },
721});
722
723export const STYLES_OPTIONAL = css({
724  color: theme.text.secondary,
725  fontSize: '90%',
726  paddingTop: 22,
727});
728
729export const STYLES_SECONDARY = css({
730  color: theme.text.secondary,
731  fontSize: '90%',
732  fontWeight: 600,
733});
734
735const defaultValueContainerStyle = css({
736  marginTop: spacing[2],
737  marginBottom: spacing[2],
738
739  '&:last-child': {
740    marginBottom: 0,
741  },
742});
743
744const STYLES_EXAMPLE_IN_TABLE = css({
745  margin: `${spacing[2]}px 0`,
746});
747
748export const STYLES_ELEMENT_SPACING = css({
749  marginBottom: spacing[4],
750});
751