import { Fragment, type ReactElement, useEffect, useRef, useState, } from 'react'; import { useKeydown } from '../../hooks/use-keydown'; import { useOutsideClick } from '../../hooks/use-outside-click'; import { httpGet } from '../../lib/http'; import { isLoggedIn } from '../../lib/jwt'; import { BestPracticesIcon } from '../ReactIcons/BestPracticesIcon.tsx'; import { UserIcon } from '../ReactIcons/UserIcon.tsx'; import { GroupIcon } from '../ReactIcons/GroupIcon.tsx'; import { RoadmapIcon } from '../ReactIcons/RoadmapIcon.tsx'; import { ClipboardIcon } from '../ReactIcons/ClipboardIcon.tsx'; import { GuideIcon } from '../ReactIcons/GuideIcon.tsx'; import { HomeIcon } from '../ReactIcons/HomeIcon.tsx'; import { VideoIcon } from '../ReactIcons/VideoIcon.tsx'; import { cn } from '../../lib/classname.ts'; import type { AllowedRoadmapRenderer } from '../../lib/roadmap.ts'; export type PageType = { id: string; url: string; title: string; group: string; icon?: ReactElement; isProtected?: boolean; metadata?: Record; renderer?: AllowedRoadmapRenderer; }; const defaultPages: PageType[] = [ { id: 'home', url: '/', title: 'Home', group: 'Pages', icon: , }, { id: 'account', url: '/account', title: 'Account', group: 'Pages', icon: , isProtected: true, }, { id: 'team', url: '/team', title: 'Teams', group: 'Pages', icon: , isProtected: true, }, { id: 'friends', url: '/account/friends', title: 'Friends', group: 'Pages', icon: , isProtected: true, }, { id: 'roadmaps', url: '/roadmaps', title: 'Roadmaps', group: 'Pages', icon: , }, { id: 'account-roadmaps', url: '/account/roadmaps', title: 'Custom Roadmaps', group: 'Pages', icon: , isProtected: true, }, { id: 'best-practices', url: '/best-practices', title: 'Best Practices', group: 'Pages', icon: , }, { id: 'questions', url: '/questions', title: 'Questions', group: 'Pages', icon: , }, { id: 'guides', url: '/guides', title: 'Guides', group: 'Pages', icon: , }, { id: 'videos', url: '/videos', title: 'Videos', group: 'Pages', icon: , }, ]; 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.icon} {page.title}
); })}
); }