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