"use client" import { useRouter } from "next/navigation" import { useEffect, useMemo, useState, useRef } from "react" import { ArrowUpIcon, ArrowDownIcon, CornerDownLeftIcon, FileTextIcon } from "lucide-react" import Anchor from "./anchor" import { cn } from "@/lib/utils" import { advanceSearch } from "@/lib/search/built-in" import { ScrollArea } from "@/components/ui/scroll-area" import { page_routes } from "@/lib/routes" import { DialogContent, DialogHeader, DialogFooter, DialogClose, DialogTitle, DialogDescription, } from "@/components/ui/dialog" type ContextInfo = { icon: string description: string title?: string } type SearchResult = { title: string href: string noLink?: boolean items?: undefined score?: number context?: ContextInfo } const paddingMap = { 1: "pl-2", 2: "pl-4", 3: "pl-10", } as const interface SearchModalProps { isOpen: boolean setIsOpen: (open: boolean) => void } export function SearchModal({ isOpen, setIsOpen }: SearchModalProps) { const router = useRouter() const [searchedInput, setSearchedInput] = useState("") const [selectedIndex, setSelectedIndex] = useState(0) const itemRefs = useRef<(HTMLDivElement | null)[]>([]) useEffect(() => { if (!isOpen) { // eslint-disable-next-line react-hooks/set-state-in-effect setSearchedInput("") } }, [isOpen]) const filteredResults = useMemo(() => { const trimmedInput = searchedInput.trim() if (trimmedInput.length < 3) { return page_routes .filter((route) => !route.href.endsWith("/")) .slice(0, 6) .map((route: { title: string; href: string; noLink?: boolean; context?: ContextInfo }) => ({ title: route.title, href: route.href, noLink: route.noLink, context: route.context, })) } return advanceSearch(trimmedInput) as unknown as SearchResult[] }, [searchedInput]) // useEffect(() => { // setSelectedIndex(0); // }, [filteredResults]); useEffect(() => { const handleNavigation = (event: KeyboardEvent) => { if (!isOpen || filteredResults.length === 0) return if (event.key === "ArrowDown") { event.preventDefault() setSelectedIndex((prev) => (prev + 1) % filteredResults.length) } else if (event.key === "ArrowUp") { event.preventDefault() setSelectedIndex((prev) => (prev - 1 + filteredResults.length) % filteredResults.length) } else if (event.key === "Enter") { event.preventDefault() const selectedItem = filteredResults[selectedIndex] if (selectedItem) { router.push(`/docs${selectedItem.href}`) setIsOpen(false) } } } window.addEventListener("keydown", handleNavigation) return () => window.removeEventListener("keydown", handleNavigation) }, [isOpen, filteredResults, selectedIndex, router, setIsOpen]) useEffect(() => { if (itemRefs.current[selectedIndex]) { itemRefs.current[selectedIndex]?.scrollIntoView({ behavior: "smooth", block: "nearest", }) } }, [selectedIndex]) return ( Search Documentation Search through the documentation { setSearchedInput(e.target.value) setSelectedIndex(0) }} placeholder="Type something to search..." autoFocus className="h-14 w-full border-b bg-transparent px-6 text-[14px] outline-none" aria-label="Search documentation" /> {filteredResults.length == 0 && searchedInput && (

No results found for {`"${searchedInput}"`}

)}
{filteredResults.map((item, index) => { const level = (item.href.split("/").slice(1).length - 1) as keyof typeof paddingMap const paddingClass = paddingMap[level] || "pl-2" const isActive = index === selectedIndex return ( { itemRefs.current[index] = el as HTMLDivElement | null }} className={cn( "dark:hover:bg-accent/15 hover:bg-accent/10 flex w-full items-center gap-2.5 rounded-sm px-3 text-sm", isActive && "bg-primary/20 dark:bg-primary/30", paddingClass )} href={`/docs${item.href}`} tabIndex={-1} >
1 && "border-l pl-4" )} >
{item.title}
{isActive && (
Return
)}
) })}

to navigate

to select

esc

to close

) }