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