1import { DocsLogo, mergeClasses } from '@expo/styleguide'; 2import { 3 PlanEnterpriseIcon, 4 BookOpen02Icon, 5 GraduationHat02Icon, 6 Home02Icon, 7 Hash02Icon, 8} from '@expo/styleguide-icons'; 9 10import type { AlgoliaItemType } from '../types'; 11import { 12 getContentHighlightHTML, 13 getHighlightHTML, 14 isReferencePath, 15 isEASPath, 16 isHomePath, 17 isLearnPath, 18} from '../utils'; 19import { CommandItemBase } from './CommandItemBase'; 20import { FootnoteSection } from './FootnoteSection'; 21import { FootnoteArrowIcon } from './icons'; 22 23import versions from '~/public/static/constants/versions.json'; 24import { CALLOUT, CAPTION, FOOTNOTE } from '~/ui/components/Text'; 25 26const { LATEST_VERSION } = versions; 27 28type Props = { 29 item: AlgoliaItemType; 30 onSelect?: () => void; 31 isNested?: boolean; 32}; 33 34type ItemIconProps = { 35 url: string; 36 className?: string; 37 isNested?: boolean; 38}; 39 40const isDev = process.env.NODE_ENV === 'development'; 41 42const ItemIcon = ({ url, className, isNested }: ItemIconProps) => { 43 if (isNested) { 44 return <Hash02Icon className={mergeClasses('icon-sm text-icon-quaternary', className)} />; 45 } else if (isReferencePath(url)) { 46 return <DocsLogo className={mergeClasses('text-icon-secondary', className)} />; 47 } else if (isEASPath(url)) { 48 return <PlanEnterpriseIcon className={mergeClasses('text-icon-secondary', className)} />; 49 } else if (isHomePath(url)) { 50 return <Home02Icon className={mergeClasses('text-icon-secondary', className)} />; 51 } else if (isLearnPath(url)) { 52 return <GraduationHat02Icon className={mergeClasses('text-icon-secondary', className)} />; 53 } 54 return <BookOpen02Icon className={mergeClasses('text-icon-secondary', className)} />; 55}; 56 57const getFootnotePrefix = (url: string) => { 58 if (isReferencePath(url)) { 59 return 'Reference'; 60 } else if (isEASPath(url)) { 61 return 'Expo Application Services'; 62 } else if (isHomePath(url)) { 63 return 'Home'; 64 } else if (isLearnPath(url)) { 65 return 'Learn'; 66 } else { 67 return 'Guides'; 68 } 69}; 70 71const ItemFootnotePrefix = ({ url, isNested = false }: { url: string; isNested?: boolean }) => { 72 return isNested ? ( 73 <> 74 <CAPTION theme="quaternary" tag="span"> 75 {getFootnotePrefix(url)} 76 </CAPTION> 77 <FootnoteArrowIcon /> 78 </> 79 ) : ( 80 <CAPTION theme="quaternary">{getFootnotePrefix(url)}</CAPTION> 81 ); 82}; 83 84const transformUrl = (url: string) => { 85 if (url.includes(LATEST_VERSION)) { 86 url = url.replace(LATEST_VERSION, 'latest'); 87 } 88 if (isDev) { 89 url = url.replace('https://docs.expo.dev/', 'http://localhost:3002/'); 90 } 91 92 // If viewing a docs preview hosted on S3, use the current origin instead of production 93 if (window?.location?.origin?.includes('s3-website-us-east-1.amazonaws.com')) { 94 url = url.replace('https://docs.expo.dev/', window.location.origin + '/'); 95 } 96 97 return url; 98}; 99 100export const ExpoDocsItem = ({ item, onSelect, isNested }: Props) => { 101 const { lvl0, lvl2, lvl3, lvl4, lvl6 } = item.hierarchy; 102 const TitleElement = isNested ? FOOTNOTE : CALLOUT; 103 const ContentElement = isNested ? CAPTION : FOOTNOTE; 104 const titleWeight = isNested ? 'regular' : 'medium'; 105 106 return ( 107 <CommandItemBase 108 className={mergeClasses(isNested && 'ml-8 !mt-0.5 !min-h-[32px]')} 109 value={`expodocs-${item.objectID}`} 110 onSelect={onSelect} 111 url={transformUrl(item.url)} 112 isNested={isNested}> 113 <div className={mergeClasses('inline-flex items-center gap-3 break-words')}> 114 <ItemIcon url={item.url} isNested={isNested} className="shrink-0" /> 115 <div> 116 {lvl6 && ( 117 <> 118 <TitleElement weight={titleWeight} {...getHighlightHTML(item, 'lvl6')} /> 119 {!isNested && ( 120 <CAPTION theme="quaternary"> 121 <ItemFootnotePrefix url={item.url} isNested /> 122 <span {...getHighlightHTML(item, 'lvl0')} /> 123 <FootnoteSection item={item} levelKey="lvl2" /> 124 <FootnoteSection item={item} levelKey="lvl3" /> 125 <FootnoteSection item={item} levelKey="lvl4" /> 126 </CAPTION> 127 )} 128 </> 129 )} 130 {!lvl6 && lvl4 && ( 131 <> 132 <TitleElement weight={titleWeight} {...getHighlightHTML(item, 'lvl4')} /> 133 {!isNested && ( 134 <CAPTION theme="quaternary" className={isNested ? '!hidden' : ''}> 135 <ItemFootnotePrefix url={item.url} isNested /> 136 <span {...getHighlightHTML(item, 'lvl0')} /> 137 <FootnoteSection item={item} levelKey="lvl2" /> 138 <FootnoteSection item={item} levelKey="lvl3" /> 139 </CAPTION> 140 )} 141 </> 142 )} 143 {!lvl6 && !lvl4 && lvl3 && ( 144 <> 145 <TitleElement weight={titleWeight} {...getHighlightHTML(item, 'lvl3')} /> 146 {!isNested && ( 147 <CAPTION theme="quaternary" className={isNested ? '!hidden' : ''}> 148 <ItemFootnotePrefix url={item.url} isNested /> 149 <span {...getHighlightHTML(item, 'lvl0')} /> 150 <FootnoteSection item={item} levelKey="lvl2" /> 151 </CAPTION> 152 )} 153 </> 154 )} 155 {!lvl6 && !lvl4 && !lvl3 && lvl2 && ( 156 <> 157 <TitleElement weight={titleWeight} {...getHighlightHTML(item, 'lvl2')} /> 158 {!isNested && ( 159 <CAPTION theme="quaternary" className={isNested ? '!hidden' : ''}> 160 <ItemFootnotePrefix url={item.url} isNested /> 161 <span {...getHighlightHTML(item, 'lvl0')} /> 162 </CAPTION> 163 )} 164 </> 165 )} 166 {!lvl6 && !lvl4 && !lvl3 && !lvl2 && lvl0 && ( 167 <> 168 <TitleElement weight={titleWeight} {...getHighlightHTML(item, 'lvl0')} /> 169 <ItemFootnotePrefix url={item.url} /> 170 </> 171 )} 172 {(!isNested || item.content) && ( 173 <ContentElement theme="secondary" {...getContentHighlightHTML(item)} /> 174 )} 175 </div> 176 </div> 177 </CommandItemBase> 178 ); 179}; 180