1import { mergeClasses } from '@expo/styleguide'; 2 3import { 4 DefaultPropsDefinitionData, 5 PropData, 6 PropsDefinitionData, 7 TypeDefinitionData, 8} from '~/components/plugins/api/APIDataTypes'; 9import { APISectionDeprecationNote } from '~/components/plugins/api/APISectionDeprecationNote'; 10import { APISectionPlatformTags } from '~/components/plugins/api/APISectionPlatformTags'; 11import { 12 CommentTextBlock, 13 getCommentContent, 14 getCommentOrSignatureComment, 15 getTagData, 16 getTagNamesList, 17 H3Code, 18 H4Code, 19 renderTypeOrSignatureType, 20 resolveTypeName, 21 STYLES_APIBOX, 22 STYLES_APIBOX_NESTED, 23 ELEMENT_SPACING, 24 STYLES_NESTED_SECTION_HEADER, 25 STYLES_NOT_EXPOSED_HEADER, 26 STYLES_SECONDARY, 27 TypeDocKind, 28} from '~/components/plugins/api/APISectionUtils'; 29import { CODE, H2, H3, H4, LI, MONOSPACE, P, UL } from '~/ui/components/Text'; 30 31export type APISectionPropsProps = { 32 data: PropsDefinitionData[]; 33 defaultProps?: DefaultPropsDefinitionData; 34 header?: string; 35}; 36 37const UNKNOWN_VALUE = '...'; 38 39const extractDefaultPropValue = ( 40 { comment, name }: PropData, 41 defaultProps?: DefaultPropsDefinitionData 42): string | undefined => { 43 const annotationDefault = getTagData('default', comment); 44 if (annotationDefault) { 45 return getCommentContent(annotationDefault.content); 46 } 47 return defaultProps?.type?.declaration?.children?.filter( 48 (defaultProp: PropData) => defaultProp.name === name 49 )[0]?.defaultValue; 50}; 51 52const renderInheritedProp = (ip: TypeDefinitionData) => { 53 return ( 54 <LI key={`inherited-prop-${ip.name}-${ip.type}`}> 55 <CODE>{resolveTypeName(ip)}</CODE> 56 </LI> 57 ); 58}; 59 60const renderInheritedProps = ( 61 data: PropsDefinitionData | undefined, 62 exposeInSidebar?: boolean 63): JSX.Element | undefined => { 64 const inheritedData = data?.type?.types ?? data?.extendedTypes ?? []; 65 const inheritedProps = 66 inheritedData.filter((ip: TypeDefinitionData) => ip.type === 'reference') ?? []; 67 if (inheritedProps.length) { 68 return ( 69 <> 70 {exposeInSidebar ? <H3>Inherited Props</H3> : <H4>Inherited Props</H4>} 71 <UL>{inheritedProps.map(renderInheritedProp)}</UL> 72 </> 73 ); 74 } 75 return undefined; 76}; 77 78const getPropsBaseTypes = (def: PropsDefinitionData) => { 79 if (def.kind === TypeDocKind.TypeAlias) { 80 const baseTypes = def?.type?.types 81 ? def.type.types?.filter((t: TypeDefinitionData) => t.declaration) 82 : [def.type]; 83 return baseTypes.map(def => def?.declaration?.children); 84 } else if (def.kind === TypeDocKind.Interface) { 85 return def.children?.filter(child => !child.inheritedFrom) ?? []; 86 } 87 return []; 88}; 89 90const renderProps = ( 91 def: PropsDefinitionData, 92 defaultValues?: DefaultPropsDefinitionData, 93 exposeInSidebar?: boolean 94): JSX.Element => { 95 const propsDeclarations = getPropsBaseTypes(def) 96 .flat() 97 .filter((dec, i, arr) => arr.findIndex(t => t?.name === dec?.name) === i); 98 99 return ( 100 <div key={`props-definition-${def.name}`}> 101 {propsDeclarations?.map(prop => 102 prop 103 ? renderProp(prop, extractDefaultPropValue(prop, defaultValues), exposeInSidebar) 104 : null 105 )} 106 {renderInheritedProps(def, exposeInSidebar)} 107 </div> 108 ); 109}; 110 111export const renderProp = ( 112 { comment, name, type, flags, signatures }: PropData, 113 defaultValue?: string, 114 exposeInSidebar?: boolean 115) => { 116 const HeaderComponent = exposeInSidebar ? H3Code : H4Code; 117 const extractedSignatures = signatures || type?.declaration?.signatures; 118 const extractedComment = getCommentOrSignatureComment(comment, extractedSignatures); 119 120 return ( 121 <div key={`prop-entry-${name}`} css={[STYLES_APIBOX, STYLES_APIBOX_NESTED]}> 122 <APISectionDeprecationNote comment={extractedComment} /> 123 <APISectionPlatformTags comment={comment} prefix="Only for:" /> 124 <HeaderComponent tags={getTagNamesList(comment)}> 125 <MONOSPACE weight="medium" css={!exposeInSidebar && STYLES_NOT_EXPOSED_HEADER}> 126 {name} 127 </MONOSPACE> 128 </HeaderComponent> 129 <P className={mergeClasses(extractedComment && ELEMENT_SPACING)}> 130 {flags?.isOptional && <span css={STYLES_SECONDARY}>Optional • </span>} 131 <span css={STYLES_SECONDARY}>Type:</span>{' '} 132 {renderTypeOrSignatureType(type, extractedSignatures)} 133 {defaultValue && defaultValue !== UNKNOWN_VALUE ? ( 134 <span> 135 <span css={STYLES_SECONDARY}> • Default:</span>{' '} 136 <CODE>{defaultValue}</CODE> 137 </span> 138 ) : null} 139 </P> 140 <CommentTextBlock comment={extractedComment} includePlatforms={false} /> 141 {!extractedComment && <br />} 142 </div> 143 ); 144}; 145 146const APISectionProps = ({ data, defaultProps, header = 'Props' }: APISectionPropsProps) => { 147 const baseProp = data.find(prop => prop.name === header); 148 return data?.length > 0 ? ( 149 <> 150 {data?.length === 1 || header === 'Props' ? ( 151 <H2 key="props-header">{header}</H2> 152 ) : ( 153 <div> 154 {baseProp && <APISectionDeprecationNote comment={baseProp.comment} />} 155 <div css={STYLES_NESTED_SECTION_HEADER}> 156 <H4 key={`${header}-props-header`}>{header}</H4> 157 </div> 158 {baseProp && baseProp.comment ? <CommentTextBlock comment={baseProp.comment} /> : null} 159 </div> 160 )} 161 {data.map((propsDefinition: PropsDefinitionData) => 162 renderProps(propsDefinition, defaultProps, data?.length === 1 || header === 'Props') 163 )} 164 </> 165 ) : null; 166}; 167 168export default APISectionProps; 169