feat(mcp): подготовить сервер и доступ к приёмке
This commit is contained in:
commit
bf3d50e1ff
3835 files changed
+537122
No files matched your search
@@ -0,0 +1,74 @@
|
||||
"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>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user