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