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