xref: /expo/docs/components/plugins/APISection.tsx (revision cbbfd658)
1import React, { useContext } from 'react';
2
3import DocumentationPageContext from '~/components/DocumentationPageContext';
4import { P } from '~/components/base/paragraph';
5import { GeneratedData } from '~/components/plugins/api/APIDataTypes';
6import APISectionClasses from '~/components/plugins/api/APISectionClasses';
7import APISectionComponents from '~/components/plugins/api/APISectionComponents';
8import APISectionConstants from '~/components/plugins/api/APISectionConstants';
9import APISectionEnums from '~/components/plugins/api/APISectionEnums';
10import APISectionInterfaces from '~/components/plugins/api/APISectionInterfaces';
11import APISectionMethods from '~/components/plugins/api/APISectionMethods';
12import APISectionProps from '~/components/plugins/api/APISectionProps';
13import APISectionTypes from '~/components/plugins/api/APISectionTypes';
14import { TypeDocKind } from '~/components/plugins/api/APISectionUtils';
15
16const LATEST_VERSION = `v${require('~/package.json').version}`;
17
18type Props = {
19  packageName: string;
20  apiName?: string;
21  forceVersion?: string;
22};
23
24const filterDataByKind = (
25  entries: GeneratedData[] = [],
26  kind: TypeDocKind | TypeDocKind[],
27  additionalCondition: (entry: GeneratedData) => boolean = () => true
28) =>
29  entries.filter(
30    (entry: GeneratedData) =>
31      (Array.isArray(kind) ? kind.includes(entry.kind) : entry.kind === kind) &&
32      additionalCondition(entry)
33  );
34
35const isHook = ({ name }: GeneratedData) =>
36  name.startsWith('use') &&
37  // note(simek): hardcode this exception until the method will be renamed
38  name !== 'useSystemBrightnessAsync';
39
40const isListener = ({ name }: GeneratedData) =>
41  name.endsWith('Listener') || name.endsWith('Listeners');
42
43const isProp = ({ name }: GeneratedData) => name.includes('Props') && name !== 'ErrorRecoveryProps';
44
45const isComponent = ({ type, extendedTypes }: GeneratedData) =>
46  type?.name === 'React.FC' ||
47  (extendedTypes && extendedTypes.length ? extendedTypes[0].name === 'Component' : false);
48
49const isConstant = ({ flags, name, type }: GeneratedData) =>
50  (flags?.isConst || false) && name !== 'default' && type?.name !== 'React.FC';
51
52const renderAPI = (
53  packageName: string,
54  version: string = 'unversioned',
55  apiName?: string,
56  isTestMode: boolean = false
57): JSX.Element => {
58  try {
59    // note(simek): When the path prefix is interpolated Next or Webpack fails to locate the file
60    const { children: data } = isTestMode
61      ? require(`../../public/static/data/${version}/${packageName}.json`)
62      : require(`~/public/static/data/${version}/${packageName}.json`);
63
64    const methods = filterDataByKind(
65      data,
66      TypeDocKind.Function,
67      entry => !isListener(entry) && !isHook(entry)
68    );
69    const hooks = filterDataByKind(data, TypeDocKind.Function, isHook);
70    const eventSubscriptions = filterDataByKind(data, TypeDocKind.Function, isListener);
71
72    const types = filterDataByKind(
73      data,
74      TypeDocKind.TypeAlias,
75      entry =>
76        !isProp(entry) &&
77        !!(
78          entry.type.declaration ||
79          entry.type.types ||
80          entry.type.type ||
81          entry.type.typeArguments
82        )
83    );
84
85    const props = filterDataByKind(
86      data,
87      TypeDocKind.TypeAlias,
88      entry => isProp(entry) && !!(entry.type.types || entry.type.declaration?.children)
89    );
90    const defaultProps = filterDataByKind(
91      data
92        .filter((entry: GeneratedData) => entry.kind === TypeDocKind.Class)
93        .map((entry: GeneratedData) => entry.children)
94        .flat(),
95      TypeDocKind.Property,
96      entry => entry.name === 'defaultProps'
97    )[0];
98
99    const enums = filterDataByKind(data, [TypeDocKind.Enum, TypeDocKind.LegacyEnum]);
100    const interfaces = filterDataByKind(data, TypeDocKind.Interface);
101    const constants = filterDataByKind(data, TypeDocKind.Variable, entry => isConstant(entry));
102
103    const components = filterDataByKind(data, [TypeDocKind.Variable, TypeDocKind.Class], entry =>
104      isComponent(entry)
105    );
106    const componentsPropNames = components.map(component => `${component.name}Props`);
107    const componentsProps = filterDataByKind(props, TypeDocKind.TypeAlias, entry =>
108      componentsPropNames.includes(entry.name)
109    );
110
111    const classes = filterDataByKind(data, TypeDocKind.Class, entry => !isComponent(entry));
112
113    return (
114      <>
115        <APISectionComponents data={components} componentsProps={componentsProps} />
116        <APISectionClasses data={classes} />
117        <APISectionConstants data={constants} apiName={apiName} />
118        <APISectionMethods data={hooks} header="Hooks" />
119        <APISectionMethods data={methods} apiName={apiName} />
120        <APISectionMethods
121          data={eventSubscriptions}
122          apiName={apiName}
123          header="Event Subscriptions"
124        />
125        {props && !componentsProps.length ? (
126          <APISectionProps data={props} defaultProps={defaultProps} />
127        ) : null}
128        <APISectionTypes data={types} />
129        <APISectionInterfaces data={interfaces} />
130        <APISectionEnums data={enums} />
131      </>
132    );
133  } catch (error) {
134    return <P>No API data file found, sorry!</P>;
135  }
136};
137
138const APISection = ({ packageName, apiName, forceVersion }: Props) => {
139  const { version } = useContext(DocumentationPageContext);
140  const resolvedVersion =
141    forceVersion ||
142    (version === 'unversioned' ? version : version === 'latest' ? LATEST_VERSION : version);
143  return renderAPI(packageName, resolvedVersion, apiName, !!forceVersion);
144};
145
146export default APISection;
147