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