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