1import React from 'react';
2
3import { InlineCode } from '~/components/base/code';
4import { LI, UL } from '~/components/base/list';
5import { P } from '~/components/base/paragraph';
6import { H2, H3, H3Code, H4, H4Code } from '~/components/plugins/Headings';
7import {
8  DefaultPropsDefinitionData,
9  PropData,
10  PropsDefinitionData,
11  TypeDefinitionData,
12} from '~/components/plugins/api/APIDataTypes';
13import { APISectionDeprecationNote } from '~/components/plugins/api/APISectionDeprecationNote';
14import { PlatformTags } from '~/components/plugins/api/APISectionPlatformTags';
15import {
16  CommentTextBlock,
17  getCommentOrSignatureComment,
18  getTagData,
19  renderTypeOrSignatureType,
20  resolveTypeName,
21  STYLES_APIBOX,
22  STYLES_APIBOX_NESTED,
23  STYLES_NESTED_SECTION_HEADER,
24  STYLES_NOT_EXPOSED_HEADER,
25  STYLES_SECONDARY,
26} from '~/components/plugins/api/APISectionUtils';
27
28export type APISectionPropsProps = {
29  data: PropsDefinitionData[];
30  defaultProps?: DefaultPropsDefinitionData;
31  header?: string;
32};
33
34const UNKNOWN_VALUE = '...';
35
36const extractDefaultPropValue = (
37  { comment, name }: PropData,
38  defaultProps?: DefaultPropsDefinitionData
39): string | undefined => {
40  const annotationDefault = getTagData('default', comment);
41  if (annotationDefault) {
42    return annotationDefault.text;
43  }
44  return defaultProps?.type?.declaration?.children?.filter(
45    (defaultProp: PropData) => defaultProp.name === name
46  )[0]?.defaultValue;
47};
48
49const renderInheritedProp = (ip: TypeDefinitionData) => {
50  return (
51    <LI key={`inherited-prop-${ip.name}-${ip.type}`}>
52      <InlineCode>{resolveTypeName(ip)}</InlineCode>
53    </LI>
54  );
55};
56
57const renderInheritedProps = (
58  data: TypeDefinitionData[] | undefined,
59  exposeInSidebar?: boolean
60): JSX.Element | undefined => {
61  const inheritedProps = data?.filter((ip: TypeDefinitionData) => ip.type === 'reference') ?? [];
62  if (inheritedProps.length) {
63    return (
64      <>
65        {exposeInSidebar ? <H3>Inherited Props</H3> : <H4>Inherited Props</H4>}
66        <UL>{inheritedProps.map(renderInheritedProp)}</UL>
67      </>
68    );
69  }
70  return undefined;
71};
72
73const renderProps = (
74  { name, type }: PropsDefinitionData,
75  defaultValues?: DefaultPropsDefinitionData,
76  exposeInSidebar?: boolean
77): JSX.Element => {
78  const baseTypes = type.types
79    ? type.types?.filter((t: TypeDefinitionData) => t.declaration)
80    : [type];
81  const propsDeclarations = baseTypes
82    .map(def => def?.declaration?.children)
83    .flat()
84    .filter((dec, i, arr) => arr.findIndex(t => t?.name === dec?.name) === i);
85
86  return (
87    <div key={`props-definition-${name}`}>
88      {propsDeclarations?.map(prop =>
89        prop
90          ? renderProp(prop, extractDefaultPropValue(prop, defaultValues), exposeInSidebar)
91          : null
92      )}
93      {renderInheritedProps(type.types, exposeInSidebar)}
94    </div>
95  );
96};
97
98export const renderProp = (
99  { comment, name, type, flags, signatures }: PropData,
100  defaultValue?: string,
101  exposeInSidebar?: boolean
102) => {
103  const HeaderComponent = exposeInSidebar ? H3Code : H4Code;
104  const extractedComment = getCommentOrSignatureComment(comment, signatures);
105  return (
106    <div key={`prop-entry-${name}`} css={[STYLES_APIBOX, !exposeInSidebar && STYLES_APIBOX_NESTED]}>
107      <APISectionDeprecationNote comment={extractedComment} />
108      <PlatformTags comment={comment} prefix="Only for:" firstElement />
109      <HeaderComponent>
110        <InlineCode customCss={!exposeInSidebar ? STYLES_NOT_EXPOSED_HEADER : undefined}>
111          {name}
112        </InlineCode>
113      </HeaderComponent>
114      <P>
115        {flags?.isOptional && <span css={STYLES_SECONDARY}>Optional&emsp;&bull;&emsp;</span>}
116        <span css={STYLES_SECONDARY}>Type:</span> {renderTypeOrSignatureType(type, signatures)}
117        {defaultValue && defaultValue !== UNKNOWN_VALUE ? (
118          <span>
119            <span css={STYLES_SECONDARY}>&emsp;&bull;&emsp;Default:</span>{' '}
120            <InlineCode>{defaultValue}</InlineCode>
121          </span>
122        ) : null}
123      </P>
124      <CommentTextBlock comment={extractedComment} includePlatforms={false} />
125    </div>
126  );
127};
128
129const APISectionProps = ({ data, defaultProps, header = 'Props' }: APISectionPropsProps) => {
130  const baseProp = data.find(prop => prop.name === header);
131  return data?.length ? (
132    <>
133      {header === 'Props' ? (
134        <H2 key="props-header">{header}</H2>
135      ) : (
136        <div>
137          {baseProp && <APISectionDeprecationNote comment={baseProp.comment} />}
138          <div css={STYLES_NESTED_SECTION_HEADER}>
139            <H4 key={`${header}-props-header`}>{header}</H4>
140          </div>
141          {baseProp && baseProp.comment ? <CommentTextBlock comment={baseProp.comment} /> : null}
142        </div>
143      )}
144      {data.map((propsDefinition: PropsDefinitionData) =>
145        renderProps(propsDefinition, defaultProps, header === 'Props')
146      )}
147    </>
148  ) : null;
149};
150
151export default APISectionProps;
152