@medialane/ui 0.5.0 → 0.5.1
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.
|
@@ -51,6 +51,12 @@ function useNavCommandMenu() {
|
|
|
51
51
|
function NavCommandMenu({ commands, trigger }) {
|
|
52
52
|
const [open, setOpen] = React.useState(false);
|
|
53
53
|
const router = (0, import_navigation.useRouter)();
|
|
54
|
+
const inputRef = React.useRef(null);
|
|
55
|
+
React.useEffect(() => {
|
|
56
|
+
if (!open) return;
|
|
57
|
+
const t = setTimeout(() => inputRef.current?.focus(), 60);
|
|
58
|
+
return () => clearTimeout(t);
|
|
59
|
+
}, [open]);
|
|
54
60
|
React.useEffect(() => {
|
|
55
61
|
const onKey = (e) => {
|
|
56
62
|
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
|
@@ -140,6 +146,7 @@ function NavCommandMenu({ commands, trigger }) {
|
|
|
140
146
|
animate: { opacity: 1, scale: 1 },
|
|
141
147
|
exit: { opacity: 0, scale: 0.97 },
|
|
142
148
|
transition: { duration: 0.15, ease: "easeOut" },
|
|
149
|
+
onClick: () => setOpen(false),
|
|
143
150
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
144
151
|
"div",
|
|
145
152
|
{
|
|
@@ -151,6 +158,7 @@ function NavCommandMenu({ commands, trigger }) {
|
|
|
151
158
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
152
159
|
import_cmdk.Command.Input,
|
|
153
160
|
{
|
|
161
|
+
ref: inputRef,
|
|
154
162
|
placeholder: "Type a command or search\u2026",
|
|
155
163
|
className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
156
164
|
}
|
|
@@ -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}\n\nexport interface NavCommandGroup {\n heading: string;\n items: NavCommand[];\n}\n\ninterface 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\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// ── Component ─────────────────────────────────────────────────────────────────\n\nexport function NavCommandMenu({ commands, trigger }: NavCommandMenuProps) {\n const [open, setOpen] = React.useState(false);\n const router = useRouter();\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 {/* Aurora blobs — vivid intensity */}\n <motion.div\n className=\"nav-canvas-aurora\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.2 }}\n >\n <div className=\"aurora-purple animate-blob w-[60vw] h-[60vw] -top-[10vw] -left-[10vw]\"\n style={{ opacity: 0.25 }} />\n <div className=\"aurora-blue animate-blob-slow w-[50vw] h-[50vw] -top-[5vw] -right-[10vw]\"\n style={{ opacity: 0.2 }} />\n <div className=\"aurora-rose animate-blob w-[45vw] h-[45vw] bottom-[5vw] -left-[5vw]\"\n style={{ opacity: 0.15 }} />\n <div className=\"aurora-orange animate-blob-slow w-[40vw] h-[40vw] -bottom-[5vw] -right-[5vw]\"\n style={{ opacity: 0.15 }} />\n </motion.div>\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 */}\n <motion.div\n className=\"fixed inset-0 z-[101] flex items-center justify-center p-4\"\n initial={{ opacity: 0, scale: 0.97 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.97 }}\n transition={{ duration: 0.15, ease: \"easeOut\" }}\n >\n <div\n className=\"w-full max-w-lg bg-background/90 border border-border/40 rounded-2xl overflow-hidden shadow-2xl\"\n onClick={(e) => e.stopPropagation()}\n >\n <Command shouldFilter label=\"Medialane navigation\">\n {/* Search bar */}\n <div className=\"flex items-center gap-3 px-4 py-3 border-b border-border/40\">\n <Search className=\"h-4 w-4 text-muted-foreground shrink-0\" />\n <Command.Input\n placeholder=\"Type a command or search…\"\n className=\"flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground\"\n />\n <button\n onClick={() => setOpen(false)}\n className=\"p-1 rounded-md hover:bg-muted/50 transition-colors\"\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=\"max-h-[60vh] overflow-y-auto 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 <React.Fragment key={group.heading}>\n {i > 0 && (\n <Command.Separator className=\"my-1 h-px bg-border/40\" />\n )}\n <Command.Group\n heading={group.heading}\n className={cn(\n \"[&_[cmdk-group-heading]]:px-2\",\n \"[&_[cmdk-group-heading]]:py-1.5\",\n \"[&_[cmdk-group-heading]]:text-xs\",\n \"[&_[cmdk-group-heading]]:font-medium\",\n \"[&_[cmdk-group-heading]]:text-muted-foreground\"\n )}\n >\n {group.items.map((item) => (\n <Command.Item\n key={item.id}\n value={[item.label, ...(item.keywords ?? [])].join(\" \")}\n onSelect={() => runCommand(item)}\n className={cn(\n \"flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm cursor-pointer\",\n \"transition-colors\",\n \"aria-selected:bg-muted/60\"\n )}\n >\n <item.icon className=\"h-4 w-4 text-muted-foreground shrink-0\" />\n <span className=\"flex-1\">{item.label}</span>\n <ArrowRight className=\"h-3.5 w-3.5 text-muted-foreground/40 shrink-0\" />\n </Command.Item>\n ))}\n </Command.Group>\n </React.Fragment>\n ))}\n </Command.List>\n\n {/* Footer */}\n <div className=\"px-4 py-2.5 border-t border-border/40 flex items-center justify-between\">\n <span className=\"text-[10px] text-muted-foreground/50\">medialane</span>\n <kbd className=\"text-[10px] text-muted-foreground/50 font-mono\">⌘K</kbd>\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}\n\nexport interface NavCommandGroup {\n heading: string;\n items: NavCommand[];\n}\n\ninterface 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\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// ── Component ─────────────────────────────────────────────────────────────────\n\nexport function NavCommandMenu({ commands, trigger }: NavCommandMenuProps) {\n const [open, setOpen] = React.useState(false);\n const router = useRouter();\n const inputRef = React.useRef<HTMLInputElement>(null);\n\n React.useEffect(() => {\n if (!open) return;\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 {/* Aurora blobs — vivid intensity */}\n <motion.div\n className=\"nav-canvas-aurora\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.2 }}\n >\n <div className=\"aurora-purple animate-blob w-[60vw] h-[60vw] -top-[10vw] -left-[10vw]\"\n style={{ opacity: 0.25 }} />\n <div className=\"aurora-blue animate-blob-slow w-[50vw] h-[50vw] -top-[5vw] -right-[10vw]\"\n style={{ opacity: 0.2 }} />\n <div className=\"aurora-rose animate-blob w-[45vw] h-[45vw] bottom-[5vw] -left-[5vw]\"\n style={{ opacity: 0.15 }} />\n <div className=\"aurora-orange animate-blob-slow w-[40vw] h-[40vw] -bottom-[5vw] -right-[5vw]\"\n style={{ opacity: 0.15 }} />\n </motion.div>\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 */}\n <motion.div\n className=\"fixed inset-0 z-[101] flex items-center justify-center p-4\"\n initial={{ opacity: 0, scale: 0.97 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.97 }}\n transition={{ duration: 0.15, ease: \"easeOut\" }}\n onClick={() => setOpen(false)}\n >\n <div\n className=\"w-full max-w-lg bg-background/90 border border-border/40 rounded-2xl overflow-hidden shadow-2xl\"\n onClick={(e) => e.stopPropagation()}\n >\n <Command shouldFilter label=\"Medialane navigation\">\n {/* Search bar */}\n <div className=\"flex items-center gap-3 px-4 py-3 border-b border-border/40\">\n <Search className=\"h-4 w-4 text-muted-foreground shrink-0\" />\n <Command.Input\n ref={inputRef}\n placeholder=\"Type a command or search…\"\n className=\"flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground\"\n />\n <button\n onClick={() => setOpen(false)}\n className=\"p-1 rounded-md hover:bg-muted/50 transition-colors\"\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=\"max-h-[60vh] overflow-y-auto 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 <React.Fragment key={group.heading}>\n {i > 0 && (\n <Command.Separator className=\"my-1 h-px bg-border/40\" />\n )}\n <Command.Group\n heading={group.heading}\n className={cn(\n \"[&_[cmdk-group-heading]]:px-2\",\n \"[&_[cmdk-group-heading]]:py-1.5\",\n \"[&_[cmdk-group-heading]]:text-xs\",\n \"[&_[cmdk-group-heading]]:font-medium\",\n \"[&_[cmdk-group-heading]]:text-muted-foreground\"\n )}\n >\n {group.items.map((item) => (\n <Command.Item\n key={item.id}\n value={[item.label, ...(item.keywords ?? [])].join(\" \")}\n onSelect={() => runCommand(item)}\n className={cn(\n \"flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm cursor-pointer\",\n \"transition-colors\",\n \"aria-selected:bg-muted/60\"\n )}\n >\n <item.icon className=\"h-4 w-4 text-muted-foreground shrink-0\" />\n <span className=\"flex-1\">{item.label}</span>\n <ArrowRight className=\"h-3.5 w-3.5 text-muted-foreground/40 shrink-0\" />\n </Command.Item>\n ))}\n </Command.Group>\n </React.Fragment>\n ))}\n </Command.List>\n\n {/* Footer */}\n <div className=\"px-4 py-2.5 border-t border-border/40 flex items-center justify-between\">\n <span className=\"text-[10px] text-muted-foreground/50\">medialane</span>\n <kbd className=\"text-[10px] text-muted-foreground/50 font-mono\">⌘K</kbd>\n </div>\n </Command>\n </div>\n </motion.div>\n </>\n )}\n </AnimatePresence>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -17,6 +17,12 @@ function useNavCommandMenu() {
|
|
|
17
17
|
function NavCommandMenu({ commands, trigger }) {
|
|
18
18
|
const [open, setOpen] = React.useState(false);
|
|
19
19
|
const router = useRouter();
|
|
20
|
+
const inputRef = React.useRef(null);
|
|
21
|
+
React.useEffect(() => {
|
|
22
|
+
if (!open) return;
|
|
23
|
+
const t = setTimeout(() => inputRef.current?.focus(), 60);
|
|
24
|
+
return () => clearTimeout(t);
|
|
25
|
+
}, [open]);
|
|
20
26
|
React.useEffect(() => {
|
|
21
27
|
const onKey = (e) => {
|
|
22
28
|
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
|
@@ -106,6 +112,7 @@ function NavCommandMenu({ commands, trigger }) {
|
|
|
106
112
|
animate: { opacity: 1, scale: 1 },
|
|
107
113
|
exit: { opacity: 0, scale: 0.97 },
|
|
108
114
|
transition: { duration: 0.15, ease: "easeOut" },
|
|
115
|
+
onClick: () => setOpen(false),
|
|
109
116
|
children: /* @__PURE__ */ jsx(
|
|
110
117
|
"div",
|
|
111
118
|
{
|
|
@@ -117,6 +124,7 @@ function NavCommandMenu({ commands, trigger }) {
|
|
|
117
124
|
/* @__PURE__ */ jsx(
|
|
118
125
|
Command.Input,
|
|
119
126
|
{
|
|
127
|
+
ref: inputRef,
|
|
120
128
|
placeholder: "Type a command or search\u2026",
|
|
121
129
|
className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
122
130
|
}
|
|
@@ -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}\n\nexport interface NavCommandGroup {\n heading: string;\n items: NavCommand[];\n}\n\ninterface 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\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// ── Component ─────────────────────────────────────────────────────────────────\n\nexport function NavCommandMenu({ commands, trigger }: NavCommandMenuProps) {\n const [open, setOpen] = React.useState(false);\n const router = useRouter();\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 {/* Aurora blobs — vivid intensity */}\n <motion.div\n className=\"nav-canvas-aurora\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.2 }}\n >\n <div className=\"aurora-purple animate-blob w-[60vw] h-[60vw] -top-[10vw] -left-[10vw]\"\n style={{ opacity: 0.25 }} />\n <div className=\"aurora-blue animate-blob-slow w-[50vw] h-[50vw] -top-[5vw] -right-[10vw]\"\n style={{ opacity: 0.2 }} />\n <div className=\"aurora-rose animate-blob w-[45vw] h-[45vw] bottom-[5vw] -left-[5vw]\"\n style={{ opacity: 0.15 }} />\n <div className=\"aurora-orange animate-blob-slow w-[40vw] h-[40vw] -bottom-[5vw] -right-[5vw]\"\n style={{ opacity: 0.15 }} />\n </motion.div>\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 */}\n <motion.div\n className=\"fixed inset-0 z-[101] flex items-center justify-center p-4\"\n initial={{ opacity: 0, scale: 0.97 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.97 }}\n transition={{ duration: 0.15, ease: \"easeOut\" }}\n >\n <div\n className=\"w-full max-w-lg bg-background/90 border border-border/40 rounded-2xl overflow-hidden shadow-2xl\"\n onClick={(e) => e.stopPropagation()}\n >\n <Command shouldFilter label=\"Medialane navigation\">\n {/* Search bar */}\n <div className=\"flex items-center gap-3 px-4 py-3 border-b border-border/40\">\n <Search className=\"h-4 w-4 text-muted-foreground shrink-0\" />\n <Command.Input\n placeholder=\"Type a command or search…\"\n className=\"flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground\"\n />\n <button\n onClick={() => setOpen(false)}\n className=\"p-1 rounded-md hover:bg-muted/50 transition-colors\"\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=\"max-h-[60vh] overflow-y-auto 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 <React.Fragment key={group.heading}>\n {i > 0 && (\n <Command.Separator className=\"my-1 h-px bg-border/40\" />\n )}\n <Command.Group\n heading={group.heading}\n className={cn(\n \"[&_[cmdk-group-heading]]:px-2\",\n \"[&_[cmdk-group-heading]]:py-1.5\",\n \"[&_[cmdk-group-heading]]:text-xs\",\n \"[&_[cmdk-group-heading]]:font-medium\",\n \"[&_[cmdk-group-heading]]:text-muted-foreground\"\n )}\n >\n {group.items.map((item) => (\n <Command.Item\n key={item.id}\n value={[item.label, ...(item.keywords ?? [])].join(\" \")}\n onSelect={() => runCommand(item)}\n className={cn(\n \"flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm cursor-pointer\",\n \"transition-colors\",\n \"aria-selected:bg-muted/60\"\n )}\n >\n <item.icon className=\"h-4 w-4 text-muted-foreground shrink-0\" />\n <span className=\"flex-1\">{item.label}</span>\n <ArrowRight className=\"h-3.5 w-3.5 text-muted-foreground/40 shrink-0\" />\n </Command.Item>\n ))}\n </Command.Group>\n </React.Fragment>\n ))}\n </Command.List>\n\n {/* Footer */}\n <div className=\"px-4 py-2.5 border-t border-border/40 flex items-center justify-between\">\n <span className=\"text-[10px] text-muted-foreground/50\">medialane</span>\n <kbd className=\"text-[10px] text-muted-foreground/50 font-mono\">⌘K</kbd>\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}\n\nexport interface NavCommandGroup {\n heading: string;\n items: NavCommand[];\n}\n\ninterface 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\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// ── Component ─────────────────────────────────────────────────────────────────\n\nexport function NavCommandMenu({ commands, trigger }: NavCommandMenuProps) {\n const [open, setOpen] = React.useState(false);\n const router = useRouter();\n const inputRef = React.useRef<HTMLInputElement>(null);\n\n React.useEffect(() => {\n if (!open) return;\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 {/* Aurora blobs — vivid intensity */}\n <motion.div\n className=\"nav-canvas-aurora\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.2 }}\n >\n <div className=\"aurora-purple animate-blob w-[60vw] h-[60vw] -top-[10vw] -left-[10vw]\"\n style={{ opacity: 0.25 }} />\n <div className=\"aurora-blue animate-blob-slow w-[50vw] h-[50vw] -top-[5vw] -right-[10vw]\"\n style={{ opacity: 0.2 }} />\n <div className=\"aurora-rose animate-blob w-[45vw] h-[45vw] bottom-[5vw] -left-[5vw]\"\n style={{ opacity: 0.15 }} />\n <div className=\"aurora-orange animate-blob-slow w-[40vw] h-[40vw] -bottom-[5vw] -right-[5vw]\"\n style={{ opacity: 0.15 }} />\n </motion.div>\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 */}\n <motion.div\n className=\"fixed inset-0 z-[101] flex items-center justify-center p-4\"\n initial={{ opacity: 0, scale: 0.97 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.97 }}\n transition={{ duration: 0.15, ease: \"easeOut\" }}\n onClick={() => setOpen(false)}\n >\n <div\n className=\"w-full max-w-lg bg-background/90 border border-border/40 rounded-2xl overflow-hidden shadow-2xl\"\n onClick={(e) => e.stopPropagation()}\n >\n <Command shouldFilter label=\"Medialane navigation\">\n {/* Search bar */}\n <div className=\"flex items-center gap-3 px-4 py-3 border-b border-border/40\">\n <Search className=\"h-4 w-4 text-muted-foreground shrink-0\" />\n <Command.Input\n ref={inputRef}\n placeholder=\"Type a command or search…\"\n className=\"flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground\"\n />\n <button\n onClick={() => setOpen(false)}\n className=\"p-1 rounded-md hover:bg-muted/50 transition-colors\"\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=\"max-h-[60vh] overflow-y-auto 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 <React.Fragment key={group.heading}>\n {i > 0 && (\n <Command.Separator className=\"my-1 h-px bg-border/40\" />\n )}\n <Command.Group\n heading={group.heading}\n className={cn(\n \"[&_[cmdk-group-heading]]:px-2\",\n \"[&_[cmdk-group-heading]]:py-1.5\",\n \"[&_[cmdk-group-heading]]:text-xs\",\n \"[&_[cmdk-group-heading]]:font-medium\",\n \"[&_[cmdk-group-heading]]:text-muted-foreground\"\n )}\n >\n {group.items.map((item) => (\n <Command.Item\n key={item.id}\n value={[item.label, ...(item.keywords ?? [])].join(\" \")}\n onSelect={() => runCommand(item)}\n className={cn(\n \"flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm cursor-pointer\",\n \"transition-colors\",\n \"aria-selected:bg-muted/60\"\n )}\n >\n <item.icon className=\"h-4 w-4 text-muted-foreground shrink-0\" />\n <span className=\"flex-1\">{item.label}</span>\n <ArrowRight className=\"h-3.5 w-3.5 text-muted-foreground/40 shrink-0\" />\n </Command.Item>\n ))}\n </Command.Group>\n </React.Fragment>\n ))}\n </Command.List>\n\n {/* Footer */}\n <div className=\"px-4 py-2.5 border-t border-border/40 flex items-center justify-between\">\n <span className=\"text-[10px] text-muted-foreground/50\">medialane</span>\n <kbd className=\"text-[10px] text-muted-foreground/50 font-mono\">⌘K</kbd>\n </div>\n </Command>\n </div>\n </motion.div>\n </>\n )}\n </AnimatePresence>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|