1import { spacing } from '@expo/styleguide-base'; 2import assert from 'assert'; 3import { ComponentType, Fragment, ReactNode, useId } from 'react'; 4 5import { Table, Row as TableRow, Cell as TableCell } from '~/ui/components/Table'; 6import { CODE, P, FOOTNOTE } from '~/ui/components/Text'; 7 8interface MetadataTableProps { 9 headers: MetadataHeader[]; 10 children: MetadataProperty[]; 11} 12 13type MetadataHeader = 'Description' | 'Language Code' | 'Language' | 'Name' | 'Property' | 'Type'; 14 15interface MetadataProperty { 16 name: string; 17 nested?: number; 18 type?: string | ReactNode; 19 description?: string | ReactNode[]; 20 rules?: string[]; 21} 22 23interface MetadataPropertyProps { 24 property: MetadataProperty; 25} 26 27export function MetadataTable(props: MetadataTableProps) { 28 const id = useId(); 29 const { headers = ['Property', 'Type', 'Description'], children = [] } = props; 30 31 return ( 32 <div className="mb-2 mt-1"> 33 <Table headers={headers}> 34 {children.map(property => ( 35 <TableRow key={`${id}-${property.name}`}> 36 {headers.map(column => { 37 const Property = metadataProperties[column]; 38 assert(Property, `No metadata property renderer found for ${column}`); 39 return <Property key={`${id}-${property.name}-${column}`} property={property} />; 40 })} 41 </TableRow> 42 ))} 43 </Table> 44 </div> 45 ); 46} 47 48const metadataProperties: Record<MetadataHeader, ComponentType<MetadataPropertyProps>> = { 49 Description: MetadataDescriptionCell, 50 Language: MetadataLanguageNameCell, 51 'Language Code': MetadataLanguageCodeCell, 52 Name: MetadataNameCell, 53 Property: MetadataNameCell, 54 Type: MetadataTypeCell, 55}; 56 57function MetadataNameCell({ property }: MetadataPropertyProps) { 58 const style = { 59 display: property.nested ? 'list-item' : 'block', 60 marginLeft: property.nested ? spacing[6] * property.nested : 0, 61 listStyleType: (property.nested ?? 0) % 2 ? 'default' : 'circle', 62 }; 63 64 return ( 65 <TableCell fitContent> 66 <P css={style}> 67 <CODE>{property.name}</CODE> 68 </P> 69 </TableCell> 70 ); 71} 72 73function MetadataTypeCell({ property }: MetadataPropertyProps) { 74 return ( 75 <TableCell fitContent> 76 <P> 77 <CODE>{property.type}</CODE> 78 </P> 79 <MetadataPropertyTypeRules property={property} /> 80 </TableCell> 81 ); 82} 83 84function MetadataPropertyTypeRules({ property }: MetadataPropertyProps) { 85 const id = useId(); 86 87 return ( 88 <P css={{ marginTop: spacing[1] }}> 89 {property.rules?.map(rule => ( 90 <FOOTNOTE 91 key={`${id}-${property.name}-${rule}`} 92 tag="span" 93 theme="secondary" 94 css={{ display: 'block', whiteSpace: 'nowrap' }}> 95 {rule} 96 </FOOTNOTE> 97 ))} 98 </P> 99 ); 100} 101 102function MetadataDescriptionCell({ property }: MetadataPropertyProps) { 103 return ( 104 <TableCell> 105 {typeof property.description === 'string' ? ( 106 <P>{property.description}</P> 107 ) : ( 108 property.description?.map((item, i) => ( 109 <Fragment key={`${property.name}-${i}`}>{item}</Fragment> 110 )) 111 )} 112 </TableCell> 113 ); 114} 115 116function MetadataLanguageNameCell({ property }: MetadataPropertyProps) { 117 return ( 118 <TableCell fitContent> 119 <P>{property.name}</P> 120 </TableCell> 121 ); 122} 123 124function MetadataLanguageCodeCell({ property }: MetadataPropertyProps) { 125 return ( 126 <TableCell fitContent> 127 {typeof property.description === 'string' ? ( 128 <P> 129 <CODE>{property.description}</CODE> 130 </P> 131 ) : ( 132 property.description?.map((item, i) => ( 133 <Fragment key={`${property.name}-${i}`}>{item}</Fragment> 134 )) 135 )} 136 </TableCell> 137 ); 138} 139export function MetadataSubcategories({ children }: { children: string[] }) { 140 const id = useId(); 141 142 return ( 143 <> 144 {children.map(category => ( 145 <Fragment key={`${id}-${category}`}> 146 <CODE className="my-1">{category}</CODE>{' '} 147 </Fragment> 148 ))} 149 </> 150 ); 151} 152