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