@medialane/ui 0.84.0 → 0.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/nav-command-menu.cjs +10 -3
- package/dist/components/nav-command-menu.cjs.map +1 -1
- package/dist/components/nav-command-menu.d.cts +13 -1
- package/dist/components/nav-command-menu.d.ts +13 -1
- package/dist/components/nav-command-menu.js +10 -3
- package/dist/components/nav-command-menu.js.map +1 -1
- package/package.json +1 -1
|
@@ -120,7 +120,14 @@ const GROUP_HEADING_CLASSES = (0, import_cn.cn)(
|
|
|
120
120
|
"[&_[cmdk-group-heading]]:tracking-[0.08em]",
|
|
121
121
|
"[&_[cmdk-group-heading]]:text-muted-foreground/60"
|
|
122
122
|
);
|
|
123
|
-
function NavCommandMenu({
|
|
123
|
+
function NavCommandMenu({
|
|
124
|
+
commands,
|
|
125
|
+
trigger,
|
|
126
|
+
accountSlot,
|
|
127
|
+
footerSlot,
|
|
128
|
+
showKeyboardHints = true,
|
|
129
|
+
brandSlot
|
|
130
|
+
}) {
|
|
124
131
|
const [open, setOpen] = React.useState(false);
|
|
125
132
|
const [query, setQuery] = React.useState("");
|
|
126
133
|
const router = (0, import_navigation.useRouter)();
|
|
@@ -244,7 +251,7 @@ function NavCommandMenu({ commands, trigger, accountSlot, footerSlot }) {
|
|
|
244
251
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between gap-3 border-t border-border/40 bg-muted/20 px-3 py-2", children: [
|
|
245
252
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-3", children: [
|
|
246
253
|
footerSlot,
|
|
247
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "hidden items-center gap-3 text-[10.5px] text-muted-foreground/70 sm:flex", children: [
|
|
254
|
+
showKeyboardHints && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "hidden items-center gap-3 text-[10.5px] text-muted-foreground/70 sm:flex", children: [
|
|
248
255
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex items-center gap-1", children: [
|
|
249
256
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kbd, { children: "\u2191" }),
|
|
250
257
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kbd, { children: "\u2193" }),
|
|
@@ -256,7 +263,7 @@ function NavCommandMenu({ commands, trigger, accountSlot, footerSlot }) {
|
|
|
256
263
|
] })
|
|
257
264
|
] })
|
|
258
265
|
] }),
|
|
259
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex shrink-0 items-center gap-2 text-[10px] text-muted-foreground/50", children: [
|
|
266
|
+
brandSlot ?? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex shrink-0 items-center gap-2 text-[10px] text-muted-foreground/50", children: [
|
|
260
267
|
"medialane",
|
|
261
268
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kbd, { className: "hidden sm:inline-flex", children: "\u2318K" })
|
|
262
269
|
] })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/nav-command-menu.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { Command } from \"cmdk\";\nimport { useRouter } from \"next/navigation\";\nimport { Search, X, ArrowRight } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { cn } from \"../utils/cn.js\";\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface NavCommand {\n id: string;\n label: string;\n icon: React.ComponentType<{ className?: string }>;\n href?: string;\n action?: () => void;\n /** Extra search terms beyond the label */\n keywords?: string[];\n /** Optional one-line description shown under the label */\n description?: string;\n}\n\nexport interface NavCommandGroup {\n /**\n * Optional group heading. Omit it for the primary group so its items\n * read as the top-level menu — they render first, emphasized, with no\n * label, separated from the headed groups below.\n */\n heading?: string;\n items: NavCommand[];\n}\n\nexport interface NavCommandMenuProps {\n commands: NavCommandGroup[];\n /**\n * Optional trigger element rendered inline at the component's mount point.\n * For most apps, omit this and call `useNavCommandMenu().open()` from a\n * separate button — that keeps the trigger in the right place in the layout.\n */\n trigger?: React.ReactNode;\n /**\n * Optional pinned account/connect area rendered below command results.\n * Apps own the auth implementation here (Clerk, ChipiPay, wallet connectors,\n * Privy, Cartridge, etc.) so the shared nav stays framework-agnostic.\n */\n accountSlot?: React.ReactNode;\n /**\n * Optional control rendered in the footer row (e.g. a theme toggle).\n * Apps own theme state (next-themes etc.) so the shared nav stays\n * framework-agnostic — same pattern as `accountSlot`.\n */\n footerSlot?: React.ReactNode;\n}\n\n// ── Singleton hook ─────────────────────────────────────────────────────────────\n\nconst ML_NAV_OPEN = \"ml:nav-open\";\nconst ML_NAV_CLOSE = \"ml:nav-close\";\n\nexport function useNavCommandMenu() {\n return {\n open: () => document.dispatchEvent(new CustomEvent(ML_NAV_OPEN)),\n close: () => document.dispatchEvent(new CustomEvent(ML_NAV_CLOSE)),\n };\n}\n\n// ── Small primitives ──────────────────────────────────────────────────────────\n\nfunction Kbd({ children, className }: { children: React.ReactNode; className?: string }) {\n return (\n <kbd\n className={cn(\n \"inline-flex min-w-[18px] items-center justify-center rounded-md bg-muted/60 px-1.5 py-0.5\",\n \"font-sans text-[10px] leading-none text-muted-foreground\",\n className\n )}\n >\n {children}\n </kbd>\n );\n}\n\nfunction CommandRow({ item, primary, onSelect }: { item: NavCommand; primary: boolean; onSelect: () => void }) {\n return (\n <Command.Item\n value={[item.label, ...(item.keywords ?? [])].join(\" \")}\n onSelect={onSelect}\n className={cn(\n \"group/item flex cursor-pointer items-center gap-3 rounded-xl\",\n \"transition-colors duration-150 aria-selected:bg-primary/10\",\n primary ? \"px-2.5 py-2.5\" : \"px-2.5 py-2\"\n )}\n >\n <span\n className={cn(\n \"flex shrink-0 items-center justify-center rounded-lg transition-colors\",\n \"bg-muted/50 group-aria-selected/item:bg-primary/15\",\n primary ? \"h-9 w-9\" : \"h-8 w-8\"\n )}\n >\n <item.icon\n className={cn(\n \"text-foreground/80 transition-colors group-aria-selected/item:text-primary\",\n primary ? \"h-[18px] w-[18px]\" : \"h-4 w-4\"\n )}\n />\n </span>\n <span className=\"min-w-0 flex-1\">\n <span\n className={cn(\n \"block truncate leading-tight\",\n primary ? \"text-[15px] font-medium\" : \"text-sm\"\n )}\n >\n {item.label}\n </span>\n {item.description && (\n <span className=\"mt-0.5 block truncate text-[11.5px] leading-tight text-muted-foreground\">\n {item.description}\n </span>\n )}\n </span>\n <ArrowRight className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground/30 transition-colors group-aria-selected/item:text-primary/70\" />\n </Command.Item>\n );\n}\n\nconst GROUP_HEADING_CLASSES = cn(\n \"[&_[cmdk-group-heading]]:px-2.5\",\n \"[&_[cmdk-group-heading]]:pt-2\",\n \"[&_[cmdk-group-heading]]:pb-1\",\n \"[&_[cmdk-group-heading]]:text-[10.5px]\",\n \"[&_[cmdk-group-heading]]:font-semibold\",\n \"[&_[cmdk-group-heading]]:uppercase\",\n \"[&_[cmdk-group-heading]]:tracking-[0.08em]\",\n \"[&_[cmdk-group-heading]]:text-muted-foreground/60\"\n);\n\n// ── Component ─────────────────────────────────────────────────────────────────\n\nexport function NavCommandMenu({ commands, trigger, accountSlot, footerSlot }: NavCommandMenuProps) {\n const [open, setOpen] = React.useState(false);\n const [query, setQuery] = React.useState(\"\");\n const router = useRouter();\n const inputRef = React.useRef<HTMLInputElement>(null);\n\n React.useEffect(() => {\n if (!open) return;\n setQuery(\"\");\n const t = setTimeout(() => inputRef.current?.focus(), 60);\n return () => clearTimeout(t);\n }, [open]);\n\n React.useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"k\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n setOpen((prev) => !prev);\n }\n if (e.key === \"Escape\") setOpen(false);\n };\n const onOpen = () => setOpen(true);\n const onClose = () => setOpen(false);\n\n document.addEventListener(\"keydown\", onKey);\n document.addEventListener(ML_NAV_OPEN, onOpen);\n document.addEventListener(ML_NAV_CLOSE, onClose);\n return () => {\n document.removeEventListener(\"keydown\", onKey);\n document.removeEventListener(ML_NAV_OPEN, onOpen);\n document.removeEventListener(ML_NAV_CLOSE, onClose);\n };\n }, []);\n\n const runCommand = React.useCallback(\n (cmd: NavCommand) => {\n setOpen(false);\n if (cmd.href) router.push(cmd.href);\n else cmd.action?.();\n },\n [router]\n );\n\n return (\n <>\n {trigger}\n\n <AnimatePresence>\n {open && (\n <>\n {/* Backdrop blur */}\n <motion.div\n className=\"nav-canvas-overlay\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.15 }}\n onClick={() => setOpen(false)}\n />\n\n {/* Command panel — centered on desktop, bottom sheet on mobile */}\n <motion.div\n className=\"fixed inset-0 z-[101] flex items-end justify-center p-3 pb-4 sm:items-center sm:p-4\"\n initial={{ opacity: 0, y: 24, scale: 1 }}\n animate={{ opacity: 1, y: 0, scale: 1 }}\n exit={{ opacity: 0, y: 24 }}\n transition={{ duration: 0.18, ease: \"easeOut\" }}\n onClick={() => setOpen(false)}\n >\n <div\n className={cn(\n \"flex w-full max-h-[85dvh] flex-col overflow-hidden rounded-[20px] sm:max-h-[min(680px,88dvh)] sm:max-w-[620px]\",\n \"border border-border/40 bg-background/90 shadow-2xl backdrop-blur-2xl backdrop-saturate-150\"\n )}\n onClick={(e) => e.stopPropagation()}\n >\n <Command shouldFilter label=\"Medialane navigation\" className=\"flex min-h-0 flex-1 flex-col\">\n {/* Drag handle (mobile) */}\n <div className=\"flex justify-center pt-2.5 sm:hidden\" aria-hidden=\"true\">\n <span className=\"h-1 w-9 rounded-full bg-muted-foreground/30\" />\n </div>\n\n {/* Search bar */}\n <div className=\"flex items-center gap-3 border-b border-border/40 px-4 py-3.5\">\n <Search\n className={cn(\n \"h-[18px] w-[18px] shrink-0 transition-colors\",\n query ? \"text-primary\" : \"text-muted-foreground\"\n )}\n />\n <Command.Input\n ref={inputRef}\n value={query}\n onValueChange={setQuery}\n placeholder=\"Search or run a command…\"\n className=\"flex-1 bg-transparent text-[15px] outline-none placeholder:text-muted-foreground\"\n />\n <Kbd className=\"hidden sm:inline-flex\">esc</Kbd>\n <button\n onClick={() => setOpen(false)}\n className=\"rounded-md p-1 transition-colors hover:bg-muted/50 sm:hidden\"\n aria-label=\"Close\"\n >\n <X className=\"h-4 w-4 text-muted-foreground\" />\n </button>\n </div>\n\n {/* Results */}\n <Command.List className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain p-2\">\n <Command.Empty className=\"py-8 text-center text-sm text-muted-foreground\">\n No results found.\n </Command.Empty>\n\n {commands.map((group, i) => {\n const primary = !group.heading;\n return (\n <React.Fragment key={group.heading ?? `__primary-${i}`}>\n {i > 0 && (\n <Command.Separator className=\"my-1.5 h-px bg-border/40\" />\n )}\n <Command.Group heading={group.heading} className={GROUP_HEADING_CLASSES}>\n {group.items.map((item) => (\n <CommandRow\n key={item.id}\n item={item}\n primary={primary}\n onSelect={() => runCommand(item)}\n />\n ))}\n </Command.Group>\n </React.Fragment>\n );\n })}\n </Command.List>\n\n {accountSlot && (\n <div className=\"border-t border-border/40 bg-muted/20 px-3 py-3\">\n {accountSlot}\n </div>\n )}\n\n {/* Footer */}\n <div className=\"flex items-center justify-between gap-3 border-t border-border/40 bg-muted/20 px-3 py-2\">\n <div className=\"flex items-center gap-3\">\n {footerSlot}\n <span className=\"hidden items-center gap-3 text-[10.5px] text-muted-foreground/70 sm:flex\">\n <span className=\"flex items-center gap-1\"><Kbd>↑</Kbd><Kbd>↓</Kbd> Navigate</span>\n <span className=\"flex items-center gap-1\"><Kbd>↵</Kbd> Open</span>\n </span>\n </div>\n <span className=\"flex shrink-0 items-center gap-2 text-[10px] text-muted-foreground/50\">\n medialane\n <Kbd className=\"hidden sm:inline-flex\">⌘K</Kbd>\n </span>\n </div>\n </Command>\n </div>\n </motion.div>\n </>\n )}\n </AnimatePresence>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/nav-command-menu.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { Command } from \"cmdk\";\nimport { useRouter } from \"next/navigation\";\nimport { Search, X, ArrowRight } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { cn } from \"../utils/cn.js\";\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface NavCommand {\n id: string;\n label: string;\n icon: React.ComponentType<{ className?: string }>;\n href?: string;\n action?: () => void;\n /** Extra search terms beyond the label */\n keywords?: string[];\n /** Optional one-line description shown under the label */\n description?: string;\n}\n\nexport interface NavCommandGroup {\n /**\n * Optional group heading. Omit it for the primary group so its items\n * read as the top-level menu — they render first, emphasized, with no\n * label, separated from the headed groups below.\n */\n heading?: string;\n items: NavCommand[];\n}\n\nexport interface NavCommandMenuProps {\n commands: NavCommandGroup[];\n /**\n * Optional trigger element rendered inline at the component's mount point.\n * For most apps, omit this and call `useNavCommandMenu().open()` from a\n * separate button — that keeps the trigger in the right place in the layout.\n */\n trigger?: React.ReactNode;\n /**\n * Optional pinned account/connect area rendered below command results.\n * Apps own the auth implementation here (Clerk, ChipiPay, wallet connectors,\n * Privy, Cartridge, etc.) so the shared nav stays framework-agnostic.\n */\n accountSlot?: React.ReactNode;\n /**\n * Optional control rendered in the footer row (e.g. a theme toggle).\n * Apps own theme state (next-themes etc.) so the shared nav stays\n * framework-agnostic — same pattern as `accountSlot`.\n */\n footerSlot?: React.ReactNode;\n /**\n * Show the \"↑↓ Navigate ↵ Open\" keyboard-hint text next to `footerSlot`.\n * Defaults to `true` (unchanged behavior).\n */\n showKeyboardHints?: boolean;\n /**\n * Optional override for the trailing \"medialane ⌘K\" brandmark at the\n * footer's right edge — replaces it entirely rather than adorning it, so\n * an app can put something more actionable there (e.g. a \"Connect\" button)\n * instead of a static label. Omit to keep the default brandmark.\n */\n brandSlot?: React.ReactNode;\n}\n\n// ── Singleton hook ─────────────────────────────────────────────────────────────\n\nconst ML_NAV_OPEN = \"ml:nav-open\";\nconst ML_NAV_CLOSE = \"ml:nav-close\";\n\nexport function useNavCommandMenu() {\n return {\n open: () => document.dispatchEvent(new CustomEvent(ML_NAV_OPEN)),\n close: () => document.dispatchEvent(new CustomEvent(ML_NAV_CLOSE)),\n };\n}\n\n// ── Small primitives ──────────────────────────────────────────────────────────\n\nfunction Kbd({ children, className }: { children: React.ReactNode; className?: string }) {\n return (\n <kbd\n className={cn(\n \"inline-flex min-w-[18px] items-center justify-center rounded-md bg-muted/60 px-1.5 py-0.5\",\n \"font-sans text-[10px] leading-none text-muted-foreground\",\n className\n )}\n >\n {children}\n </kbd>\n );\n}\n\nfunction CommandRow({ item, primary, onSelect }: { item: NavCommand; primary: boolean; onSelect: () => void }) {\n return (\n <Command.Item\n value={[item.label, ...(item.keywords ?? [])].join(\" \")}\n onSelect={onSelect}\n className={cn(\n \"group/item flex cursor-pointer items-center gap-3 rounded-xl\",\n \"transition-colors duration-150 aria-selected:bg-primary/10\",\n primary ? \"px-2.5 py-2.5\" : \"px-2.5 py-2\"\n )}\n >\n <span\n className={cn(\n \"flex shrink-0 items-center justify-center rounded-lg transition-colors\",\n \"bg-muted/50 group-aria-selected/item:bg-primary/15\",\n primary ? \"h-9 w-9\" : \"h-8 w-8\"\n )}\n >\n <item.icon\n className={cn(\n \"text-foreground/80 transition-colors group-aria-selected/item:text-primary\",\n primary ? \"h-[18px] w-[18px]\" : \"h-4 w-4\"\n )}\n />\n </span>\n <span className=\"min-w-0 flex-1\">\n <span\n className={cn(\n \"block truncate leading-tight\",\n primary ? \"text-[15px] font-medium\" : \"text-sm\"\n )}\n >\n {item.label}\n </span>\n {item.description && (\n <span className=\"mt-0.5 block truncate text-[11.5px] leading-tight text-muted-foreground\">\n {item.description}\n </span>\n )}\n </span>\n <ArrowRight className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground/30 transition-colors group-aria-selected/item:text-primary/70\" />\n </Command.Item>\n );\n}\n\nconst GROUP_HEADING_CLASSES = cn(\n \"[&_[cmdk-group-heading]]:px-2.5\",\n \"[&_[cmdk-group-heading]]:pt-2\",\n \"[&_[cmdk-group-heading]]:pb-1\",\n \"[&_[cmdk-group-heading]]:text-[10.5px]\",\n \"[&_[cmdk-group-heading]]:font-semibold\",\n \"[&_[cmdk-group-heading]]:uppercase\",\n \"[&_[cmdk-group-heading]]:tracking-[0.08em]\",\n \"[&_[cmdk-group-heading]]:text-muted-foreground/60\"\n);\n\n// ── Component ─────────────────────────────────────────────────────────────────\n\nexport function NavCommandMenu({\n commands,\n trigger,\n accountSlot,\n footerSlot,\n showKeyboardHints = true,\n brandSlot,\n}: NavCommandMenuProps) {\n const [open, setOpen] = React.useState(false);\n const [query, setQuery] = React.useState(\"\");\n const router = useRouter();\n const inputRef = React.useRef<HTMLInputElement>(null);\n\n React.useEffect(() => {\n if (!open) return;\n setQuery(\"\");\n const t = setTimeout(() => inputRef.current?.focus(), 60);\n return () => clearTimeout(t);\n }, [open]);\n\n React.useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"k\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n setOpen((prev) => !prev);\n }\n if (e.key === \"Escape\") setOpen(false);\n };\n const onOpen = () => setOpen(true);\n const onClose = () => setOpen(false);\n\n document.addEventListener(\"keydown\", onKey);\n document.addEventListener(ML_NAV_OPEN, onOpen);\n document.addEventListener(ML_NAV_CLOSE, onClose);\n return () => {\n document.removeEventListener(\"keydown\", onKey);\n document.removeEventListener(ML_NAV_OPEN, onOpen);\n document.removeEventListener(ML_NAV_CLOSE, onClose);\n };\n }, []);\n\n const runCommand = React.useCallback(\n (cmd: NavCommand) => {\n setOpen(false);\n if (cmd.href) router.push(cmd.href);\n else cmd.action?.();\n },\n [router]\n );\n\n return (\n <>\n {trigger}\n\n <AnimatePresence>\n {open && (\n <>\n {/* Backdrop blur */}\n <motion.div\n className=\"nav-canvas-overlay\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.15 }}\n onClick={() => setOpen(false)}\n />\n\n {/* Command panel — centered on desktop, bottom sheet on mobile */}\n <motion.div\n className=\"fixed inset-0 z-[101] flex items-end justify-center p-3 pb-4 sm:items-center sm:p-4\"\n initial={{ opacity: 0, y: 24, scale: 1 }}\n animate={{ opacity: 1, y: 0, scale: 1 }}\n exit={{ opacity: 0, y: 24 }}\n transition={{ duration: 0.18, ease: \"easeOut\" }}\n onClick={() => setOpen(false)}\n >\n <div\n className={cn(\n \"flex w-full max-h-[85dvh] flex-col overflow-hidden rounded-[20px] sm:max-h-[min(680px,88dvh)] sm:max-w-[620px]\",\n \"border border-border/40 bg-background/90 shadow-2xl backdrop-blur-2xl backdrop-saturate-150\"\n )}\n onClick={(e) => e.stopPropagation()}\n >\n <Command shouldFilter label=\"Medialane navigation\" className=\"flex min-h-0 flex-1 flex-col\">\n {/* Drag handle (mobile) */}\n <div className=\"flex justify-center pt-2.5 sm:hidden\" aria-hidden=\"true\">\n <span className=\"h-1 w-9 rounded-full bg-muted-foreground/30\" />\n </div>\n\n {/* Search bar */}\n <div className=\"flex items-center gap-3 border-b border-border/40 px-4 py-3.5\">\n <Search\n className={cn(\n \"h-[18px] w-[18px] shrink-0 transition-colors\",\n query ? \"text-primary\" : \"text-muted-foreground\"\n )}\n />\n <Command.Input\n ref={inputRef}\n value={query}\n onValueChange={setQuery}\n placeholder=\"Search or run a command…\"\n className=\"flex-1 bg-transparent text-[15px] outline-none placeholder:text-muted-foreground\"\n />\n <Kbd className=\"hidden sm:inline-flex\">esc</Kbd>\n <button\n onClick={() => setOpen(false)}\n className=\"rounded-md p-1 transition-colors hover:bg-muted/50 sm:hidden\"\n aria-label=\"Close\"\n >\n <X className=\"h-4 w-4 text-muted-foreground\" />\n </button>\n </div>\n\n {/* Results */}\n <Command.List className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain p-2\">\n <Command.Empty className=\"py-8 text-center text-sm text-muted-foreground\">\n No results found.\n </Command.Empty>\n\n {commands.map((group, i) => {\n const primary = !group.heading;\n return (\n <React.Fragment key={group.heading ?? `__primary-${i}`}>\n {i > 0 && (\n <Command.Separator className=\"my-1.5 h-px bg-border/40\" />\n )}\n <Command.Group heading={group.heading} className={GROUP_HEADING_CLASSES}>\n {group.items.map((item) => (\n <CommandRow\n key={item.id}\n item={item}\n primary={primary}\n onSelect={() => runCommand(item)}\n />\n ))}\n </Command.Group>\n </React.Fragment>\n );\n })}\n </Command.List>\n\n {accountSlot && (\n <div className=\"border-t border-border/40 bg-muted/20 px-3 py-3\">\n {accountSlot}\n </div>\n )}\n\n {/* Footer */}\n <div className=\"flex items-center justify-between gap-3 border-t border-border/40 bg-muted/20 px-3 py-2\">\n <div className=\"flex items-center gap-3\">\n {footerSlot}\n {showKeyboardHints && (\n <span className=\"hidden items-center gap-3 text-[10.5px] text-muted-foreground/70 sm:flex\">\n <span className=\"flex items-center gap-1\"><Kbd>↑</Kbd><Kbd>↓</Kbd> Navigate</span>\n <span className=\"flex items-center gap-1\"><Kbd>↵</Kbd> Open</span>\n </span>\n )}\n </div>\n {brandSlot ?? (\n <span className=\"flex shrink-0 items-center gap-2 text-[10px] text-muted-foreground/50\">\n medialane\n <Kbd className=\"hidden sm:inline-flex\">⌘K</Kbd>\n </span>\n )}\n </div>\n </Command>\n </div>\n </motion.div>\n </>\n )}\n </AnimatePresence>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -43,11 +43,23 @@ interface NavCommandMenuProps {
|
|
|
43
43
|
* framework-agnostic — same pattern as `accountSlot`.
|
|
44
44
|
*/
|
|
45
45
|
footerSlot?: React.ReactNode;
|
|
46
|
+
/**
|
|
47
|
+
* Show the "↑↓ Navigate ↵ Open" keyboard-hint text next to `footerSlot`.
|
|
48
|
+
* Defaults to `true` (unchanged behavior).
|
|
49
|
+
*/
|
|
50
|
+
showKeyboardHints?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Optional override for the trailing "medialane ⌘K" brandmark at the
|
|
53
|
+
* footer's right edge — replaces it entirely rather than adorning it, so
|
|
54
|
+
* an app can put something more actionable there (e.g. a "Connect" button)
|
|
55
|
+
* instead of a static label. Omit to keep the default brandmark.
|
|
56
|
+
*/
|
|
57
|
+
brandSlot?: React.ReactNode;
|
|
46
58
|
}
|
|
47
59
|
declare function useNavCommandMenu(): {
|
|
48
60
|
open: () => boolean;
|
|
49
61
|
close: () => boolean;
|
|
50
62
|
};
|
|
51
|
-
declare function NavCommandMenu({ commands, trigger, accountSlot, footerSlot }: NavCommandMenuProps): react_jsx_runtime.JSX.Element;
|
|
63
|
+
declare function NavCommandMenu({ commands, trigger, accountSlot, footerSlot, showKeyboardHints, brandSlot, }: NavCommandMenuProps): react_jsx_runtime.JSX.Element;
|
|
52
64
|
|
|
53
65
|
export { type NavCommand, type NavCommandGroup, NavCommandMenu, type NavCommandMenuProps, useNavCommandMenu };
|
|
@@ -43,11 +43,23 @@ interface NavCommandMenuProps {
|
|
|
43
43
|
* framework-agnostic — same pattern as `accountSlot`.
|
|
44
44
|
*/
|
|
45
45
|
footerSlot?: React.ReactNode;
|
|
46
|
+
/**
|
|
47
|
+
* Show the "↑↓ Navigate ↵ Open" keyboard-hint text next to `footerSlot`.
|
|
48
|
+
* Defaults to `true` (unchanged behavior).
|
|
49
|
+
*/
|
|
50
|
+
showKeyboardHints?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Optional override for the trailing "medialane ⌘K" brandmark at the
|
|
53
|
+
* footer's right edge — replaces it entirely rather than adorning it, so
|
|
54
|
+
* an app can put something more actionable there (e.g. a "Connect" button)
|
|
55
|
+
* instead of a static label. Omit to keep the default brandmark.
|
|
56
|
+
*/
|
|
57
|
+
brandSlot?: React.ReactNode;
|
|
46
58
|
}
|
|
47
59
|
declare function useNavCommandMenu(): {
|
|
48
60
|
open: () => boolean;
|
|
49
61
|
close: () => boolean;
|
|
50
62
|
};
|
|
51
|
-
declare function NavCommandMenu({ commands, trigger, accountSlot, footerSlot }: NavCommandMenuProps): react_jsx_runtime.JSX.Element;
|
|
63
|
+
declare function NavCommandMenu({ commands, trigger, accountSlot, footerSlot, showKeyboardHints, brandSlot, }: NavCommandMenuProps): react_jsx_runtime.JSX.Element;
|
|
52
64
|
|
|
53
65
|
export { type NavCommand, type NavCommandGroup, NavCommandMenu, type NavCommandMenuProps, useNavCommandMenu };
|
|
@@ -86,7 +86,14 @@ const GROUP_HEADING_CLASSES = cn(
|
|
|
86
86
|
"[&_[cmdk-group-heading]]:tracking-[0.08em]",
|
|
87
87
|
"[&_[cmdk-group-heading]]:text-muted-foreground/60"
|
|
88
88
|
);
|
|
89
|
-
function NavCommandMenu({
|
|
89
|
+
function NavCommandMenu({
|
|
90
|
+
commands,
|
|
91
|
+
trigger,
|
|
92
|
+
accountSlot,
|
|
93
|
+
footerSlot,
|
|
94
|
+
showKeyboardHints = true,
|
|
95
|
+
brandSlot
|
|
96
|
+
}) {
|
|
90
97
|
const [open, setOpen] = React.useState(false);
|
|
91
98
|
const [query, setQuery] = React.useState("");
|
|
92
99
|
const router = useRouter();
|
|
@@ -210,7 +217,7 @@ function NavCommandMenu({ commands, trigger, accountSlot, footerSlot }) {
|
|
|
210
217
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-t border-border/40 bg-muted/20 px-3 py-2", children: [
|
|
211
218
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
212
219
|
footerSlot,
|
|
213
|
-
/* @__PURE__ */ jsxs("span", { className: "hidden items-center gap-3 text-[10.5px] text-muted-foreground/70 sm:flex", children: [
|
|
220
|
+
showKeyboardHints && /* @__PURE__ */ jsxs("span", { className: "hidden items-center gap-3 text-[10.5px] text-muted-foreground/70 sm:flex", children: [
|
|
214
221
|
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
|
|
215
222
|
/* @__PURE__ */ jsx(Kbd, { children: "\u2191" }),
|
|
216
223
|
/* @__PURE__ */ jsx(Kbd, { children: "\u2193" }),
|
|
@@ -222,7 +229,7 @@ function NavCommandMenu({ commands, trigger, accountSlot, footerSlot }) {
|
|
|
222
229
|
] })
|
|
223
230
|
] })
|
|
224
231
|
] }),
|
|
225
|
-
/* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-2 text-[10px] text-muted-foreground/50", children: [
|
|
232
|
+
brandSlot ?? /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-2 text-[10px] text-muted-foreground/50", children: [
|
|
226
233
|
"medialane",
|
|
227
234
|
/* @__PURE__ */ jsx(Kbd, { className: "hidden sm:inline-flex", children: "\u2318K" })
|
|
228
235
|
] })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/nav-command-menu.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { Command } from \"cmdk\";\nimport { useRouter } from \"next/navigation\";\nimport { Search, X, ArrowRight } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { cn } from \"../utils/cn.js\";\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface NavCommand {\n id: string;\n label: string;\n icon: React.ComponentType<{ className?: string }>;\n href?: string;\n action?: () => void;\n /** Extra search terms beyond the label */\n keywords?: string[];\n /** Optional one-line description shown under the label */\n description?: string;\n}\n\nexport interface NavCommandGroup {\n /**\n * Optional group heading. Omit it for the primary group so its items\n * read as the top-level menu — they render first, emphasized, with no\n * label, separated from the headed groups below.\n */\n heading?: string;\n items: NavCommand[];\n}\n\nexport interface NavCommandMenuProps {\n commands: NavCommandGroup[];\n /**\n * Optional trigger element rendered inline at the component's mount point.\n * For most apps, omit this and call `useNavCommandMenu().open()` from a\n * separate button — that keeps the trigger in the right place in the layout.\n */\n trigger?: React.ReactNode;\n /**\n * Optional pinned account/connect area rendered below command results.\n * Apps own the auth implementation here (Clerk, ChipiPay, wallet connectors,\n * Privy, Cartridge, etc.) so the shared nav stays framework-agnostic.\n */\n accountSlot?: React.ReactNode;\n /**\n * Optional control rendered in the footer row (e.g. a theme toggle).\n * Apps own theme state (next-themes etc.) so the shared nav stays\n * framework-agnostic — same pattern as `accountSlot`.\n */\n footerSlot?: React.ReactNode;\n}\n\n// ── Singleton hook ─────────────────────────────────────────────────────────────\n\nconst ML_NAV_OPEN = \"ml:nav-open\";\nconst ML_NAV_CLOSE = \"ml:nav-close\";\n\nexport function useNavCommandMenu() {\n return {\n open: () => document.dispatchEvent(new CustomEvent(ML_NAV_OPEN)),\n close: () => document.dispatchEvent(new CustomEvent(ML_NAV_CLOSE)),\n };\n}\n\n// ── Small primitives ──────────────────────────────────────────────────────────\n\nfunction Kbd({ children, className }: { children: React.ReactNode; className?: string }) {\n return (\n <kbd\n className={cn(\n \"inline-flex min-w-[18px] items-center justify-center rounded-md bg-muted/60 px-1.5 py-0.5\",\n \"font-sans text-[10px] leading-none text-muted-foreground\",\n className\n )}\n >\n {children}\n </kbd>\n );\n}\n\nfunction CommandRow({ item, primary, onSelect }: { item: NavCommand; primary: boolean; onSelect: () => void }) {\n return (\n <Command.Item\n value={[item.label, ...(item.keywords ?? [])].join(\" \")}\n onSelect={onSelect}\n className={cn(\n \"group/item flex cursor-pointer items-center gap-3 rounded-xl\",\n \"transition-colors duration-150 aria-selected:bg-primary/10\",\n primary ? \"px-2.5 py-2.5\" : \"px-2.5 py-2\"\n )}\n >\n <span\n className={cn(\n \"flex shrink-0 items-center justify-center rounded-lg transition-colors\",\n \"bg-muted/50 group-aria-selected/item:bg-primary/15\",\n primary ? \"h-9 w-9\" : \"h-8 w-8\"\n )}\n >\n <item.icon\n className={cn(\n \"text-foreground/80 transition-colors group-aria-selected/item:text-primary\",\n primary ? \"h-[18px] w-[18px]\" : \"h-4 w-4\"\n )}\n />\n </span>\n <span className=\"min-w-0 flex-1\">\n <span\n className={cn(\n \"block truncate leading-tight\",\n primary ? \"text-[15px] font-medium\" : \"text-sm\"\n )}\n >\n {item.label}\n </span>\n {item.description && (\n <span className=\"mt-0.5 block truncate text-[11.5px] leading-tight text-muted-foreground\">\n {item.description}\n </span>\n )}\n </span>\n <ArrowRight className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground/30 transition-colors group-aria-selected/item:text-primary/70\" />\n </Command.Item>\n );\n}\n\nconst GROUP_HEADING_CLASSES = cn(\n \"[&_[cmdk-group-heading]]:px-2.5\",\n \"[&_[cmdk-group-heading]]:pt-2\",\n \"[&_[cmdk-group-heading]]:pb-1\",\n \"[&_[cmdk-group-heading]]:text-[10.5px]\",\n \"[&_[cmdk-group-heading]]:font-semibold\",\n \"[&_[cmdk-group-heading]]:uppercase\",\n \"[&_[cmdk-group-heading]]:tracking-[0.08em]\",\n \"[&_[cmdk-group-heading]]:text-muted-foreground/60\"\n);\n\n// ── Component ─────────────────────────────────────────────────────────────────\n\nexport function NavCommandMenu({ commands, trigger, accountSlot, footerSlot }: NavCommandMenuProps) {\n const [open, setOpen] = React.useState(false);\n const [query, setQuery] = React.useState(\"\");\n const router = useRouter();\n const inputRef = React.useRef<HTMLInputElement>(null);\n\n React.useEffect(() => {\n if (!open) return;\n setQuery(\"\");\n const t = setTimeout(() => inputRef.current?.focus(), 60);\n return () => clearTimeout(t);\n }, [open]);\n\n React.useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"k\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n setOpen((prev) => !prev);\n }\n if (e.key === \"Escape\") setOpen(false);\n };\n const onOpen = () => setOpen(true);\n const onClose = () => setOpen(false);\n\n document.addEventListener(\"keydown\", onKey);\n document.addEventListener(ML_NAV_OPEN, onOpen);\n document.addEventListener(ML_NAV_CLOSE, onClose);\n return () => {\n document.removeEventListener(\"keydown\", onKey);\n document.removeEventListener(ML_NAV_OPEN, onOpen);\n document.removeEventListener(ML_NAV_CLOSE, onClose);\n };\n }, []);\n\n const runCommand = React.useCallback(\n (cmd: NavCommand) => {\n setOpen(false);\n if (cmd.href) router.push(cmd.href);\n else cmd.action?.();\n },\n [router]\n );\n\n return (\n <>\n {trigger}\n\n <AnimatePresence>\n {open && (\n <>\n {/* Backdrop blur */}\n <motion.div\n className=\"nav-canvas-overlay\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.15 }}\n onClick={() => setOpen(false)}\n />\n\n {/* Command panel — centered on desktop, bottom sheet on mobile */}\n <motion.div\n className=\"fixed inset-0 z-[101] flex items-end justify-center p-3 pb-4 sm:items-center sm:p-4\"\n initial={{ opacity: 0, y: 24, scale: 1 }}\n animate={{ opacity: 1, y: 0, scale: 1 }}\n exit={{ opacity: 0, y: 24 }}\n transition={{ duration: 0.18, ease: \"easeOut\" }}\n onClick={() => setOpen(false)}\n >\n <div\n className={cn(\n \"flex w-full max-h-[85dvh] flex-col overflow-hidden rounded-[20px] sm:max-h-[min(680px,88dvh)] sm:max-w-[620px]\",\n \"border border-border/40 bg-background/90 shadow-2xl backdrop-blur-2xl backdrop-saturate-150\"\n )}\n onClick={(e) => e.stopPropagation()}\n >\n <Command shouldFilter label=\"Medialane navigation\" className=\"flex min-h-0 flex-1 flex-col\">\n {/* Drag handle (mobile) */}\n <div className=\"flex justify-center pt-2.5 sm:hidden\" aria-hidden=\"true\">\n <span className=\"h-1 w-9 rounded-full bg-muted-foreground/30\" />\n </div>\n\n {/* Search bar */}\n <div className=\"flex items-center gap-3 border-b border-border/40 px-4 py-3.5\">\n <Search\n className={cn(\n \"h-[18px] w-[18px] shrink-0 transition-colors\",\n query ? \"text-primary\" : \"text-muted-foreground\"\n )}\n />\n <Command.Input\n ref={inputRef}\n value={query}\n onValueChange={setQuery}\n placeholder=\"Search or run a command…\"\n className=\"flex-1 bg-transparent text-[15px] outline-none placeholder:text-muted-foreground\"\n />\n <Kbd className=\"hidden sm:inline-flex\">esc</Kbd>\n <button\n onClick={() => setOpen(false)}\n className=\"rounded-md p-1 transition-colors hover:bg-muted/50 sm:hidden\"\n aria-label=\"Close\"\n >\n <X className=\"h-4 w-4 text-muted-foreground\" />\n </button>\n </div>\n\n {/* Results */}\n <Command.List className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain p-2\">\n <Command.Empty className=\"py-8 text-center text-sm text-muted-foreground\">\n No results found.\n </Command.Empty>\n\n {commands.map((group, i) => {\n const primary = !group.heading;\n return (\n <React.Fragment key={group.heading ?? `__primary-${i}`}>\n {i > 0 && (\n <Command.Separator className=\"my-1.5 h-px bg-border/40\" />\n )}\n <Command.Group heading={group.heading} className={GROUP_HEADING_CLASSES}>\n {group.items.map((item) => (\n <CommandRow\n key={item.id}\n item={item}\n primary={primary}\n onSelect={() => runCommand(item)}\n />\n ))}\n </Command.Group>\n </React.Fragment>\n );\n })}\n </Command.List>\n\n {accountSlot && (\n <div className=\"border-t border-border/40 bg-muted/20 px-3 py-3\">\n {accountSlot}\n </div>\n )}\n\n {/* Footer */}\n <div className=\"flex items-center justify-between gap-3 border-t border-border/40 bg-muted/20 px-3 py-2\">\n <div className=\"flex items-center gap-3\">\n {footerSlot}\n <span className=\"hidden items-center gap-3 text-[10.5px] text-muted-foreground/70 sm:flex\">\n <span className=\"flex items-center gap-1\"><Kbd>↑</Kbd><Kbd>↓</Kbd> Navigate</span>\n <span className=\"flex items-center gap-1\"><Kbd>↵</Kbd> Open</span>\n </span>\n </div>\n <span className=\"flex shrink-0 items-center gap-2 text-[10px] text-muted-foreground/50\">\n medialane\n <Kbd className=\"hidden sm:inline-flex\">⌘K</Kbd>\n </span>\n </div>\n </Command>\n </div>\n </motion.div>\n </>\n )}\n </AnimatePresence>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/nav-command-menu.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { Command } from \"cmdk\";\nimport { useRouter } from \"next/navigation\";\nimport { Search, X, ArrowRight } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { cn } from \"../utils/cn.js\";\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface NavCommand {\n id: string;\n label: string;\n icon: React.ComponentType<{ className?: string }>;\n href?: string;\n action?: () => void;\n /** Extra search terms beyond the label */\n keywords?: string[];\n /** Optional one-line description shown under the label */\n description?: string;\n}\n\nexport interface NavCommandGroup {\n /**\n * Optional group heading. Omit it for the primary group so its items\n * read as the top-level menu — they render first, emphasized, with no\n * label, separated from the headed groups below.\n */\n heading?: string;\n items: NavCommand[];\n}\n\nexport interface NavCommandMenuProps {\n commands: NavCommandGroup[];\n /**\n * Optional trigger element rendered inline at the component's mount point.\n * For most apps, omit this and call `useNavCommandMenu().open()` from a\n * separate button — that keeps the trigger in the right place in the layout.\n */\n trigger?: React.ReactNode;\n /**\n * Optional pinned account/connect area rendered below command results.\n * Apps own the auth implementation here (Clerk, ChipiPay, wallet connectors,\n * Privy, Cartridge, etc.) so the shared nav stays framework-agnostic.\n */\n accountSlot?: React.ReactNode;\n /**\n * Optional control rendered in the footer row (e.g. a theme toggle).\n * Apps own theme state (next-themes etc.) so the shared nav stays\n * framework-agnostic — same pattern as `accountSlot`.\n */\n footerSlot?: React.ReactNode;\n /**\n * Show the \"↑↓ Navigate ↵ Open\" keyboard-hint text next to `footerSlot`.\n * Defaults to `true` (unchanged behavior).\n */\n showKeyboardHints?: boolean;\n /**\n * Optional override for the trailing \"medialane ⌘K\" brandmark at the\n * footer's right edge — replaces it entirely rather than adorning it, so\n * an app can put something more actionable there (e.g. a \"Connect\" button)\n * instead of a static label. Omit to keep the default brandmark.\n */\n brandSlot?: React.ReactNode;\n}\n\n// ── Singleton hook ─────────────────────────────────────────────────────────────\n\nconst ML_NAV_OPEN = \"ml:nav-open\";\nconst ML_NAV_CLOSE = \"ml:nav-close\";\n\nexport function useNavCommandMenu() {\n return {\n open: () => document.dispatchEvent(new CustomEvent(ML_NAV_OPEN)),\n close: () => document.dispatchEvent(new CustomEvent(ML_NAV_CLOSE)),\n };\n}\n\n// ── Small primitives ──────────────────────────────────────────────────────────\n\nfunction Kbd({ children, className }: { children: React.ReactNode; className?: string }) {\n return (\n <kbd\n className={cn(\n \"inline-flex min-w-[18px] items-center justify-center rounded-md bg-muted/60 px-1.5 py-0.5\",\n \"font-sans text-[10px] leading-none text-muted-foreground\",\n className\n )}\n >\n {children}\n </kbd>\n );\n}\n\nfunction CommandRow({ item, primary, onSelect }: { item: NavCommand; primary: boolean; onSelect: () => void }) {\n return (\n <Command.Item\n value={[item.label, ...(item.keywords ?? [])].join(\" \")}\n onSelect={onSelect}\n className={cn(\n \"group/item flex cursor-pointer items-center gap-3 rounded-xl\",\n \"transition-colors duration-150 aria-selected:bg-primary/10\",\n primary ? \"px-2.5 py-2.5\" : \"px-2.5 py-2\"\n )}\n >\n <span\n className={cn(\n \"flex shrink-0 items-center justify-center rounded-lg transition-colors\",\n \"bg-muted/50 group-aria-selected/item:bg-primary/15\",\n primary ? \"h-9 w-9\" : \"h-8 w-8\"\n )}\n >\n <item.icon\n className={cn(\n \"text-foreground/80 transition-colors group-aria-selected/item:text-primary\",\n primary ? \"h-[18px] w-[18px]\" : \"h-4 w-4\"\n )}\n />\n </span>\n <span className=\"min-w-0 flex-1\">\n <span\n className={cn(\n \"block truncate leading-tight\",\n primary ? \"text-[15px] font-medium\" : \"text-sm\"\n )}\n >\n {item.label}\n </span>\n {item.description && (\n <span className=\"mt-0.5 block truncate text-[11.5px] leading-tight text-muted-foreground\">\n {item.description}\n </span>\n )}\n </span>\n <ArrowRight className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground/30 transition-colors group-aria-selected/item:text-primary/70\" />\n </Command.Item>\n );\n}\n\nconst GROUP_HEADING_CLASSES = cn(\n \"[&_[cmdk-group-heading]]:px-2.5\",\n \"[&_[cmdk-group-heading]]:pt-2\",\n \"[&_[cmdk-group-heading]]:pb-1\",\n \"[&_[cmdk-group-heading]]:text-[10.5px]\",\n \"[&_[cmdk-group-heading]]:font-semibold\",\n \"[&_[cmdk-group-heading]]:uppercase\",\n \"[&_[cmdk-group-heading]]:tracking-[0.08em]\",\n \"[&_[cmdk-group-heading]]:text-muted-foreground/60\"\n);\n\n// ── Component ─────────────────────────────────────────────────────────────────\n\nexport function NavCommandMenu({\n commands,\n trigger,\n accountSlot,\n footerSlot,\n showKeyboardHints = true,\n brandSlot,\n}: NavCommandMenuProps) {\n const [open, setOpen] = React.useState(false);\n const [query, setQuery] = React.useState(\"\");\n const router = useRouter();\n const inputRef = React.useRef<HTMLInputElement>(null);\n\n React.useEffect(() => {\n if (!open) return;\n setQuery(\"\");\n const t = setTimeout(() => inputRef.current?.focus(), 60);\n return () => clearTimeout(t);\n }, [open]);\n\n React.useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"k\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n setOpen((prev) => !prev);\n }\n if (e.key === \"Escape\") setOpen(false);\n };\n const onOpen = () => setOpen(true);\n const onClose = () => setOpen(false);\n\n document.addEventListener(\"keydown\", onKey);\n document.addEventListener(ML_NAV_OPEN, onOpen);\n document.addEventListener(ML_NAV_CLOSE, onClose);\n return () => {\n document.removeEventListener(\"keydown\", onKey);\n document.removeEventListener(ML_NAV_OPEN, onOpen);\n document.removeEventListener(ML_NAV_CLOSE, onClose);\n };\n }, []);\n\n const runCommand = React.useCallback(\n (cmd: NavCommand) => {\n setOpen(false);\n if (cmd.href) router.push(cmd.href);\n else cmd.action?.();\n },\n [router]\n );\n\n return (\n <>\n {trigger}\n\n <AnimatePresence>\n {open && (\n <>\n {/* Backdrop blur */}\n <motion.div\n className=\"nav-canvas-overlay\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.15 }}\n onClick={() => setOpen(false)}\n />\n\n {/* Command panel — centered on desktop, bottom sheet on mobile */}\n <motion.div\n className=\"fixed inset-0 z-[101] flex items-end justify-center p-3 pb-4 sm:items-center sm:p-4\"\n initial={{ opacity: 0, y: 24, scale: 1 }}\n animate={{ opacity: 1, y: 0, scale: 1 }}\n exit={{ opacity: 0, y: 24 }}\n transition={{ duration: 0.18, ease: \"easeOut\" }}\n onClick={() => setOpen(false)}\n >\n <div\n className={cn(\n \"flex w-full max-h-[85dvh] flex-col overflow-hidden rounded-[20px] sm:max-h-[min(680px,88dvh)] sm:max-w-[620px]\",\n \"border border-border/40 bg-background/90 shadow-2xl backdrop-blur-2xl backdrop-saturate-150\"\n )}\n onClick={(e) => e.stopPropagation()}\n >\n <Command shouldFilter label=\"Medialane navigation\" className=\"flex min-h-0 flex-1 flex-col\">\n {/* Drag handle (mobile) */}\n <div className=\"flex justify-center pt-2.5 sm:hidden\" aria-hidden=\"true\">\n <span className=\"h-1 w-9 rounded-full bg-muted-foreground/30\" />\n </div>\n\n {/* Search bar */}\n <div className=\"flex items-center gap-3 border-b border-border/40 px-4 py-3.5\">\n <Search\n className={cn(\n \"h-[18px] w-[18px] shrink-0 transition-colors\",\n query ? \"text-primary\" : \"text-muted-foreground\"\n )}\n />\n <Command.Input\n ref={inputRef}\n value={query}\n onValueChange={setQuery}\n placeholder=\"Search or run a command…\"\n className=\"flex-1 bg-transparent text-[15px] outline-none placeholder:text-muted-foreground\"\n />\n <Kbd className=\"hidden sm:inline-flex\">esc</Kbd>\n <button\n onClick={() => setOpen(false)}\n className=\"rounded-md p-1 transition-colors hover:bg-muted/50 sm:hidden\"\n aria-label=\"Close\"\n >\n <X className=\"h-4 w-4 text-muted-foreground\" />\n </button>\n </div>\n\n {/* Results */}\n <Command.List className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain p-2\">\n <Command.Empty className=\"py-8 text-center text-sm text-muted-foreground\">\n No results found.\n </Command.Empty>\n\n {commands.map((group, i) => {\n const primary = !group.heading;\n return (\n <React.Fragment key={group.heading ?? `__primary-${i}`}>\n {i > 0 && (\n <Command.Separator className=\"my-1.5 h-px bg-border/40\" />\n )}\n <Command.Group heading={group.heading} className={GROUP_HEADING_CLASSES}>\n {group.items.map((item) => (\n <CommandRow\n key={item.id}\n item={item}\n primary={primary}\n onSelect={() => runCommand(item)}\n />\n ))}\n </Command.Group>\n </React.Fragment>\n );\n })}\n </Command.List>\n\n {accountSlot && (\n <div className=\"border-t border-border/40 bg-muted/20 px-3 py-3\">\n {accountSlot}\n </div>\n )}\n\n {/* Footer */}\n <div className=\"flex items-center justify-between gap-3 border-t border-border/40 bg-muted/20 px-3 py-2\">\n <div className=\"flex items-center gap-3\">\n {footerSlot}\n {showKeyboardHints && (\n <span className=\"hidden items-center gap-3 text-[10.5px] text-muted-foreground/70 sm:flex\">\n <span className=\"flex items-center gap-1\"><Kbd>↑</Kbd><Kbd>↓</Kbd> Navigate</span>\n <span className=\"flex items-center gap-1\"><Kbd>↵</Kbd> Open</span>\n </span>\n )}\n </div>\n {brandSlot ?? (\n <span className=\"flex shrink-0 items-center gap-2 text-[10px] text-muted-foreground/50\">\n medialane\n <Kbd className=\"hidden sm:inline-flex\">⌘K</Kbd>\n </span>\n )}\n </div>\n </Command>\n </div>\n </motion.div>\n </>\n )}\n </AnimatePresence>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|