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