Command Menu
thegridcn/command-menuFreeComponent
thegridcn publishes no description for this item.
See it running
Open the demo on thegridcn
thegridcn.com/r/command-menuInstall it
npx shadcn@latest add https://thegridcn.com/r/command-menu.jsonSource
1"use client";23import * as React from "react";4import { cn } from "@/lib/utils";56interface CommandMenuItem {7 description?: string;8 group?: string;9 icon?: React.ReactNode;10 label: string;11 onSelect?: () => void;12 shortcut?: string;13}1415interface CommandMenuProps extends React.HTMLAttributes<HTMLDivElement> {16 items: CommandMenuItem[];17 label?: string;18 onOpenChange?: (open: boolean) => void;19 /** Controlled open state */20 open?: boolean;21 placeholder?: string;22}2324export function CommandMenu({25 items,26 placeholder = "Type a command...",27 label,28 open: controlledOpen,29 onOpenChange,30 className,31 ...props32}: CommandMenuProps) {33 const [internalOpen, setInternalOpen] = React.useState(false);34 const open = controlledOpen ?? internalOpen;35 const setOpen = onOpenChange ?? setInternalOpen;3637 const [query, setQuery] = React.useState("");38 const inputRef = React.useRef<HTMLInputElement>(null);3940 // Filter items41 const trimmed = query.trim().toLowerCase();42 const filtered = React.useMemo(() => {43 if (!trimmed) {44 return items;45 }46 return items.filter(47 (item) =>48 item.label.toLowerCase().includes(trimmed) ||49 item.description?.toLowerCase().includes(trimmed) ||50 item.group?.toLowerCase().includes(trimmed)51 );52 }, [items, trimmed]);5354 // Group items55 const groups = React.useMemo(() => {56 const map = new Map<string, CommandMenuItem[]>();57 for (const item of filtered) {58 const key = item.group || "";59 const arr = map.get(key) || [];60 arr.push(item);61 map.set(key, arr);62 }63 return Array.from(map.entries());64 }, [filtered]);6566 // Keyboard navigation67 const [activeIdx, setActiveIdx] = React.useState(0);68 React.useEffect(() => setActiveIdx(0), []);6970 // Ctrl/Cmd+K to toggle71 React.useEffect(() => {72 const handler = (e: KeyboardEvent) => {73 if ((e.metaKey || e.ctrlKey) && e.key === "k") {74 e.preventDefault();75 setOpen(!open);76 }77 if (e.key === "Escape" && open) {78 setOpen(false);79 }80 };81 window.addEventListener("keydown", handler);82 return () => window.removeEventListener("keydown", handler);83 }, [open, setOpen]);8485 // Focus input on open86 React.useEffect(() => {87 if (open) {88 setQuery("");89 setTimeout(() => inputRef.current?.focus(), 50);90 }91 }, [open]);9293 function handleKeyDown(e: React.KeyboardEvent) {94 if (e.key === "ArrowDown") {95 e.preventDefault();96// … truncated
Files it writes
More from thegridcn
All 139 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | thegridcn | Components | Free | 2 deps | |
| Agent Avataragent-avatar | thegridcn | Components | Free | no deps | |
| Alertalert | thegridcn | Components | Free | 1 dep | |
| Alert Dialogalert-dialog | thegridcn | Components | Free | 1 dep | |
| Announcement Barannouncement-bar | thegridcn | Components | Free | no deps | |
| Anomaly Banneranomaly-banner | thegridcn | Components | Free | no deps | |
| Arrival Panelarrival-panel | thegridcn | Components | Free | no deps | |
| Aspect Ratioaspect-ratio | thegridcn | Components | Free | 1 dep |
