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