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