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 TypeDefinitionTypesData, 16} from '~/components/plugins/api/APIDataTypes'; 17 18export enum TypeDocKind { 19 Enum = 4, 20 Variable = 32, 21 Function = 64, 22 Class = 128, 23 Interface = 256, 24 Property = 1024, 25 TypeAlias = 4194304, 26} 27 28export type MDRenderers = React.ComponentProps<typeof ReactMarkdown>['renderers']; 29 30export const mdRenderers: MDRenderers = { 31 blockquote: ({ children }) => ( 32 <Quote> 33 {React.Children.map(children, child => 34 child.type.name === 'paragraph' ? child.props.children : child 35 )} 36 </Quote> 37 ), 38 code: ({ value, language }) => <Code className={`language-${language}`}>{value}</Code>, 39 heading: ({ children }) => <H4>{children}</H4>, 40 inlineCode: ({ value }) => <InlineCode>{value}</InlineCode>, 41 list: ({ children }) => <UL>{children}</UL>, 42 listItem: ({ children }) => <LI>{children}</LI>, 43 link: ({ href, children }) => <Link href={href}>{children}</Link>, 44 paragraph: ({ children }) => (children ? <P>{children}</P> : null), 45 strong: ({ children }) => <B>{children}</B>, 46 text: ({ value }) => (value ? <span>{value}</span> : null), 47}; 48 49export const mdInlineRenderers: MDRenderers = { 50 ...mdRenderers, 51 paragraph: ({ children }) => (children ? <span>{children}</span> : null), 52}; 53 54const nonLinkableTypes = ['Date', 'T', 'TaskOptions', 'Uint8Array']; 55 56export const resolveTypeName = ({ 57 elementType, 58 name, 59 type, 60 types, 61 typeArguments, 62 declaration, 63}: TypeDefinitionData): string | JSX.Element | (string | JSX.Element)[] => { 64 if (name) { 65 if (type === 'reference') { 66 if (typeArguments) { 67 if (name === 'Promise') { 68 return ( 69 <span> 70 {name}<{typeArguments.map(resolveTypeName)}> 71 </span> 72 ); 73 } else if (name === 'Record') { 74 return ( 75 <span> 76 {name}<{typeArguments.map(resolveTypeName).join(',')}> 77 </span> 78 ); 79 } else { 80 return `${typeArguments.map(resolveTypeName)}`; 81 } 82 } else { 83 if (nonLinkableTypes.includes(name)) { 84 return name; 85 } else { 86 return ( 87 <Link href={`#${name.toLowerCase()}`} key={`type-link-${name}`}> 88 {name} 89 </Link> 90 ); 91 } 92 } 93 } else { 94 return name; 95 } 96 } else if (elementType?.name) { 97 if (elementType.type === 'reference') { 98 return ( 99 <Link href={`#${elementType.name?.toLowerCase()}`} key={`type-link-${elementType.name}`}> 100 {elementType.name} 101 {type === 'array' && '[]'} 102 </Link> 103 ); 104 } 105 if (type === 'array') { 106 return elementType.name + '[]'; 107 } 108 return elementType.name + type; 109 } else if (type === 'union' && types?.length) { 110 return types 111 .map((t: TypeDefinitionTypesData) => 112 t.type === 'reference' ? ( 113 <Link href={`#${t.name?.toLowerCase()}`} key={`type-link-${t.name}`}> 114 {t.name} 115 </Link> 116 ) : t.type === 'array' ? ( 117 `${t.elementType?.name}[]` 118 ) : t.type === 'literal' && typeof t.value === 'string' ? ( 119 `'${t.name || t.value}'` 120 ) : ( 121 `${t.name || t.value}` 122 ) 123 ) 124 .map((valueToRender, index) => ( 125 <span key={`union-type-${index}`}> 126 {valueToRender} 127 {index + 1 !== types.length && ' | '} 128 </span> 129 )); 130 } else if (declaration?.signatures) { 131 const baseSignature = declaration.signatures[0]; 132 if (baseSignature?.parameters?.length) { 133 return ( 134 <> 135 ( 136 {baseSignature.parameters?.map((param, index) => ( 137 <span key={`param-${index}-${param.name}`}> 138 {param.name}: {resolveTypeName(param.type)} 139 {index + 1 !== baseSignature.parameters.length && ', '} 140 </span> 141 ))} 142 ) {'=>'} {resolveTypeName(baseSignature.type)} 143 </> 144 ); 145 } else { 146 return `() => ${resolveTypeName(baseSignature.type)}`; 147 } 148 } 149 return 'undefined'; 150}; 151 152export const renderParam = ({ comment, name, type }: MethodParamData): JSX.Element => ( 153 <LI key={`param-${name}`}> 154 <B> 155 {name} (<InlineCode>{resolveTypeName(type)}</InlineCode>) 156 </B> 157 <CommentTextBlock comment={comment} renderers={mdInlineRenderers} withDash /> 158 </LI> 159); 160 161export type CommentTextBlockProps = { 162 comment?: CommentData; 163 renderers?: MDRenderers; 164 withDash?: boolean; 165}; 166 167export const CommentTextBlock: React.FC<CommentTextBlockProps> = ({ 168 comment, 169 renderers = mdRenderers, 170 withDash, 171}) => { 172 const shortText = comment?.shortText?.trim().length ? ( 173 <ReactMarkdown renderers={renderers}>{comment.shortText}</ReactMarkdown> 174 ) : null; 175 const text = comment?.text?.trim().length ? ( 176 <ReactMarkdown renderers={renderers}>{comment.text}</ReactMarkdown> 177 ) : null; 178 179 const example = comment?.tags?.filter(tag => tag.tag === 'example')[0]; 180 const exampleText = example ? ( 181 <ReactMarkdown renderers={renderers}>{`__Example:__ ${example.text}`}</ReactMarkdown> 182 ) : null; 183 184 return ( 185 <> 186 {withDash && (shortText || text) ? ' - ' : null} 187 {shortText} 188 {text} 189 {exampleText} 190 </> 191 ); 192}; 193 194export const STYLES_OPTIONAL = css` 195 color: ${theme.text.secondary}; 196 font-size: 90%; 197 padding-top: 22px; 198`; 199 200export const STYLES_SECONDARY = css` 201 color: ${theme.text.secondary}; 202 font-size: 90%; 203 font-weight: 600; 204`; 205