1import { ClassDefinitionData, GeneratedData } from '~/components/plugins/api/APIDataTypes'; 2import APISectionClasses from '~/components/plugins/api/APISectionClasses'; 3import APISectionComponents from '~/components/plugins/api/APISectionComponents'; 4import APISectionConstants from '~/components/plugins/api/APISectionConstants'; 5import APISectionEnums from '~/components/plugins/api/APISectionEnums'; 6import APISectionInterfaces from '~/components/plugins/api/APISectionInterfaces'; 7import APISectionMethods from '~/components/plugins/api/APISectionMethods'; 8import APISectionNamespaces from '~/components/plugins/api/APISectionNamespaces'; 9import APISectionProps from '~/components/plugins/api/APISectionProps'; 10import APISectionTypes from '~/components/plugins/api/APISectionTypes'; 11import { getComponentName, TypeDocKind } from '~/components/plugins/api/APISectionUtils'; 12import { usePageApiVersion } from '~/providers/page-api-version'; 13import versions from '~/public/static/constants/versions.json'; 14import { P } from '~/ui/components/Text'; 15 16const { LATEST_VERSION } = versions; 17 18type Props = { 19 packageName: string; 20 apiName?: string; 21 forceVersion?: string; 22 strictTypes?: boolean; 23 testRequire?: any; 24}; 25 26const filterDataByKind = ( 27 entries: GeneratedData[] = [], 28 kind: TypeDocKind | TypeDocKind[], 29 additionalCondition: (entry: GeneratedData) => boolean = () => true 30) => 31 entries.filter( 32 (entry: GeneratedData) => 33 (Array.isArray(kind) ? kind.includes(entry.kind) : entry.kind === kind) && 34 additionalCondition(entry) 35 ); 36 37const isHook = ({ name }: GeneratedData) => 38 name.startsWith('use') && 39 // note(simek): hardcode this exception until the method will be renamed 40 name !== 'useSystemBrightnessAsync'; 41 42const isListener = ({ name }: GeneratedData) => 43 name.endsWith('Listener') || name.endsWith('Listeners'); 44 45const isProp = ({ name }: GeneratedData) => name.includes('Props') && name !== 'ErrorRecoveryProps'; 46 47const isComponent = ({ type, extendedTypes, signatures }: GeneratedData) => { 48 if (type?.name && ['React.FC', 'ForwardRefExoticComponent'].includes(type?.name)) { 49 return true; 50 } else if (extendedTypes && extendedTypes.length) { 51 return extendedTypes[0].name === 'Component' || extendedTypes[0].name === 'PureComponent'; 52 } else if (signatures && signatures.length) { 53 if ( 54 signatures[0].type.name === 'Element' || 55 (signatures[0].type.types && signatures[0].type.types.map(t => t.name).includes('Element')) || 56 (signatures[0].parameters && signatures[0].parameters[0].name === 'props') 57 ) { 58 return true; 59 } 60 } 61 return false; 62}; 63 64const isConstant = ({ name, type }: GeneratedData) => 65 !['default', 'Constants', 'EventEmitter'].includes(name) && 66 !(type?.name && ['React.FC', 'ForwardRefExoticComponent'].includes(type?.name)); 67 68const renderAPI = ( 69 packageName: string, 70 version: string = 'unversioned', 71 apiName?: string, 72 strictTypes: boolean = false, 73 testRequire: any = undefined 74): JSX.Element => { 75 try { 76 const { children: data } = testRequire 77 ? testRequire(`~/public/static/data/${version}/${packageName}.json`) 78 : require(`~/public/static/data/${version}/${packageName}.json`); 79 80 const methods = filterDataByKind( 81 data, 82 TypeDocKind.Function, 83 entry => !isListener(entry) && !isHook(entry) && !isComponent(entry) 84 ); 85 const eventSubscriptions = filterDataByKind(data, TypeDocKind.Function, isListener); 86 87 const types = filterDataByKind( 88 data, 89 TypeDocKind.TypeAlias, 90 entry => 91 !isProp(entry) && 92 !!( 93 entry.type.declaration || 94 entry.type.types || 95 entry.type.type || 96 entry.type.typeArguments 97 ) && 98 (strictTypes && apiName ? entry.name.startsWith(apiName) : true) 99 ); 100 101 const props = filterDataByKind( 102 data, 103 [TypeDocKind.TypeAlias, TypeDocKind.Interface], 104 entry => 105 isProp(entry) && 106 (entry.kind === TypeDocKind.TypeAlias 107 ? !!(entry.type.types || entry.type.declaration?.children) 108 : true) 109 ); 110 const defaultProps = filterDataByKind( 111 data 112 .filter((entry: GeneratedData) => entry.kind === TypeDocKind.Class) 113 .map((entry: GeneratedData) => entry.children) 114 .flat(), 115 TypeDocKind.Property, 116 entry => entry.name === 'defaultProps' 117 )[0]; 118 119 const enums = filterDataByKind(data, TypeDocKind.Enum, entry => entry.name !== 'default'); 120 const interfaces = filterDataByKind( 121 data, 122 TypeDocKind.Interface, 123 entry => !entry.name.includes('Props') 124 ); 125 const constants = filterDataByKind(data, TypeDocKind.Variable, entry => isConstant(entry)); 126 127 const components = filterDataByKind( 128 data, 129 [TypeDocKind.Variable, TypeDocKind.Class, TypeDocKind.Function], 130 entry => isComponent(entry) 131 ); 132 const componentsPropNames = components.map( 133 ({ name, children }) => `${getComponentName(name, children)}Props` 134 ); 135 const componentsProps = filterDataByKind( 136 props, 137 [TypeDocKind.TypeAlias, TypeDocKind.Interface], 138 entry => componentsPropNames.includes(entry.name) 139 ); 140 141 const namespaces = filterDataByKind(data, TypeDocKind.Namespace); 142 143 const classes = filterDataByKind( 144 data, 145 TypeDocKind.Class, 146 entry => !isComponent(entry) && entry.name !== 'default' 147 ); 148 149 const componentsChildren = components 150 .map((cls: ClassDefinitionData) => 151 cls.children?.filter( 152 child => 153 (child?.kind === TypeDocKind.Method || child?.kind === TypeDocKind.Property) && 154 child?.flags?.isExternal !== true && 155 !child.inheritedFrom && 156 child.name !== 'render' && 157 // note(simek): hide unannotated "private" methods 158 !child.name.startsWith('_') 159 ) 160 ) 161 .flat(); 162 163 const methodsNames = methods.map(method => method.name); 164 const staticMethods = componentsChildren.filter( 165 // note(simek): hide duplicate exports from class components 166 method => 167 method?.kind === TypeDocKind.Method && 168 method?.flags?.isStatic === true && 169 !methodsNames.includes(method.name) && 170 !isHook(method as GeneratedData) 171 ); 172 const componentMethods = componentsChildren 173 .filter( 174 method => 175 method?.kind === TypeDocKind.Method && 176 method?.flags?.isStatic !== true && 177 !method?.overwrites 178 ) 179 .filter(Boolean); 180 181 const hooks = filterDataByKind( 182 [...data, ...componentsChildren].filter(Boolean), 183 [TypeDocKind.Function, TypeDocKind.Property], 184 isHook 185 ); 186 187 return ( 188 <> 189 <APISectionComponents data={components} componentsProps={componentsProps} /> 190 <APISectionMethods data={staticMethods} header="Static Methods" /> 191 <APISectionMethods data={componentMethods} header="Component Methods" /> 192 <APISectionConstants data={constants} apiName={apiName} /> 193 <APISectionMethods data={hooks} header="Hooks" /> 194 <APISectionClasses data={classes} /> 195 {props && !componentsProps.length ? ( 196 <APISectionProps data={props} defaultProps={defaultProps} /> 197 ) : null} 198 <APISectionMethods data={methods} apiName={apiName} /> 199 <APISectionMethods 200 data={eventSubscriptions} 201 apiName={apiName} 202 header="Event Subscriptions" 203 /> 204 <APISectionNamespaces data={namespaces} /> 205 <APISectionInterfaces data={interfaces} /> 206 <APISectionTypes data={types} /> 207 <APISectionEnums data={enums} /> 208 </> 209 ); 210 } catch { 211 return <P>No API data file found, sorry!</P>; 212 } 213}; 214 215const APISection = ({ 216 packageName, 217 apiName, 218 forceVersion, 219 strictTypes = false, 220 testRequire = undefined, 221}: Props) => { 222 const { version } = usePageApiVersion(); 223 const resolvedVersion = 224 forceVersion || 225 (version === 'unversioned' ? version : version === 'latest' ? LATEST_VERSION : version); 226 return renderAPI(packageName, resolvedVersion, apiName, strictTypes, testRequire); 227}; 228 229export default APISection; 230