Sunflower UI
Search
Copy Code
Light Mode
command-menu.tsx
prompt.md
styles.css
Collapse
Reset
Copy Code
Download
Run
1
'use client';
2
3
import * as React from 'react';
4
import { Command } from 'cmdk';
5
import { Search, ChevronRight, ArrowRight, ArrowLeft, Link, LogIn, Palette, VolumeX, LayoutGrid, Command as CommandIcon } from 'lucide-react';
6
import { motion, AnimatePresence } from 'framer-motion';
7
import { useRouter } from 'next/navigation';
8
import { cn } from '@/lib/utils';
9
10
export function CommandMenu() {
11
const [open, setOpen] = React.useState(false);
12
const [view, setView] = React.useState<'home' | 'search'>('home');
13
const [inputValue, setInputValue] = React.useState('');
14
const router = useRouter();
15
16
// Toggle the menu when ⌘K is pressed
17
React.useEffect(() => {
18
const down = (e: KeyboardEvent) => {
19
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
20
e.preventDefault();
21
setOpen((open) => !open);
22
}
23
};
24
document.addEventListener('keydown', down);
25
return () => document.removeEventListener('keydown', down);
26
}, []);
27
28
const resetAndClose = () => {
29
setOpen(false);
30
setTimeout(() => {
31
setView('home');
32
setInputValue('');
33
}, 200);
34
};
35
36
const handleKeyDown = (e: React.KeyboardEvent) => {
37
if (e.key === 'Backspace' && !inputValue && view === 'search') {
38
e.preventDefault();
39
setView('home');
40
}
41
};
42
43
return (
44
<div className="flex flex-col items-center justify-center w-full min-h-[500px]">
45
<button
46
onClick={() => setOpen(true)}
47
className="group flex items-center gap-3 px-4 py-2.5 text-[14px] font-medium text-black dark:text-white bg-white dark:bg-[#111111] border border-black/10 dark:border-white/10 rounded-xl hover:bg-black/5 dark:hover:bg-white/10 transition-colors shadow-sm"
48
>
49
<span>Open command menu</span>
50
<kbd className="flex items-center gap-1 px-1.5 py-0.5 text-[11.5px] font-sans font-semibold bg-black/5 dark:bg-white/10 rounded-md text-black/60 dark:text-white/60 border border-black/5 dark:border-white/5 group-hover:bg-black/10 dark:group-hover:bg-white/20 transition-colors">
51
<CommandIcon size={12} /> K
52
</kbd>
53
</button>
54
55
<AnimatePresence>
56
{open && (
57
<motion.div
58
className="fixed inset-0 z-50 flex items-start justify-center pt-[15vh] sm:pt-[20vh]"
59
initial={{ opacity: 0 }}
60
animate={{ opacity: 1 }}
61
exit={{ opacity: 0 }}
62
transition={{ duration: 0.15 }}
63
>
64
{/* Backdrop */}
65
<div
66
className="absolute inset-0 bg-black/20 dark:bg-black/60 backdrop-blur-sm"
67
onClick={resetAndClose}
68
/>
69
70
{/* Command Dialog Container */}
71
<motion.div
72
initial={{ opacity: 0, scale: 0.95, y: 10 }}
73
animate={{ opacity: 1, scale: 1, y: 0 }}
74
exit={{ opacity: 0, scale: 0.95, y: 10 }}
75
transition={{ type: "spring", stiffness: 400, damping: 30 }}
76
className="relative w-full max-w-[640px] mx-4 rounded-xl border border-black/5 dark:border-white/5 dark:border-t-white/10 shadow-[0_16px_64px_rgba(0,0,0,0.1)] dark:shadow-none overflow-hidden bg-white/70 dark:bg-black/60 backdrop-blur-[16px]"
77
>
78
<Command
79
className="w-full flex flex-col font-matter"
80
onKeyDown={handleKeyDown}
81
shouldFilter={true}
82
>
83
{/* Header */}
84
<div className="flex items-start justify-between px-4 py-4 border-b border-black/5 dark:border-white/5">
85
<div className="flex flex-col gap-3 w-full">
86
{/* Breadcrumbs */}
87
<div className="flex items-center gap-1.5">
88
<div className="px-2 py-0.5 text-[12px] font-medium bg-black/5 dark:bg-white/10 text-black/70 dark:text-white/70 rounded-md">
89
Home
90
</div>
91
{view === 'search' && (
92
<div className="px-2 py-0.5 text-[12px] font-medium bg-black/5 dark:bg-white/10 text-black/70 dark:text-white/70 rounded-md">
93
Search
94
</div>
95
)}
96
</div>
97
{/* Input */}
98
<Command.Input
99
autoFocus
100
value={inputValue}
101
onValueChange={setInputValue}
102
placeholder="Type a command or search..."
103
className="w-full bg-transparent text-[15px] outline-none border-none placeholder:text-black/40 dark:placeholder:text-white/40 text-black dark:text-white font-medium"
104
/>
105
</div>
106
<kbd className="hidden sm:flex items-center px-2 py-1 text-[11px] font-medium bg-black/5 dark:bg-white/10 rounded text-black/50 dark:text-white/50 border border-black/5 dark:border-white/5 mt-1">
107
Esc
108
</kbd>
109
</div>
110
111
{/* List Area */}
112
<div className="max-h-[350px] overflow-y-auto p-2 overscroll-contain">
113
<Command.List className="w-full">
114
<Command.Empty className="py-6 text-center text-sm text-black/50 dark:text-white/50">
115
No results found.
116
</Command.Empty>
117
118
{view === 'home' && (
119
<>
120
<Command.Group heading={<span className="text-[11px] font-semibold text-black/40 dark:text-white/40 px-2 py-2 block uppercase tracking-wider">Components</span>}>
121
<Command.Item
122
onSelect={() => setView('search')}
123
className="flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer text-[14px] text-black/80 dark:text-white/80 hover:bg-black/5 dark:hover:bg-white/10 aria-selected:bg-black/5 dark:aria-selected:bg-white/10 aria-selected:text-black dark:aria-selected:text-white transition-colors group"
124
>
125
<Search size={16} className="text-black/40 dark:text-white/40 group-aria-selected:text-black/70 dark:group-aria-selected:text-white/70" />
126
<span className="flex-1 font-medium">Search components...</span>
127
<ChevronRight size={16} className="text-black/30 dark:text-white/30" />
128
</Command.Item>
129
<Command.Item className="flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer text-[14px] text-black/80 dark:text-white/80 hover:bg-black/5 dark:hover:bg-white/10 aria-selected:bg-black/5 dark:aria-selected:bg-white/10 aria-selected:text-black dark:aria-selected:text-white transition-colors group">
130
<ArrowRight size={16} className="text-black/40 dark:text-white/40 group-aria-selected:text-black/70 dark:group-aria-selected:text-white/70" />
131
<span className="flex-1 font-medium">Next component</span>
132
</Command.Item>
133
<Command.Item className="flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer text-[14px] text-black/80 dark:text-white/80 hover:bg-black/5 dark:hover:bg-white/10 aria-selected:bg-black/5 dark:aria-selected:bg-white/10 aria-selected:text-black dark:aria-selected:text-white transition-colors group">
134
<ArrowLeft size={16} className="text-black/40 dark:text-white/40 group-aria-selected:text-black/70 dark:group-aria-selected:text-white/70" />
135
<span className="flex-1 font-medium">Previous component</span>
136
</Command.Item>
137
<Command.Item className="flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer text-[14px] text-black/80 dark:text-white/80 hover:bg-black/5 dark:hover:bg-white/10 aria-selected:bg-black/5 dark:aria-selected:bg-white/10 aria-selected:text-black dark:aria-selected:text-white transition-colors group">
138
<Link size={16} className="text-black/40 dark:text-white/40 group-aria-selected:text-black/70 dark:group-aria-selected:text-white/70" />
139
<span className="flex-1 font-medium">Copy link to this page</span>
140
</Command.Item>
141
</Command.Group>
142
143
<Command.Group heading={<span className="text-[11px] font-semibold text-black/40 dark:text-white/40 px-2 py-2 block uppercase tracking-wider mt-2">Settings</span>}>
144
<Command.Item className="flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer text-[14px] text-black/80 dark:text-white/80 hover:bg-black/5 dark:hover:bg-white/10 aria-selected:bg-black/5 dark:aria-selected:bg-white/10 aria-selected:text-black dark:aria-selected:text-white transition-colors group">
145
<LogIn size={16} className="text-black/40 dark:text-white/40 group-aria-selected:text-black/70 dark:group-aria-selected:text-white/70" />
146
<span className="flex-1 font-medium">Sign in</span>
147
</Command.Item>
148
<Command.Item className="flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer text-[14px] text-black/80 dark:text-white/80 hover:bg-black/5 dark:hover:bg-white/10 aria-selected:bg-black/5 dark:aria-selected:bg-white/10 aria-selected:text-black dark:aria-selected:text-white transition-colors group">
149
<Palette size={16} className="text-black/40 dark:text-white/40 group-aria-selected:text-black/70 dark:group-aria-selected:text-white/70" />
150
<span className="flex-1 font-medium">Theme</span>
151
<ChevronRight size={16} className="text-black/30 dark:text-white/30" />
152
</Command.Item>
153
<Command.Item className="flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer text-[14px] text-black/80 dark:text-white/80 hover:bg-black/5 dark:hover:bg-white/10 aria-selected:bg-black/5 dark:aria-selected:bg-white/10 aria-selected:text-black dark:aria-selected:text-white transition-colors group">
154
<VolumeX size={16} className="text-black/40 dark:text-white/40 group-aria-selected:text-black/70 dark:group-aria-selected:text-white/70" />
155
<span className="flex-1 font-medium">Mute interface sounds</span>
156
</Command.Item>
157
</Command.Group>
158
</>
159
)}
160
161
{view === 'search' && (
162
<Command.Group>
163
{[
164
{ name: 'Accordion', id: 'accordion' },
165
{ name: 'Advertisement', id: 'advertisement' },
166
{ name: 'AI Editor', id: 'ai-editor' },
167
{ name: 'Alert', id: 'alert' },
168
{ name: 'Aspect Ratio', id: 'aspect-ratio' },
169
{ name: 'Avatar', id: 'avatar' },
170
{ name: 'Badge', id: 'badge' },
171
{ name: 'Breadcrumb', id: 'breadcrumb' },
172
{ name: 'Button', id: 'button' },
173
{ name: 'Carousel', id: 'carousel' },
174
{ name: 'Command Menu', id: 'command-menu' }
175
].map(comp => (
176
<Command.Item
177
key={comp.id}
178
value={comp.name}
179
onSelect={() => {
180
resetAndClose();
181
router.push(`/?name=${comp.id}`);
182
}}
183
className="flex items-center gap-3 px-3 py-2.5 rounded-lg cursor-pointer text-[14px] text-black/80 dark:text-white/80 hover:bg-black/5 dark:hover:bg-white/10 aria-selected:bg-black/5 dark:aria-selected:bg-white/10 aria-selected:text-black dark:aria-selected:text-white transition-colors group"
184
>
185
<LayoutGrid size={16} className="text-black/40 dark:text-white/40 group-aria-selected:text-black/70 dark:group-aria-selected:text-white/70" />
186
<span className="font-medium">{comp.name}</span>
187
</Command.Item>
188
))}
189
</Command.Group>
190
)}
191
</Command.List>
192
</div>
193
</Command>
194
</motion.div>
195
</motion.div>
196
)}
197
</AnimatePresence>
198
</div>
199
);
200
}
201