import { Fragment, useEffect, useRef, useState } from 'react'; import { useKeydown } from '../../hooks/use-keydown'; import { useOutsideClick } from '../../hooks/use-outside-click'; import BestPracticesIcon from '../../icons/best-practices.svg'; import GuideIcon from '../../icons/guide.svg'; import HomeIcon from '../../icons/home.svg'; import RoadmapIcon from '../../icons/roadmap.svg'; import UserIcon from '../../icons/user.svg'; import GroupIcon from '../../icons/group.svg'; import VideoIcon from '../../icons/video.svg'; import { httpGet } from '../../lib/http'; import { isLoggedIn } from '../../lib/jwt'; export type PageType = { id: string; url: string; title: string; group: string; icon?: string; isProtected?: boolean; metadata?: Record; }; const defaultPages: PageType[] = [ { id: 'home', url: '/', title: 'Home', group: 'Pages', icon: HomeIcon.src }, { id: 'account', url: '/account', title: 'Account', group: 'Pages', icon: UserIcon.src, isProtected: true, }, { id: 'team', url: '/team', title: 'Teams', group: 'Pages', icon: GroupIcon.src, isProtected: true, }, { id: 'roadmaps', url: '/roadmaps', title: 'Roadmaps', group: 'Pages', icon: RoadmapIcon.src, }, { id: 'best-practices', url: '/best-practices', title: 'Best Practices', group: 'Pages', icon: BestPracticesIcon.src, }, { id: 'guides', url: '/guides', title: 'Guides', group: 'Pages', icon: GuideIcon.src, }, { id: 'videos', url: '/videos', title: 'Videos', group: 'Pages', icon: VideoIcon.src, }, ]; function shouldShowPage(page: PageType) { const isUser = isLoggedIn(); return !page.isProtected || isUser; } export function CommandMenu() { const inputRef = useRef(null); const modalRef = useRef(null); const [isActive, setIsActive] = useState(false); const [allPages, setAllPages] = useState([]); const [searchResults, setSearchResults] = useState(defaultPages); const [searchedText, setSearchedText] = useState(''); const [activeCounter, setActiveCounter] = useState(0); useKeydown('mod_k', () => { setIsActive(true); }); useOutsideClick(modalRef, () => { setSearchedText(''); setIsActive(false); }); useEffect(() => { function handleToggleTopic(e: any) { setIsActive(true); } getAllPages(); window.addEventListener(`command.k`, handleToggleTopic); return () => { window.removeEventListener(`command.k`, handleToggleTopic); }; }, []); useEffect(() => { if (!isActive || !inputRef.current) { return; } inputRef.current.focus(); }, [isActive]); async function getAllPages() { if (allPages.length > 0) { return allPages; } const { error, response } = await httpGet(`/pages.json`); if (!response) { return defaultPages.filter(shouldShowPage); } setAllPages([...defaultPages, ...response].filter(shouldShowPage)); return response; } useEffect(() => { if (!searchedText) { setSearchResults(defaultPages.filter(shouldShowPage)); return; } const normalizedSearchText = searchedText.trim().toLowerCase(); getAllPages().then((unfilteredPages = defaultPages) => { const filteredPages = unfilteredPages .filter((currPage: PageType) => { return ( currPage.title.toLowerCase().indexOf(normalizedSearchText) !== -1 ); }) .slice(0, 10); setActiveCounter(0); setSearchResults(filteredPages); }); }, [searchedText]); if (!isActive) { return null; } return (
{ const value = (e.target as HTMLInputElement).value.trim(); setSearchedText(value); }} onKeyDown={(e) => { if (e.key === 'ArrowDown') { const canGoNext = activeCounter < searchResults.length - 1; setActiveCounter(canGoNext ? activeCounter + 1 : 0); } else if (e.key === 'ArrowUp') { const canGoPrev = activeCounter > 0; setActiveCounter( canGoPrev ? activeCounter - 1 : searchResults.length - 1 ); } else if (e.key === 'Tab') { e.preventDefault(); } else if (e.key === 'Escape') { setSearchedText(''); setIsActive(false); } else if (e.key === 'Enter') { const activePage = searchResults[activeCounter]; if (activePage) { window.location.href = activePage.url; } } }} />
{searchResults.length === 0 && (
No results found
)} {searchResults.map((page: PageType, counter: number) => { const prevPage = searchResults[counter - 1]; const groupChanged = prevPage && prevPage.group !== page.group; return ( {groupChanged && (
)} setActiveCounter(counter)} href={page.url} > {!page.icon && ( {page.group} )} {page.icon && ( {page.title} )} {page.title}
); })}
); }