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  getCommentContent,
25} from '~/components/plugins/api/APISectionUtils';
26import { H2, H3, H4, LI, UL, P, CODE } from '~/ui/components/Text';
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 getCommentContent(annotationDefault.content);
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      <CODE>{resolveTypeName(ip)}</CODE>
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 extractedSignatures = signatures || type?.declaration?.signatures;
105  const extractedComment = getCommentOrSignatureComment(comment, extractedSignatures);
106  return (
107    <div key={`prop-entry-${name}`} css={[STYLES_APIBOX, STYLES_APIBOX_NESTED]}>
108      <APISectionDeprecationNote comment={extractedComment} />
109      <APISectionPlatformTags comment={comment} prefix="Only for:" />
110      <HeaderComponent tags={getTagNamesList(comment)}>
111        <CODE css={!exposeInSidebar ? STYLES_NOT_EXPOSED_HEADER : undefined}>{name}</CODE>
112      </HeaderComponent>
113      <P css={extractedComment && STYLES_ELEMENT_SPACING}>
114        {flags?.isOptional && <span css={STYLES_SECONDARY}>Optional&emsp;&bull;&emsp;</span>}
115        <span css={STYLES_SECONDARY}>Type:</span>{' '}
116        {renderTypeOrSignatureType(type, extractedSignatures)}
117        {defaultValue && defaultValue !== UNKNOWN_VALUE ? (
118          <span>
119            <span css={STYLES_SECONDARY}>&emsp;&bull;&emsp;Default:</span>{' '}
120            <CODE>{defaultValue}</CODE>
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      {data?.length === 1 || 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, data?.length === 1 || header === 'Props')
146      )}
147    </>
148  ) : null;
149};
150
151export default APISectionProps;
152