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