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 = ['Date', 'Error', 'Promise', 'T', 'TaskOptions', 'Uint8Array']; 54 55export const resolveTypeName = ({ 56 elements, 57 elementType, 58 name, 59 type, 60 types, 61 typeArguments, 62 declaration, 63 value, 64 queryType, 65}: TypeDefinitionData): string | JSX.Element | (string | JSX.Element)[] => { 66 if (name) { 67 if (type === 'reference') { 68 if (typeArguments) { 69 if (name === 'Record' || name === 'React.ComponentProps') { 70 return ( 71 <> 72 {name}< 73 {typeArguments.map((type, index) => ( 74 <span key={`record-type-${index}`}> 75 {resolveTypeName(type)} 76 {index !== typeArguments.length - 1 ? ', ' : ''} 77 </span> 78 ))} 79 > 80 </> 81 ); 82 } else { 83 return ( 84 <> 85 {nonLinkableTypes.includes(name) ? ( 86 name 87 ) : ( 88 <Link href={`#${name.toLowerCase()}`} key={`type-link-${name}`}> 89 {name} 90 </Link> 91 )} 92 < 93 {typeArguments.map((type, index) => ( 94 <span key={`${name}-nested-type-${index}`}>{resolveTypeName(type)}</span> 95 ))} 96 > 97 </> 98 ); 99 } 100 } else { 101 if (nonLinkableTypes.includes(name)) { 102 return name; 103 } else { 104 return ( 105 <Link href={`#${name.toLowerCase()}`} key={`type-link-${name}`}> 106 {name} 107 </Link> 108 ); 109 } 110 } 111 } else { 112 return name; 113 } 114 } else if (elementType?.name) { 115 if (elementType.type === 'reference') { 116 if (nonLinkableTypes.includes(elementType.name)) { 117 return elementType.name + (type === 'array' && '[]'); 118 } 119 return ( 120 <Link href={`#${elementType.name?.toLowerCase()}`} key={`type-link-${elementType.name}`}> 121 {elementType.name} 122 {type === 'array' && '[]'} 123 </Link> 124 ); 125 } 126 if (type === 'array') { 127 return elementType.name + '[]'; 128 } 129 return elementType.name + type; 130 } else if (type === 'union' && types?.length) { 131 return types.map(resolveTypeName).map((valueToRender, index) => ( 132 <span key={`union-type-${index}`}> 133 {valueToRender} 134 {index + 1 !== types.length && ' | '} 135 </span> 136 )); 137 } else if (declaration?.signatures) { 138 const baseSignature = declaration.signatures[0]; 139 if (baseSignature?.parameters?.length) { 140 return ( 141 <> 142 ( 143 {baseSignature.parameters?.map((param, index) => ( 144 <span key={`param-${index}-${param.name}`}> 145 {param.name}: {resolveTypeName(param.type)} 146 {index + 1 !== baseSignature.parameters?.length && ', '} 147 </span> 148 ))} 149 ) {'=>'} {resolveTypeName(baseSignature.type)} 150 </> 151 ); 152 } else { 153 return ( 154 <> 155 {'() =>'} {resolveTypeName(baseSignature.type)} 156 </> 157 ); 158 } 159 } else if (type === 'tuple' && elements) { 160 return ( 161 <> 162 [ 163 {elements.map((elem, i) => ( 164 <span key={`tuple-${name}-${i}`}> 165 {resolveTypeName(elem)} 166 {i + 1 !== elements.length ? ', ' : ''} 167 </span> 168 ))} 169 ] 170 </> 171 ); 172 } else if (type === 'query' && queryType) { 173 return queryType.name; 174 } else if (type === 'literal' && value) { 175 return `'${value}'`; 176 } else if (value === null) { 177 return 'null'; 178 } 179 return 'undefined'; 180}; 181 182export const renderParam = ({ comment, name, type }: MethodParamData): JSX.Element => ( 183 <LI key={`param-${name}`}> 184 <B> 185 {name} (<InlineCode>{resolveTypeName(type)}</InlineCode>) 186 </B> 187 <CommentTextBlock comment={comment} renderers={mdInlineRenderers} withDash /> 188 </LI> 189); 190 191export type CommentTextBlockProps = { 192 comment?: CommentData; 193 renderers?: MDRenderers; 194 withDash?: boolean; 195 beforeContent?: JSX.Element; 196}; 197 198export const CommentTextBlock: React.FC<CommentTextBlockProps> = ({ 199 comment, 200 renderers = mdRenderers, 201 withDash, 202 beforeContent, 203}) => { 204 const shortText = comment?.shortText?.trim().length ? ( 205 <ReactMarkdown renderers={renderers}>{comment.shortText}</ReactMarkdown> 206 ) : null; 207 const text = comment?.text?.trim().length ? ( 208 <ReactMarkdown renderers={renderers}>{comment.text}</ReactMarkdown> 209 ) : null; 210 211 const example = comment?.tags?.filter(tag => tag.tag === 'example')[0]; 212 const exampleText = example ? ( 213 <ReactMarkdown renderers={renderers}>{`__Example:__ ${example.text}`}</ReactMarkdown> 214 ) : null; 215 216 const deprecation = comment?.tags?.filter(tag => tag.tag === 'deprecated')[0]; 217 const deprecationNote = deprecation ? ( 218 <Quote key="deprecation-note"> 219 <B>{deprecation.text.trim().length ? deprecation.text : 'Deprecated'}</B> 220 </Quote> 221 ) : null; 222 223 return ( 224 <> 225 {deprecationNote} 226 {beforeContent} 227 {withDash && (shortText || text) ? ' - ' : null} 228 {shortText} 229 {text} 230 {exampleText} 231 </> 232 ); 233}; 234 235export const STYLES_OPTIONAL = css` 236 color: ${theme.text.secondary}; 237 font-size: 90%; 238 padding-top: 22px; 239`; 240 241export const STYLES_SECONDARY = css` 242 color: ${theme.text.secondary}; 243 font-size: 90%; 244 font-weight: 600; 245`; 246