1import { css } from '@emotion/react'; 2import { borderRadius, breakpoints, spacing, theme, typography } from '@expo/styleguide'; 3import ReactMarkdown from 'react-markdown'; 4 5import { InlineCode } from '../base/code'; 6 7import { createPermalinkedComponent } from '~/common/create-permalinked-component'; 8import { HeadingType } from '~/common/headingManager'; 9import { PDIVHEADER } from '~/components/base/paragraph'; 10import { APIBox } from '~/components/plugins/APIBox'; 11import { mdComponents, mdInlineComponents } from '~/components/plugins/api/APISectionUtils'; 12import { Collapsible } from '~/ui/components/Collapsible'; 13import { CALLOUT } from '~/ui/components/Text'; 14 15type PropertyMeta = { 16 regexHuman?: string; 17 deprecated?: boolean; 18 hidden?: boolean; 19 expoKit?: string; 20 bareWorkflow?: string; 21}; 22 23export type Property = { 24 description?: string; 25 type?: string | string[]; 26 meta?: PropertyMeta; 27 pattern?: string; 28 enum?: string[]; 29 example?: any; 30 exampleString?: string; 31 host?: object; 32 properties?: Record<string, Property>; 33 items?: { 34 properties?: Record<string, Property>; 35 [key: string]: any; 36 }; 37 uniqueItems?: boolean; 38 additionalProperties?: boolean; 39}; 40 41type FormattedProperty = { 42 name: string; 43 description: string; 44 type?: string; 45 example?: string; 46 expoKit?: string; 47 bareWorkflow?: string; 48 subproperties: FormattedProperty[]; 49 parent?: string; 50}; 51 52type AppConfigSchemaProps = { 53 schema: Record<string, Property>; 54}; 55 56const Anchor = createPermalinkedComponent(PDIVHEADER, { 57 baseNestingLevel: 3, 58 sidebarType: HeadingType.InlineCode, 59}); 60 61const PropertyName = ({ name, nestingLevel }: { name: string; nestingLevel: number }) => ( 62 <Anchor level={nestingLevel} data-testid={name} data-heading="true"> 63 <InlineCode css={typography.fontSizes[16]}>{name}</InlineCode> 64 </Anchor> 65); 66 67export function formatSchema(rawSchema: [string, Property][]) { 68 const formattedSchema: FormattedProperty[] = []; 69 70 rawSchema.map(property => { 71 appendProperty(formattedSchema, property); 72 }); 73 74 return formattedSchema; 75} 76 77function appendProperty(formattedSchema: FormattedProperty[], property: [string, Property]) { 78 const propertyValue = property[1]; 79 80 if (propertyValue.meta && (propertyValue.meta.deprecated || propertyValue.meta.hidden)) { 81 return; 82 } 83 84 formattedSchema.push(formatProperty(property)); 85} 86 87function formatProperty(property: [string, Property], parent?: string): FormattedProperty { 88 const propertyKey = property[0]; 89 const propertyValue = property[1]; 90 91 const subproperties: FormattedProperty[] = []; 92 93 if (propertyValue.properties) { 94 Object.entries(propertyValue.properties).forEach(subproperty => { 95 subproperties.push( 96 formatProperty(subproperty, parent ? `${parent}.${propertyKey}` : propertyKey) 97 ); 98 }); 99 } // note: sub-properties are sometimes nested within "items" 100 else if (propertyValue.items && propertyValue.items.properties) { 101 Object.entries(propertyValue.items.properties).forEach(subproperty => { 102 subproperties.push( 103 formatProperty(subproperty, parent ? `${parent}.${propertyKey}` : propertyKey) 104 ); 105 }); 106 } 107 108 return { 109 name: propertyKey, 110 description: createDescription(property), 111 type: _getType(propertyValue), 112 example: propertyValue.exampleString?.replaceAll('\n', ''), 113 expoKit: propertyValue?.meta?.expoKit, 114 bareWorkflow: propertyValue?.meta?.bareWorkflow, 115 subproperties, 116 parent, 117 }; 118} 119 120export function _getType({ enum: enm, type }: Partial<Property>) { 121 return enm ? 'enum' : type?.toString().replace(',', ' || '); 122} 123 124export function createDescription(propertyEntry: [string, Property]) { 125 const { description, meta } = propertyEntry[1]; 126 127 let propertyDescription = ``; 128 if (description) { 129 propertyDescription += description; 130 } 131 if (meta && meta.regexHuman) { 132 propertyDescription += `\n\n` + meta.regexHuman; 133 } 134 135 return propertyDescription; 136} 137 138const AppConfigSchemaPropertiesTable = ({ schema }: AppConfigSchemaProps) => { 139 const rawSchema = Object.entries(schema); 140 const formattedSchema = formatSchema(rawSchema); 141 142 return ( 143 <div> 144 {formattedSchema.map((formattedProperty, index) => ( 145 <AppConfigProperty 146 {...formattedProperty} 147 key={`${formattedProperty.name}-${index}`} 148 nestingLevel={0} 149 /> 150 ))} 151 </div> 152 ); 153}; 154 155const AppConfigProperty = ({ 156 name, 157 description, 158 example, 159 expoKit, 160 bareWorkflow, 161 type, 162 nestingLevel, 163 subproperties, 164 parent, 165}: FormattedProperty & { nestingLevel: number }) => ( 166 <APIBox css={boxStyle}> 167 <PropertyName name={name} nestingLevel={nestingLevel} /> 168 <CALLOUT theme="secondary" data-text="true"> 169 Type: <InlineCode>{type || 'undefined'}</InlineCode> 170 {nestingLevel > 0 && ( 171 <> 172  • Path:{' '} 173 <code css={secondaryCodeLineStyle}> 174 {parent}.{name} 175 </code> 176 </> 177 )} 178 </CALLOUT> 179 <br /> 180 <ReactMarkdown components={mdComponents}>{description}</ReactMarkdown> 181 {expoKit && ( 182 <Collapsible summary="ExpoKit"> 183 <ReactMarkdown components={mdComponents}>{expoKit}</ReactMarkdown> 184 </Collapsible> 185 )} 186 {bareWorkflow && ( 187 <Collapsible summary="Bare Workflow"> 188 <ReactMarkdown components={mdComponents}>{bareWorkflow}</ReactMarkdown> 189 </Collapsible> 190 )} 191 {example && <ReactMarkdown components={mdInlineComponents}>{`> ${example}`}</ReactMarkdown>} 192 <div> 193 {subproperties.length > 0 && 194 subproperties.map((formattedProperty, index) => ( 195 <AppConfigProperty 196 {...formattedProperty} 197 key={`${name}-${index}`} 198 nestingLevel={nestingLevel + 1} 199 /> 200 ))} 201 </div> 202 </APIBox> 203); 204 205const boxStyle = css({ 206 boxShadow: 'none', 207 marginBottom: 0, 208 borderRadius: 0, 209 borderBottomWidth: 0, 210 211 '&:first-of-type': { 212 borderTopLeftRadius: borderRadius.medium, 213 borderTopRightRadius: borderRadius.medium, 214 }, 215 216 '&:last-of-type': { 217 borderBottomLeftRadius: borderRadius.medium, 218 borderBottomRightRadius: borderRadius.medium, 219 marginBottom: spacing[4], 220 borderBottomWidth: 1, 221 }, 222 223 [`@media screen and (max-width: ${breakpoints.medium + 124}px)`]: { 224 paddingTop: spacing[4], 225 }, 226}); 227 228const secondaryCodeLineStyle = css({ 229 fontFamily: typography.fontStacks.mono, 230 color: theme.text.secondary, 231 padding: `0 ${spacing[1]}px`, 232 wordBreak: 'break-word', 233}); 234 235export default AppConfigSchemaPropertiesTable; 236