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