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