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