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