1import { SearchIcon, theme, XIcon } from '@expo/styleguide';
2import { Command } from 'cmdk';
3import { useEffect, useState } from 'react';
4import type { Dispatch, SetStateAction } from 'react';
5
6import { BarLoader } from './BarLoader';
7import { CommandFooter } from './CommandFooter';
8import { RNDirectoryItem, RNDocsItem, ExpoDocsItem, ExpoItem } from './Items';
9import { entries } from './expoEntries';
10import { searchIconStyle, closeIconStyle } from './styles';
11import type { ExpoItemType, RNDirectoryItemType, AlgoliaItemType } from './types';
12import { getExpoDocsResults, getRNDocsResults, getDirectoryResults, getItemsAsync } from './utils';
13
14import { CALLOUT } from '~/ui/components/Text';
15
16type Props = {
17  version: string;
18  open: boolean;
19  setOpen: Dispatch<SetStateAction<boolean>>;
20};
21
22export const CommandMenu = ({ version, open, setOpen }: Props) => {
23  const [loading, setLoading] = useState(false);
24  const [query, setQuery] = useState('');
25  const [expoDocsItems, setExpoDocsItems] = useState<AlgoliaItemType[]>([]);
26  const [expoItems, setExpoItems] = useState<ExpoItemType[]>([]);
27  const [rnDocsItems, setRnDocsItems] = useState<AlgoliaItemType[]>([]);
28  const [directoryItems, setDirectoryItems] = useState<RNDirectoryItemType[]>([]);
29
30  const getExpoDocsItems = async () =>
31    getItemsAsync(query, getExpoDocsResults, setExpoDocsItems, version);
32  const getRNDocsItems = async () => getItemsAsync(query, getRNDocsResults, setRnDocsItems);
33  const getDirectoryItems = async () =>
34    getItemsAsync(query, getDirectoryResults, setDirectoryItems);
35
36  const getExpoItems = async () => {
37    setExpoItems(entries.filter(entry => entry.label.toLowerCase().includes(query.toLowerCase())));
38  };
39
40  const dismiss = () => setOpen(false);
41
42  const fetchData = () => {
43    Promise.all([getExpoDocsItems(), getRNDocsItems(), getDirectoryItems(), getExpoItems()]).then(
44      () => setLoading(false)
45    );
46  };
47
48  const onQueryChange = () => {
49    setLoading(true);
50    const inputTimeout = setTimeout(fetchData, 150);
51    return () => clearTimeout(inputTimeout);
52  };
53
54  useEffect(onQueryChange, [query]);
55
56  const totalCount =
57    expoDocsItems.length + rnDocsItems.length + directoryItems.length + expoItems.length;
58
59  return (
60    <Command.Dialog open={open} onOpenChange={setOpen} label="Search Menu" shouldFilter={false}>
61      <SearchIcon color={theme.icon.secondary} css={searchIconStyle} />
62      <XIcon color={theme.icon.secondary} css={closeIconStyle} onClick={() => setOpen(false)} />
63      <Command.Input value={query} onValueChange={setQuery} placeholder="search anything…" />
64      <BarLoader isLoading={loading} />
65      <Command.List>
66        {expoDocsItems.length > 0 && (
67          <Command.Group heading="Expo documentation">
68            {expoDocsItems.map(item => (
69              <ExpoDocsItem item={item} onSelect={dismiss} key={`hit-expo-docs-${item.objectID}`} />
70            ))}
71          </Command.Group>
72        )}
73        {expoItems.length > 0 && (
74          <Command.Group heading="Expo dashboard">
75            {expoItems.map((item: ExpoItemType) => (
76              <ExpoItem item={item} onSelect={dismiss} key={`hit-expo-${item.url}`} query={query} />
77            ))}
78          </Command.Group>
79        )}
80        {rnDocsItems.length > 0 && (
81          <Command.Group heading="React Native documentation">
82            {rnDocsItems.map(item => (
83              <RNDocsItem item={item} onSelect={dismiss} key={`hit-rn-docs-${item.objectID}`} />
84            ))}
85          </Command.Group>
86        )}
87        {directoryItems.length > 0 && (
88          <Command.Group heading="React Native directory">
89            {directoryItems.map(item => (
90              <RNDirectoryItem
91                item={item}
92                onSelect={dismiss}
93                key={`hit-rn-dir-${item.npmPkg}`}
94                query={query}
95              />
96            ))}
97          </Command.Group>
98        )}
99        {totalCount === 0 && (
100          <Command.Empty>
101            <CALLOUT theme="secondary">No results found.</CALLOUT>
102          </Command.Empty>
103        )}
104      </Command.List>
105      <CommandFooter />
106    </Command.Dialog>
107  );
108};
109