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