import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Search } from "lucide-react"; import { VisuallyHidden } from "radix-ui"; import { useNavigate } from "react-router"; import { Dialog, DialogContent, DialogDescription, DialogTitle } from "@/components/ui/dialog"; import { countSettingsSearchItems, filterSettingsSearchEntries, filterSettingsSearchGroups, } from "@/components/settings/settingsSearch"; import { navigateToPluginRoute } from "@/lib/buildPluginHref"; import type { AdminNavGroup, AdminNavItem } from "@/lib/adminNavigation"; import { cn } from "@/lib/utils"; interface AdminSectionCommandDialogProps { sections: readonly AdminNavGroup[]; } export function AdminSectionCommandDialog({ sections }: AdminSectionCommandDialogProps) { const [open, setOpen] = useState(false); const [query, setQuery] = useState(""); const [selectedIndex, setSelectedIndex] = useState(0); const inputRef = useRef(null); const navigate = useNavigate(); const filteredSections = useMemo( () => filterSettingsSearchGroups(sections, query), [query, sections], ); const results = useMemo( () => filteredSections.flatMap((section) => section.items), [filteredSections], ); const resultIndexByHref = useMemo( () => new Map(results.map((item, index) => [item.href, index])), [results], ); const totalCount = countSettingsSearchItems(sections); const resultCount = results.length; const selectedResult = selectedIndex >= 0 ? results[selectedIndex] : undefined; const focusSearch = useCallback(() => { const focus = () => { inputRef.current?.focus(); inputRef.current?.select(); }; if (typeof window.requestAnimationFrame === "function") { window.requestAnimationFrame(focus); return; } window.setTimeout(focus, 0); }, []); const closeDialog = useCallback(() => { setOpen(false); setQuery(""); setSelectedIndex(0); }, []); const openDialog = useCallback(() => { setOpen(true); focusSearch(); }, [focusSearch]); useEffect(() => { const onKeyDown = (event: KeyboardEvent) => { if (event.defaultPrevented || !(event.metaKey || event.ctrlKey)) return; if (event.key.toLowerCase() !== "k") return; event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); openDialog(); }; window.addEventListener("keydown", onKeyDown, { capture: true }); return () => window.removeEventListener("keydown", onKeyDown, { capture: true }); }, [openDialog]); const pickResult = useCallback( (item: AdminNavItem) => { closeDialog(); if (item.external) { void navigateToPluginRoute(item.href); return; } navigate(item.href); }, [closeDialog, navigate], ); function handleInputKeyDown(event: React.KeyboardEvent) { if (event.key === "ArrowDown") { event.preventDefault(); setSelectedIndex((current) => (resultCount === 0 ? -1 : (current + 1) % resultCount)); } else if (event.key === "ArrowUp") { event.preventDefault(); setSelectedIndex((current) => resultCount === 0 ? -1 : current <= 0 ? resultCount - 1 : current - 1, ); } else if (event.key === "Enter") { if (!selectedResult) return; event.preventDefault(); pickResult(selectedResult); } else if (event.key === "Escape") { event.preventDefault(); closeDialog(); } } return ( { if (nextOpen) { openDialog(); return; } closeDialog(); }} > Search admin sections Search and open admin sections.
{filteredSections.length > 0 ? (
{filteredSections.map((section) => (
{section.label}
{section.items.map((item) => { const index = resultIndexByHref.get(item.href) ?? -1; return ( setSelectedIndex(index)} onPick={() => pickResult(item)} /> ); })}
))}
) : (

No matching admin sections

)}
{query.trim() ? `${resultCount} ${resultCount === 1 ? "match" : "matches"}` : `${totalCount} admin sections`}
); } function AdminCommandResultRow({ item, query, selected, onMouseEnter, onPick, }: { item: AdminNavItem; query: string; selected: boolean; onMouseEnter: () => void; onPick: () => void; }) { const Icon = item.icon; const matchingSettings = filterSettingsSearchEntries(item.settings, query).slice(0, 3); return ( ); } function resultId(href: string) { return `admin-command-${href.replace(/[^a-z0-9]+/gi, "-")}`; }