11import { Keyboard , X } from "lucide-react" ;
22import React , { useEffect , useRef } from "react" ;
3- import { useLocation } from "react-router-dom" ;
3+ import { useLocation , useNavigate } from "react-router-dom" ;
44
5+ import { useTheme } from "@/providers/ThemeProvider" ;
56import { cn } from "@/utils" ;
67
78interface KeyboardShortcutsPanelProps {
@@ -14,6 +15,8 @@ export function KeyboardShortcutsPanel({
1415 onClose,
1516} : KeyboardShortcutsPanelProps ) {
1617 const location = useLocation ( ) ;
18+ const navigate = useNavigate ( ) ;
19+ const { toggle : toggleTheme } = useTheme ( ) ;
1720 const modalRef = useRef < HTMLDivElement | null > ( null ) ;
1821
1922 // Add click-outside functionality
@@ -44,6 +47,160 @@ export function KeyboardShortcutsPanel({
4447 } ;
4548 } , [ isOpen , onClose ] ) ;
4649
50+ // Handle shortcut clicks
51+ const handleShortcutClick = ( shortcutKey : string ) => {
52+ // Close the panel first
53+ onClose ( ) ;
54+
55+ // Determine the action based on the shortcut key
56+ const isVisualizerPage = location . pathname . includes ( "/viz/" ) ;
57+
58+ switch ( shortcutKey ) {
59+ // Navigation shortcuts
60+ case "H" :
61+ navigate ( "/" ) ;
62+ break ;
63+ case "V" :
64+ if ( ! isVisualizerPage ) {
65+ navigate ( "/viz/sorting/bubble-sort" ) ;
66+ }
67+ break ;
68+ case "Esc" :
69+ // Already closed the panel above
70+ break ;
71+
72+ // Search shortcuts
73+ case "/" :
74+ case "Ctrl+K" : {
75+ const searchInput = document . querySelector (
76+ 'input[type="search"], input[placeholder*="search" i]'
77+ ) as HTMLInputElement ;
78+ if ( searchInput ) {
79+ searchInput . focus ( ) ;
80+ }
81+ break ;
82+ }
83+ case "Enter" : {
84+ // Trigger search with current query
85+ const searchBtn = document . querySelector (
86+ "[data-search-button]"
87+ ) as HTMLButtonElement ;
88+ if ( searchBtn ) {
89+ searchBtn . click ( ) ;
90+ }
91+ break ;
92+ }
93+
94+ // Theme and interface
95+ case "T" :
96+ toggleTheme ( ) ;
97+ break ;
98+ case "F" :
99+ if ( document . fullscreenElement ) {
100+ document . exitFullscreen ( ) ;
101+ } else {
102+ document . documentElement . requestFullscreen ( ) ;
103+ }
104+ break ;
105+ case "?" :
106+ // Already handled by closing the panel
107+ break ;
108+
109+ // Visualizer controls (only work on visualizer pages)
110+ case "Space" :
111+ if ( isVisualizerPage ) {
112+ document . dispatchEvent ( new CustomEvent ( "algolens:play-pause" ) ) ;
113+ }
114+ break ;
115+ case "→" :
116+ if ( isVisualizerPage ) {
117+ document . dispatchEvent ( new CustomEvent ( "algolens:step-forward" ) ) ;
118+ }
119+ break ;
120+ case "←" :
121+ if ( isVisualizerPage ) {
122+ document . dispatchEvent ( new CustomEvent ( "algolens:step-backward" ) ) ;
123+ }
124+ break ;
125+ case "↑" :
126+ if ( isVisualizerPage ) {
127+ document . dispatchEvent ( new CustomEvent ( "algolens:speed-up" ) ) ;
128+ }
129+ break ;
130+ case "↓" :
131+ if ( isVisualizerPage ) {
132+ document . dispatchEvent ( new CustomEvent ( "algolens:speed-down" ) ) ;
133+ }
134+ break ;
135+ case "R" :
136+ if ( isVisualizerPage ) {
137+ document . dispatchEvent ( new CustomEvent ( "algolens:reset" ) ) ;
138+ }
139+ break ;
140+ case "Ctrl+Home" :
141+ if ( isVisualizerPage ) {
142+ document . dispatchEvent ( new CustomEvent ( "algolens:go-to-start" ) ) ;
143+ }
144+ break ;
145+ case "Ctrl+End" :
146+ if ( isVisualizerPage ) {
147+ document . dispatchEvent ( new CustomEvent ( "algolens:go-to-end" ) ) ;
148+ }
149+ break ;
150+
151+ // Speed presets
152+ case "1" :
153+ if ( isVisualizerPage ) {
154+ document . dispatchEvent (
155+ new CustomEvent ( "algolens:set-speed" , { detail : 0.25 } )
156+ ) ;
157+ }
158+ break ;
159+ case "2" :
160+ if ( isVisualizerPage ) {
161+ document . dispatchEvent (
162+ new CustomEvent ( "algolens:set-speed" , { detail : 0.5 } )
163+ ) ;
164+ }
165+ break ;
166+ case "3" :
167+ if ( isVisualizerPage ) {
168+ document . dispatchEvent (
169+ new CustomEvent ( "algolens:set-speed" , { detail : 1 } )
170+ ) ;
171+ }
172+ break ;
173+ case "4" :
174+ if ( isVisualizerPage ) {
175+ document . dispatchEvent (
176+ new CustomEvent ( "algolens:set-speed" , { detail : 2 } )
177+ ) ;
178+ }
179+ break ;
180+ case "5" :
181+ if ( isVisualizerPage ) {
182+ document . dispatchEvent (
183+ new CustomEvent ( "algolens:set-speed" , { detail : 4 } )
184+ ) ;
185+ }
186+ break ;
187+
188+ // Filter management shortcuts (home page)
189+ case "Ctrl+Shift+C" :
190+ document . dispatchEvent ( new CustomEvent ( "algolens:clear-filters" ) ) ;
191+ break ;
192+ case "Ctrl+Shift+F" :
193+ document . dispatchEvent ( new CustomEvent ( "algolens:toggle-featured" ) ) ;
194+ break ;
195+ case "Ctrl+Shift+E" :
196+ document . dispatchEvent ( new CustomEvent ( "algolens:toggle-filter-bar" ) ) ;
197+ break ;
198+
199+ default :
200+ console . log ( `Shortcut ${ shortcutKey } not implemented yet` ) ;
201+ }
202+ } ;
203+
47204 if ( ! isOpen ) return null ;
48205
49206 // Get context-aware shortcuts based on current page
@@ -251,14 +408,16 @@ export function KeyboardShortcutsPanel({
251408 ) => (
252409 < div
253410 key = { index }
411+ onClick = { ( ) => handleShortcutClick ( shortcut . key ) }
254412 className = { cn (
255- "group flex items-center justify-between rounded-xl p-4 transition-all duration-200" ,
413+ "group flex cursor-pointer items-center justify-between rounded-xl p-4 transition-all duration-200" ,
256414 "bg-gradient-to-r from-slate-50 to-slate-100/50 dark:from-slate-800/50 dark:to-slate-800/20" ,
257415 "border border-slate-200/60 dark:border-slate-700/60" ,
258416 "hover:from-primary-50 hover:to-secondary-50 dark:hover:from-primary-900/20 dark:hover:to-secondary-900/20" ,
259417 "hover:border-primary-200 dark:hover:border-primary-700" ,
260418 "hover:scale-[1.02] hover:shadow-md active:scale-[0.98]"
261419 ) }
420+ title = { `Click to execute: ${ shortcut . description } ` }
262421 >
263422 < div className = "flex items-center gap-3" >
264423 { shortcut . icon && (
0 commit comments