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