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