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