1import { css } from '@emotion/react'; 2import { theme } from '@expo/styleguide'; 3import React from 'react'; 4import ReactMarkdown from 'react-markdown'; 5 6import { Code, InlineCode } from '~/components/base/code'; 7import { H4 } from '~/components/base/headings'; 8import Link from '~/components/base/link'; 9import { LI, UL } from '~/components/base/list'; 10import { B, P, Quote } from '~/components/base/paragraph'; 11import { 12 CommentData, 13 MethodParamData, 14 TypeDefinitionData, 15} from '~/components/plugins/api/APIDataTypes'; 16 17export enum TypeDocKind { 18 Enum = 4, 19 Variable = 32, 20 Function = 64, 21 Class = 128, 22 Interface = 256, 23 Property = 1024, 24 TypeAlias = 4194304, 25} 26 27export type MDRenderers = React.ComponentProps<typeof ReactMarkdown>['renderers']; 28 29export const mdRenderers: MDRenderers = { 30 blockquote: ({ children }) => ( 31 <Quote> 32 {React.Children.map(children, child => 33 child.type.name === 'paragraph' ? child.props.children : child 34 )} 35 </Quote> 36 ), 37 code: ({ value, language }) => <Code className={`language-${language}`}>{value}</Code>, 38 heading: ({ children }) => <H4>{children}</H4>, 39 inlineCode: ({ value }) => <InlineCode>{value}</InlineCode>, 40 list: ({ children }) => <UL>{children}</UL>, 41 listItem: ({ children }) => <LI>{children}</LI>, 42 link: ({ href, children }) => <Link href={href}>{children}</Link>, 43 paragraph: ({ children }) => (children ? <P>{children}</P> : null), 44 strong: ({ children }) => <B>{children}</B>, 45 text: ({ value }) => (value ? <span>{value}</span> : null), 46}; 47 48export const mdInlineRenderers: MDRenderers = { 49 ...mdRenderers, 50 paragraph: ({ children }) => (children ? <span>{children}</span> : null), 51}; 52 53const nonLinkableTypes = [ 54 'Date', 55 'Error', 56 'Omit', 57 'Promise', 58 'React.FC', 59 'StyleProp', 60 'T', 61 'TaskOptions', 62 'Uint8Array', 63 'ViewStyle', 64]; 65 66export const resolveTypeName = ({ 67 elements, 68 elementType, 69 name, 70 type, 71 types, 72 typeArguments, 73 declaration, 74 value, 75 queryType, 76}: TypeDefinitionData): string | JSX.Element | (string | JSX.Element)[] => { 77 if (name) { 78 if (type === 'reference') { 79 if (typeArguments) { 80 if (name === 'Record' || name === 'React.ComponentProps') { 81 return ( 82 <> 83 {name}< 84 {typeArguments.map((type, index) => ( 85 <span key={`record-type-${index}`}> 86 {resolveTypeName(type)} 87 {index !== typeArguments.length - 1 ? ', ' : null} 88 </span> 89 ))} 90 > 91 </> 92 ); 93 } else { 94 return ( 95 <> 96 {nonLinkableTypes.includes(name) ? ( 97 name 98 ) : ( 99 <Link href={`#${name.toLowerCase()}`} key={`type-link-${name}`}> 100 {name} 101 </Link> 102 )} 103 < 104 {typeArguments.map((type, index) => ( 105 <span key={`${name}-nested-type-${index}`}> 106 {resolveTypeName(type)} 107 {index !== typeArguments.length - 1 ? ', ' : null} 108 </span> 109 ))} 110 > 111 </> 112 ); 113 } 114 } else { 115 if (nonLinkableTypes.includes(name)) { 116 return name; 117 } else { 118 return ( 119 <Link href={`#${name.toLowerCase()}`} key={`type-link-${name}`}> 120 {name} 121 </Link> 122 ); 123 } 124 } 125 } else { 126 return name; 127 } 128 } else if (elementType?.name) { 129 if (elementType.type === 'reference') { 130 if (nonLinkableTypes.includes(elementType.name)) { 131 return elementType.name + (type === 'array' && '[]'); 132 } 133 return ( 134 <Link href={`#${elementType.name?.toLowerCase()}`} key={`type-link-${elementType.name}`}> 135 {elementType.name} 136 {type === 'array' && '[]'} 137 </Link> 138 ); 139 } 140 if (type === 'array') { 141 return elementType.name + '[]'; 142 } 143 return elementType.name + type; 144 } else if (type === 'union' && types?.length) { 145 return types.map(resolveTypeName).map((valueToRender, index) => ( 146 <span key={`union-type-${index}`}> 147 {valueToRender} 148 {index + 1 !== types.length && ' | '} 149 </span> 150 )); 151 } else if (declaration?.signatures) { 152 const baseSignature = declaration.signatures[0]; 153 if (baseSignature?.parameters?.length) { 154 return ( 155 <> 156 ( 157 {baseSignature.parameters?.map((param, index) => ( 158 <span key={`param-${index}-${param.name}`}> 159 {param.name}: {resolveTypeName(param.type)} 160 {index + 1 !== baseSignature.parameters?.length && ', '} 161 </span> 162 ))} 163 ) {'=>'} {resolveTypeName(baseSignature.type)} 164 </> 165 ); 166 } else { 167 return ( 168 <> 169 {'() =>'} {resolveTypeName(baseSignature.type)} 170 </> 171 ); 172 } 173 } else if (type === 'tuple' && elements) { 174 return ( 175 <> 176 [ 177 {elements.map((elem, i) => ( 178 <span key={`tuple-${name}-${i}`}> 179 {resolveTypeName(elem)} 180 {i + 1 !== elements.length ? ', ' : null} 181 </span> 182 ))} 183 ] 184 </> 185 ); 186 } else if (type === 'query' && queryType) { 187 return queryType.name; 188 } else if (type === 'literal' && value) { 189 return `'${value}'`; 190 } else if (value === null) { 191 return 'null'; 192 } 193 return 'undefined'; 194}; 195 196export const renderParam = ({ comment, name, type }: MethodParamData): JSX.Element => ( 197 <LI key={`param-${name}`}> 198 <B> 199 {name} (<InlineCode>{resolveTypeName(type)}</InlineCode>) 200 </B> 201 <CommentTextBlock comment={comment} renderers={mdInlineRenderers} withDash /> 202 </LI> 203); 204 205export type CommentTextBlockProps = { 206 comment?: CommentData; 207 renderers?: MDRenderers; 208 withDash?: boolean; 209 beforeContent?: JSX.Element; 210}; 211 212export const CommentTextBlock: React.FC<CommentTextBlockProps> = ({ 213 comment, 214 renderers = mdRenderers, 215 withDash, 216 beforeContent, 217}) => { 218 const shortText = comment?.shortText?.trim().length ? ( 219 <ReactMarkdown renderers={renderers}>{comment.shortText}</ReactMarkdown> 220 ) : null; 221 const text = comment?.text?.trim().length ? ( 222 <ReactMarkdown renderers={renderers}>{comment.text}</ReactMarkdown> 223 ) : null; 224 225 const example = comment?.tags?.filter(tag => tag.tag === 'example')[0]; 226 const exampleText = example ? ( 227 <ReactMarkdown renderers={renderers}>{`__Example:__ ${example.text}`}</ReactMarkdown> 228 ) : null; 229 230 const deprecation = comment?.tags?.filter(tag => tag.tag === 'deprecated')[0]; 231 const deprecationNote = deprecation ? ( 232 <Quote key="deprecation-note"> 233 <B>{deprecation.text.trim().length ? deprecation.text : 'Deprecated'}</B> 234 </Quote> 235 ) : null; 236 237 return ( 238 <> 239 {deprecationNote} 240 {beforeContent} 241 {withDash && (shortText || text) ? ' - ' : null} 242 {shortText} 243 {text} 244 {exampleText} 245 </> 246 ); 247}; 248 249export const STYLES_OPTIONAL = css` 250 color: ${theme.text.secondary}; 251 font-size: 90%; 252 padding-top: 22px; 253`; 254 255export const STYLES_SECONDARY = css` 256 color: ${theme.text.secondary}; 257 font-size: 90%; 258 font-weight: 600; 259`; 260