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