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