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