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