11import { Link , Outlet , useNavigate } from '@tanstack/react-router' ;
2- import { ChevronDown , Menu } from 'lucide-react' ;
3- import { useEffect , useMemo } from 'react' ;
2+ import { ChevronDown , FolderOpen , Menu , Plus , Search , Settings } from 'lucide-react' ;
3+ import { useEffect , useMemo , useState } from 'react' ;
44import { signOut , useAuth } from '@/features/auth' ;
55import { useUserBuckets } from '@/features/settings' ;
66import { CAPTURE_NAV , HEADER_NAV , NAV_LINKS } from '@/lib/navigation' ;
77import { NavLink } from '../NavLink' ;
8+ import { Button } from '../ui/button' ;
9+ import { CommandDialog , CommandEmpty , CommandGroup , CommandInput , CommandItem , CommandList , CommandSeparator } from '../ui/command' ;
810import {
911 DropdownMenu ,
1012 DropdownMenuContent ,
@@ -13,11 +15,13 @@ import {
1315 DropdownMenuSeparator ,
1416 DropdownMenuTrigger ,
1517} from '../ui/dropdown-menu' ;
18+ import { Kbd } from '../ui/kbd' ;
1619
1720export function ProtectedLayout ( ) {
1821 // Use AuthProvider (reactive); router context isn't reactive when RouterProvider context changes.
1922 const { data : session , isPending } = useAuth ( ) ;
2023 const navigate = useNavigate ( ) ;
24+ const [ commandOpen , setCommandOpen ] = useState ( false ) ;
2125
2226 const isAuthenticated = ! ! session ;
2327 const email = session ?. user ?. email ?? null ;
@@ -40,6 +44,18 @@ export function ProtectedLayout() {
4044 }
4145 } , [ isPending , isAuthenticated , navigate ] ) ;
4246
47+ // Global Cmd+K keyboard shortcut
48+ useEffect ( ( ) => {
49+ const handleKeyDown = ( e : KeyboardEvent ) => {
50+ if ( ( e . metaKey || e . ctrlKey ) && e . key === 'k' ) {
51+ e . preventDefault ( ) ;
52+ setCommandOpen ( ( open ) => ! open ) ;
53+ }
54+ } ;
55+ document . addEventListener ( 'keydown' , handleKeyDown ) ;
56+ return ( ) => document . removeEventListener ( 'keydown' , handleKeyDown ) ;
57+ } , [ ] ) ;
58+
4359 // Show loading while auth is pending or redirecting
4460 if ( isPending || ! isAuthenticated ) {
4561 return (
@@ -132,15 +148,16 @@ export function ProtectedLayout() {
132148 </ div >
133149
134150 < div className = "flex items-center gap-3" >
151+ < Button variant = "outline" size = "sm" onClick = { ( ) => setCommandOpen ( true ) } className = "hidden sm:flex items-center gap-2" >
152+ < Search className = "size-3.5" />
153+ < span > Search</ span >
154+ < Kbd > ⌘K</ Kbd >
155+ </ Button >
135156 < NavLink to = { CAPTURE_NAV . to } > { CAPTURE_NAV . label } </ NavLink >
136157 { shortEmail && < span className = "hidden sm:inline text-xs text-zinc-500" > { shortEmail } </ span > }
137- < button
138- type = "button"
139- onClick = { ( ) => signOut ( ) }
140- className = "rounded-md bg-zinc-900 px-3 py-2 text-sm text-zinc-200 hover:bg-zinc-800 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-200/40"
141- >
158+ < Button variant = "secondary" size = "sm" onClick = { ( ) => signOut ( ) } >
142159 Sign out
143- </ button >
160+ </ Button >
144161 </ div >
145162 </ div >
146163 </ header >
@@ -150,6 +167,62 @@ export function ProtectedLayout() {
150167 < Outlet />
151168 </ div >
152169 </ main >
170+
171+ { /* Command palette (Cmd+K) */ }
172+ < CommandDialog open = { commandOpen } onOpenChange = { setCommandOpen } >
173+ < CommandInput placeholder = "Type to search..." />
174+ < CommandList >
175+ < CommandEmpty > No results found.</ CommandEmpty >
176+ < CommandGroup heading = "Navigation" >
177+ < CommandItem
178+ onSelect = { ( ) => {
179+ navigate ( { to : '/' } ) ;
180+ setCommandOpen ( false ) ;
181+ } }
182+ >
183+ < Plus className = "mr-2 size-4" />
184+ Capture
185+ </ CommandItem >
186+ < CommandItem
187+ onSelect = { ( ) => {
188+ navigate ( { to : '/search' } ) ;
189+ setCommandOpen ( false ) ;
190+ } }
191+ >
192+ < Search className = "mr-2 size-4" />
193+ Search
194+ </ CommandItem >
195+ < CommandItem
196+ onSelect = { ( ) => {
197+ navigate ( { to : '/settings' } ) ;
198+ setCommandOpen ( false ) ;
199+ } }
200+ >
201+ < Settings className = "mr-2 size-4" />
202+ Settings
203+ </ CommandItem >
204+ </ CommandGroup >
205+ { buckets . length > 0 && (
206+ < >
207+ < CommandSeparator />
208+ < CommandGroup heading = "Buckets" >
209+ { buckets . map ( ( bucket ) => (
210+ < CommandItem
211+ key = { bucket . id }
212+ onSelect = { ( ) => {
213+ navigate ( { to : '/bucket/$slug' , params : { slug : bucket . slug } } ) ;
214+ setCommandOpen ( false ) ;
215+ } }
216+ >
217+ < FolderOpen className = "mr-2 size-4" />
218+ { bucket . name }
219+ </ CommandItem >
220+ ) ) }
221+ </ CommandGroup >
222+ </ >
223+ ) }
224+ </ CommandList >
225+ </ CommandDialog >
153226 </ div >
154227 ) ;
155228}
0 commit comments