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