1import { css } from '@emotion/react';
2import React from 'react';
3
4import { InlineCode } from '~/components/base/code';
5import { H4 } from '~/components/base/headings';
6import { LI, UL } from '~/components/base/list';
7import { P } from '~/components/base/paragraph';
8import { H2, H3, H3Code } from '~/components/plugins/Headings';
9import {
10  CommentTagData,
11  DefaultPropsDefinitionData,
12  PropData,
13  PropsDefinitionData,
14  TypeDefinitionData,
15} from '~/components/plugins/api/APIDataTypes';
16import {
17  CommentTextBlock,
18  getCommentOrSignatureComment,
19  renderTypeOrSignatureType,
20  resolveTypeName,
21  STYLES_SECONDARY,
22} from '~/components/plugins/api/APISectionUtils';
23
24export type APISectionPropsProps = {
25  data: PropsDefinitionData[];
26  defaultProps?: DefaultPropsDefinitionData;
27  header?: string;
28};
29
30const UNKNOWN_VALUE = '...';
31
32const PROP_LIST_ELEMENT_STYLE = css`
33  padding: 0;
34`;
35
36const extractDefaultPropValue = (
37  { comment, name }: PropData,
38  defaultProps?: DefaultPropsDefinitionData
39): string | undefined => {
40  const annotationDefault = comment?.tags?.filter((tag: CommentTagData) => tag.tag === 'default');
41  if (annotationDefault?.length) {
42    return annotationDefault[0].text;
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      <InlineCode>{resolveTypeName(ip)}</InlineCode>
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      <UL>
89        {propsDeclarations?.map(prop =>
90          prop
91            ? renderProp(prop, extractDefaultPropValue(prop, defaultValues), exposeInSidebar)
92            : null
93        )}
94      </UL>
95      {renderInheritedProps(type.types, exposeInSidebar)}
96    </div>
97  );
98};
99
100const renderProp = (
101  { comment, name, type, flags, signatures }: PropData,
102  defaultValue?: string,
103  exposeInSidebar?: boolean
104) => (
105  <LI key={`prop-entry-${name}`} customCss={exposeInSidebar ? PROP_LIST_ELEMENT_STYLE : undefined}>
106    {exposeInSidebar ? <H3>{name}</H3> : <H4>{name}</H4>}
107    <P>
108      {flags?.isOptional && <span css={STYLES_SECONDARY}>Optional&emsp;&bull;&emsp;</span>}
109      <span css={STYLES_SECONDARY}>Type:</span> {renderTypeOrSignatureType(type, signatures, true)}
110      {defaultValue && defaultValue !== UNKNOWN_VALUE ? (
111        <span>
112          <span css={STYLES_SECONDARY}>&emsp;&bull;&emsp;Default:</span>{' '}
113          <InlineCode>{defaultValue}</InlineCode>
114        </span>
115      ) : null}
116    </P>
117    <CommentTextBlock comment={getCommentOrSignatureComment(comment, signatures)} />
118  </LI>
119);
120
121const APISectionProps: React.FC<APISectionPropsProps> = ({
122  data,
123  defaultProps,
124  header = 'Props',
125}) =>
126  data?.length ? (
127    <>
128      {header === 'Props' ? (
129        <H2 key="props-header">{header}</H2>
130      ) : (
131        <>
132          <H3Code key={`${header}-props-header`}>
133            <InlineCode>{header}</InlineCode>
134          </H3Code>
135          <br />
136        </>
137      )}
138      {data.map((propsDefinition: PropsDefinitionData) =>
139        renderProps(propsDefinition, defaultProps, header === 'Props')
140      )}
141    </>
142  ) : null;
143
144export default APISectionProps;
145