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 {
14  CommentTextBlock,
15  getCommentOrSignatureComment,
16  getTagData,
17  renderTypeOrSignatureType,
18  resolveTypeName,
19  STYLES_APIBOX,
20  STYLES_APIBOX_NESTED,
21  STYLES_NESTED_SECTION_HEADER,
22  STYLES_NOT_EXPOSED_HEADER,
23  STYLES_SECONDARY,
24} from '~/components/plugins/api/APISectionUtils';
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      <InlineCode>{resolveTypeName(ip)}</InlineCode>
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  return (
103    <div key={`prop-entry-${name}`} css={[STYLES_APIBOX, !exposeInSidebar && STYLES_APIBOX_NESTED]}>
104      <HeaderComponent>
105        <InlineCode customCss={!exposeInSidebar ? STYLES_NOT_EXPOSED_HEADER : undefined}>
106          {name}
107        </InlineCode>
108      </HeaderComponent>
109      <P>
110        {flags?.isOptional && <span css={STYLES_SECONDARY}>Optional&emsp;&bull;&emsp;</span>}
111        <span css={STYLES_SECONDARY}>Type:</span> {renderTypeOrSignatureType(type, signatures)}
112        {defaultValue && defaultValue !== UNKNOWN_VALUE ? (
113          <span>
114            <span css={STYLES_SECONDARY}>&emsp;&bull;&emsp;Default:</span>{' '}
115            <InlineCode>{defaultValue}</InlineCode>
116          </span>
117        ) : null}
118      </P>
119      <CommentTextBlock comment={getCommentOrSignatureComment(comment, signatures)} />
120    </div>
121  );
122};
123
124const APISectionProps = ({ data, defaultProps, header = 'Props' }: APISectionPropsProps) => {
125  const baseProp = data.find(prop => prop.name === header);
126  return data?.length ? (
127    <>
128      {header === 'Props' ? (
129        <H2 key="props-header">{header}</H2>
130      ) : (
131        <div>
132          <div css={STYLES_NESTED_SECTION_HEADER}>
133            <H4 key={`${header}-props-header`}>{header}</H4>
134          </div>
135          {baseProp && baseProp.comment ? <CommentTextBlock comment={baseProp.comment} /> : null}
136        </div>
137      )}
138      {data.map((propsDefinition: PropsDefinitionData) =>
139        renderProps(propsDefinition, defaultProps, header === 'Props')
140      )}
141    </>
142  ) : null;
143};
144
145export default APISectionProps;
146