1f4b1168bSBartosz Kaszubowskiimport { SearchSmIcon, XIcon } from '@expo/styleguide-icons';
289d2c67fSBartosz Kaszubowskiimport { Command } from 'cmdk';
3*0a4db0c7SBartosz Kaszubowski// eslint-disable-next-line lodash/import-scope
4*0a4db0c7SBartosz Kaszubowskiimport { groupBy } from 'lodash';
589d2c67fSBartosz Kaszubowskiimport { useEffect, useState } from 'react';
689d2c67fSBartosz Kaszubowskiimport type { Dispatch, SetStateAction } from 'react';
789d2c67fSBartosz Kaszubowski
889d2c67fSBartosz Kaszubowskiimport { BarLoader } from './BarLoader';
989d2c67fSBartosz Kaszubowskiimport { CommandFooter } from './CommandFooter';
1089d2c67fSBartosz Kaszubowskiimport { RNDirectoryItem, RNDocsItem, ExpoDocsItem, ExpoItem } from './Items';
1189d2c67fSBartosz Kaszubowskiimport { entries } from './expoEntries';
1289d2c67fSBartosz Kaszubowskiimport { searchIconStyle, closeIconStyle } from './styles';
1389d2c67fSBartosz Kaszubowskiimport type { ExpoItemType, RNDirectoryItemType, AlgoliaItemType } from './types';
1489d2c67fSBartosz Kaszubowskiimport { getExpoDocsResults, getRNDocsResults, getDirectoryResults, getItemsAsync } from './utils';
1589d2c67fSBartosz Kaszubowski
1689d2c67fSBartosz Kaszubowskiimport { CALLOUT } from '~/ui/components/Text';
1789d2c67fSBartosz Kaszubowski
1889d2c67fSBartosz Kaszubowskitype Props = {
1989d2c67fSBartosz Kaszubowski  version: string;
2089d2c67fSBartosz Kaszubowski  open: boolean;
2189d2c67fSBartosz Kaszubowski  setOpen: Dispatch<SetStateAction<boolean>>;
2289d2c67fSBartosz Kaszubowski};
2389d2c67fSBartosz Kaszubowski
2489d2c67fSBartosz Kaszubowskiexport const CommandMenu = ({ version, open, setOpen }: Props) => {
258a411220SBartosz Kaszubowski  const [initialized, setInitialized] = useState(false);
2689d2c67fSBartosz Kaszubowski  const [loading, setLoading] = useState(false);
2789d2c67fSBartosz Kaszubowski  const [query, setQuery] = useState('');
2889d2c67fSBartosz Kaszubowski  const [expoDocsItems, setExpoDocsItems] = useState<AlgoliaItemType[]>([]);
2989d2c67fSBartosz Kaszubowski  const [expoItems, setExpoItems] = useState<ExpoItemType[]>([]);
3089d2c67fSBartosz Kaszubowski  const [rnDocsItems, setRnDocsItems] = useState<AlgoliaItemType[]>([]);
3189d2c67fSBartosz Kaszubowski  const [directoryItems, setDirectoryItems] = useState<RNDirectoryItemType[]>([]);
3289d2c67fSBartosz Kaszubowski
3389d2c67fSBartosz Kaszubowski  const getExpoDocsItems = async () =>
3489d2c67fSBartosz Kaszubowski    getItemsAsync(query, getExpoDocsResults, setExpoDocsItems, version);
3589d2c67fSBartosz Kaszubowski  const getRNDocsItems = async () => getItemsAsync(query, getRNDocsResults, setRnDocsItems);
3689d2c67fSBartosz Kaszubowski  const getDirectoryItems = async () =>
3789d2c67fSBartosz Kaszubowski    getItemsAsync(query, getDirectoryResults, setDirectoryItems);
3889d2c67fSBartosz Kaszubowski
3989d2c67fSBartosz Kaszubowski  const getExpoItems = async () => {
4089d2c67fSBartosz Kaszubowski    setExpoItems(entries.filter(entry => entry.label.toLowerCase().includes(query.toLowerCase())));
4189d2c67fSBartosz Kaszubowski  };
4289d2c67fSBartosz Kaszubowski
4389d2c67fSBartosz Kaszubowski  const dismiss = () => setOpen(false);
4489d2c67fSBartosz Kaszubowski
458a411220SBartosz Kaszubowski  const fetchData = (callback: () => void) => {
4689d2c67fSBartosz Kaszubowski    Promise.all([getExpoDocsItems(), getRNDocsItems(), getDirectoryItems(), getExpoItems()]).then(
478a411220SBartosz Kaszubowski      callback
4889d2c67fSBartosz Kaszubowski    );
4989d2c67fSBartosz Kaszubowski  };
5089d2c67fSBartosz Kaszubowski
5189d2c67fSBartosz Kaszubowski  const onQueryChange = () => {
528a411220SBartosz Kaszubowski    if (open) {
5389d2c67fSBartosz Kaszubowski      setLoading(true);
548a411220SBartosz Kaszubowski      const inputTimeout = setTimeout(() => fetchData(() => setLoading(false)), 150);
5589d2c67fSBartosz Kaszubowski      return () => clearTimeout(inputTimeout);
568a411220SBartosz Kaszubowski    }
5789d2c67fSBartosz Kaszubowski  };
5889d2c67fSBartosz Kaszubowski
598a411220SBartosz Kaszubowski  const onMenuOpen = () => {
608a411220SBartosz Kaszubowski    if (open && !initialized) {
618a411220SBartosz Kaszubowski      fetchData(() => {
628a411220SBartosz Kaszubowski        setInitialized(true);
638a411220SBartosz Kaszubowski        setLoading(false);
648a411220SBartosz Kaszubowski      });
658a411220SBartosz Kaszubowski    }
668a411220SBartosz Kaszubowski  };
678a411220SBartosz Kaszubowski
688a411220SBartosz Kaszubowski  useEffect(onMenuOpen, [open]);
6989d2c67fSBartosz Kaszubowski  useEffect(onQueryChange, [query]);
7089d2c67fSBartosz Kaszubowski
7189d2c67fSBartosz Kaszubowski  const totalCount =
7289d2c67fSBartosz Kaszubowski    expoDocsItems.length + rnDocsItems.length + directoryItems.length + expoItems.length;
7389d2c67fSBartosz Kaszubowski
74*0a4db0c7SBartosz Kaszubowski  const expoDocsGroupedItems = groupBy(
75*0a4db0c7SBartosz Kaszubowski    expoDocsItems.map((expoDocsItem: AlgoliaItemType) => ({
76*0a4db0c7SBartosz Kaszubowski      ...expoDocsItem,
77*0a4db0c7SBartosz Kaszubowski      baseUrl: expoDocsItem.url.replace(/#.+/, ''),
78*0a4db0c7SBartosz Kaszubowski    })),
79*0a4db0c7SBartosz Kaszubowski    'baseUrl'
80*0a4db0c7SBartosz Kaszubowski  );
81*0a4db0c7SBartosz Kaszubowski
8289d2c67fSBartosz Kaszubowski  return (
8389d2c67fSBartosz Kaszubowski    <Command.Dialog open={open} onOpenChange={setOpen} label="Search Menu" shouldFilter={false}>
84f4b1168bSBartosz Kaszubowski      <SearchSmIcon className="text-icon-secondary" css={searchIconStyle} />
85f4b1168bSBartosz Kaszubowski      <div css={closeIconStyle}>
86f4b1168bSBartosz Kaszubowski        <XIcon className="text-icon-secondary" onClick={() => setOpen(false)} />
87f4b1168bSBartosz Kaszubowski      </div>
88e5be0ff4SAman Mittal      <Command.Input value={query} onValueChange={setQuery} placeholder="Search…" />
8989d2c67fSBartosz Kaszubowski      <BarLoader isLoading={loading} />
9089d2c67fSBartosz Kaszubowski      <Command.List>
918a411220SBartosz Kaszubowski        {initialized && (
928a411220SBartosz Kaszubowski          <>
9389d2c67fSBartosz Kaszubowski            {expoDocsItems.length > 0 && (
9489d2c67fSBartosz Kaszubowski              <Command.Group heading="Expo documentation">
95*0a4db0c7SBartosz Kaszubowski                {Object.values(expoDocsGroupedItems).map(values =>
96*0a4db0c7SBartosz Kaszubowski                  values
97*0a4db0c7SBartosz Kaszubowski                    .sort((a, b) => a.url.localeCompare(a.baseUrl) - b.url.localeCompare(b.baseUrl))
98*0a4db0c7SBartosz Kaszubowski                    .slice(0, 6)
99*0a4db0c7SBartosz Kaszubowski                    .map((item, index) => (
1008a411220SBartosz Kaszubowski                      <ExpoDocsItem
101*0a4db0c7SBartosz Kaszubowski                        isNested={index !== 0}
1028a411220SBartosz Kaszubowski                        item={item}
1038a411220SBartosz Kaszubowski                        onSelect={dismiss}
1048a411220SBartosz Kaszubowski                        key={`hit-expo-docs-${item.objectID}`}
1058a411220SBartosz Kaszubowski                      />
106*0a4db0c7SBartosz Kaszubowski                    ))
107*0a4db0c7SBartosz Kaszubowski                )}
10889d2c67fSBartosz Kaszubowski              </Command.Group>
10989d2c67fSBartosz Kaszubowski            )}
11089d2c67fSBartosz Kaszubowski            {expoItems.length > 0 && (
11189d2c67fSBartosz Kaszubowski              <Command.Group heading="Expo dashboard">
11289d2c67fSBartosz Kaszubowski                {expoItems.map((item: ExpoItemType) => (
1138a411220SBartosz Kaszubowski                  <ExpoItem
1148a411220SBartosz Kaszubowski                    item={item}
1158a411220SBartosz Kaszubowski                    onSelect={dismiss}
1168a411220SBartosz Kaszubowski                    key={`hit-expo-${item.url}`}
1178a411220SBartosz Kaszubowski                    query={query}
1188a411220SBartosz Kaszubowski                  />
11989d2c67fSBartosz Kaszubowski                ))}
12089d2c67fSBartosz Kaszubowski              </Command.Group>
12189d2c67fSBartosz Kaszubowski            )}
12289d2c67fSBartosz Kaszubowski            {rnDocsItems.length > 0 && (
12389d2c67fSBartosz Kaszubowski              <Command.Group heading="React Native documentation">
12489d2c67fSBartosz Kaszubowski                {rnDocsItems.map(item => (
12589d2c67fSBartosz Kaszubowski                  <RNDocsItem item={item} onSelect={dismiss} key={`hit-rn-docs-${item.objectID}`} />
12689d2c67fSBartosz Kaszubowski                ))}
12789d2c67fSBartosz Kaszubowski              </Command.Group>
12889d2c67fSBartosz Kaszubowski            )}
12989d2c67fSBartosz Kaszubowski            {directoryItems.length > 0 && (
13089d2c67fSBartosz Kaszubowski              <Command.Group heading="React Native directory">
13189d2c67fSBartosz Kaszubowski                {directoryItems.map(item => (
13289d2c67fSBartosz Kaszubowski                  <RNDirectoryItem
13389d2c67fSBartosz Kaszubowski                    item={item}
13489d2c67fSBartosz Kaszubowski                    onSelect={dismiss}
13589d2c67fSBartosz Kaszubowski                    key={`hit-rn-dir-${item.npmPkg}`}
13689d2c67fSBartosz Kaszubowski                    query={query}
13789d2c67fSBartosz Kaszubowski                  />
13889d2c67fSBartosz Kaszubowski                ))}
13989d2c67fSBartosz Kaszubowski              </Command.Group>
14089d2c67fSBartosz Kaszubowski            )}
1418a411220SBartosz Kaszubowski            {!loading && totalCount === 0 && (
14289d2c67fSBartosz Kaszubowski              <Command.Empty>
14389d2c67fSBartosz Kaszubowski                <CALLOUT theme="secondary">No results found.</CALLOUT>
14489d2c67fSBartosz Kaszubowski              </Command.Empty>
14589d2c67fSBartosz Kaszubowski            )}
1468a411220SBartosz Kaszubowski          </>
1478a411220SBartosz Kaszubowski        )}
14889d2c67fSBartosz Kaszubowski      </Command.List>
14989d2c67fSBartosz Kaszubowski      <CommandFooter />
15089d2c67fSBartosz Kaszubowski    </Command.Dialog>
15189d2c67fSBartosz Kaszubowski  );
15289d2c67fSBartosz Kaszubowski};
153