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