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