1import { css } from '@emotion/react';
2import { theme, spacing, UndoIcon, iconSize } from '@expo/styleguide';
3import ReactMarkdown from 'react-markdown';
4
5import { H2, H3Code, H4, H4Code } from '~/components/plugins/Headings';
6import { APIDataType } from '~/components/plugins/api/APIDataType';
7import {
8  AccessorDefinitionData,
9  MethodDefinitionData,
10  MethodSignatureData,
11  PropData,
12  TypeSignaturesData,
13} from '~/components/plugins/api/APIDataTypes';
14import { APISectionDeprecationNote } from '~/components/plugins/api/APISectionDeprecationNote';
15import { APISectionPlatformTags } from '~/components/plugins/api/APISectionPlatformTags';
16import {
17  CommentTextBlock,
18  getMethodName,
19  getTagNamesList,
20  mdComponents,
21  renderParams,
22  resolveTypeName,
23  STYLES_APIBOX,
24  STYLES_APIBOX_NESTED,
25  STYLES_NESTED_SECTION_HEADER,
26  STYLES_NOT_EXPOSED_HEADER,
27  TypeDocKind,
28} from '~/components/plugins/api/APISectionUtils';
29import { LI, UL, CODE } from '~/ui/components/Text';
30
31export type APISectionMethodsProps = {
32  data: (MethodDefinitionData | PropData)[];
33  apiName?: string;
34  header?: string;
35  exposeInSidebar?: boolean;
36};
37
38export type RenderMethodOptions = {
39  apiName?: string;
40  header?: string;
41  exposeInSidebar?: boolean;
42};
43
44export const renderMethod = (
45  method: MethodDefinitionData | AccessorDefinitionData | PropData,
46  { apiName, exposeInSidebar = true }: RenderMethodOptions = {}
47): JSX.Element[] => {
48  const signatures =
49    (method as MethodDefinitionData).signatures ||
50    (method as PropData)?.type?.declaration?.signatures ||
51    (method as AccessorDefinitionData)?.getSignature ||
52    [];
53  const HeaderComponent = exposeInSidebar ? H3Code : H4Code;
54  return signatures.map(
55    ({ name, parameters, comment, type }: MethodSignatureData | TypeSignaturesData) => (
56      <div
57        key={`method-signature-${method.name || name}-${parameters?.length || 0}`}
58        css={[STYLES_APIBOX, STYLES_APIBOX_NESTED]}>
59        <APISectionDeprecationNote comment={comment} />
60        <APISectionPlatformTags comment={comment} prefix="Only for:" />
61        <HeaderComponent tags={getTagNamesList(comment)}>
62          <CODE css={!exposeInSidebar ? STYLES_NOT_EXPOSED_HEADER : undefined}>
63            {getMethodName(method as MethodDefinitionData, apiName, name, parameters)}
64          </CODE>
65        </HeaderComponent>
66        {parameters && parameters.length > 0 && (
67          <>
68            {renderParams(parameters)}
69            <br />
70          </>
71        )}
72        <CommentTextBlock comment={comment} includePlatforms={false} />
73        {resolveTypeName(type) !== 'undefined' && (
74          <>
75            <div css={STYLES_NESTED_SECTION_HEADER}>
76              <H4>Returns</H4>
77            </div>
78            <UL css={STYLES_NO_BULLET_LIST}>
79              <LI>
80                <UndoIcon
81                  color={theme.icon.secondary}
82                  size={iconSize.small}
83                  css={returnIconStyles}
84                />
85                <APIDataType typeDefinition={type} />
86              </LI>
87            </UL>
88            <>
89              <br />
90              <ReactMarkdown components={mdComponents}>{comment?.returns ?? ''}</ReactMarkdown>
91            </>
92          </>
93        )}
94      </div>
95    )
96  );
97};
98
99const APISectionMethods = ({
100  data,
101  apiName,
102  header = 'Methods',
103  exposeInSidebar = true,
104}: APISectionMethodsProps) =>
105  data?.length ? (
106    <>
107      <H2 key="methods-header">{header}</H2>
108      {data.map((method: MethodDefinitionData | PropData) =>
109        renderMethod(method, { apiName, header, exposeInSidebar })
110      )}
111    </>
112  ) : null;
113
114const returnIconStyles = css({
115  transform: 'rotate(180deg)',
116  marginRight: spacing[2],
117  verticalAlign: 'middle',
118});
119
120export default APISectionMethods;
121
122export const APIMethod = ({
123  name,
124  comment,
125  returnTypeName,
126  isProperty = false,
127  isReturnTypeReference = false,
128  exposeInSidebar = false,
129  parameters = [],
130  platforms = [],
131}: {
132  exposeInSidebar?: boolean;
133  name: string;
134  comment: string;
135  returnTypeName: string;
136  isProperty: boolean;
137  isReturnTypeReference: boolean;
138  platforms: ('Android' | 'iOS' | 'Web')[];
139  parameters: {
140    name: string;
141    comment?: string;
142    typeName: string;
143    isReference?: boolean;
144  }[];
145}): JSX.Element[] => {
146  const parsedParameters = parameters.map(param => ({
147    name: param.name,
148    type: { name: param.typeName, type: param.isReference ? 'reference' : 'literal' },
149    comment: {
150      text: param.comment,
151    },
152  }));
153  return renderMethod(
154    {
155      name,
156      signatures: [
157        {
158          name,
159          parameters: parsedParameters,
160          comment: {
161            text: comment,
162            tags: platforms.map(text => ({
163              tag: 'platform',
164              text,
165            })),
166          },
167          type: { name: returnTypeName, type: isReturnTypeReference ? 'reference' : 'literal' },
168        },
169      ],
170      kind: isProperty ? TypeDocKind.Property : TypeDocKind.Function,
171    },
172    { exposeInSidebar }
173  );
174};
175
176const STYLES_NO_BULLET_LIST = css({
177  listStyle: 'none',
178  marginLeft: spacing[2],
179});
180