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