Files
MikrotikManager/components/command-palette.tsx
T

75 lines
3.5 KiB
TypeScript

"use client"
import { useEffect, useRef, useState } from "react"
import { usePathname, useRouter } from "next/navigation"
import { navigationGroups } from "@/lib/navigation"
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"
import { Dialog, DialogContent, DialogDescription, DialogTitle } from "@/components/ui/dialog"
/** Keyboard search from the App Shell 12 composition, using installed Base UI primitives. */
export function CommandPalette() {
const [open, setOpen] = useState(false)
const [query, setQuery] = useState("")
const inputRef = useRef<HTMLInputElement>(null)
const returnFocusRef = useRef<HTMLElement | null>(null)
const pathname = usePathname()
const router = useRouter()
useEffect(() => {
function onKey(event: KeyboardEvent) {
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k" && !event.isComposing) {
event.preventDefault()
if (!open) {
returnFocusRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null
setQuery("")
}
setOpen(!open)
}
}
window.addEventListener("keydown", onKey)
return () => window.removeEventListener("keydown", onKey)
}, [open])
useEffect(() => {
queueMicrotask(() => setOpen(false))
}, [pathname])
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent
showCloseButton={false}
initialFocus={inputRef}
finalFocus={returnFocusRef}
className="flex h-[min(32rem,calc(100dvh-2rem))] flex-col gap-0 overflow-hidden p-0 sm:max-w-xl"
>
<DialogTitle className="sr-only">Поиск страниц</DialogTitle>
<DialogDescription className="sr-only">Найдите раздел по названию или ключевым словам. Стрелки выбирают страницу, Enter открывает её, Escape закрывает поиск.</DialogDescription>
<Command className="min-h-0 flex-1" label="Поиск страниц">
<CommandInput ref={inputRef} aria-label="Поиск страниц" placeholder="Название страницы или ключевое слово…" value={query} onValueChange={setQuery} />
<CommandList className="min-h-0 max-h-none flex-1" label="Найденные страницы">
<CommandEmpty><p role="status">Ничего не найдено. Попробуйте другое название.</p></CommandEmpty>
{navigationGroups.map(group => (
<CommandGroup key={group.label} heading={group.label}>
{group.items.map(item => {
const Icon = item.icon
return (
<CommandItem key={item.id} value={item.title} keywords={item.keywords} onSelect={() => {
setOpen(false)
router.push(item.url)
}}>
<Icon aria-hidden="true" />
<span>{item.title}</span>
{pathname === item.url && <span className="ml-auto text-xs text-muted-foreground">Текущая</span>}
</CommandItem>
)
})}
</CommandGroup>
))}
</CommandList>
</Command>
<p className="shrink-0 border-t px-3 py-2 text-xs text-muted-foreground">↑↓ выбрать · Enter открыть · Esc закрыть</p>
</DialogContent>
</Dialog>
)
}