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