@@ -38,6 +38,57 @@ const CATEGORIES: KeybindCategory[] = [
3838 } ,
3939]
4040
41+ type GestureEntry = {
42+ gesture : string
43+ action : string
44+ }
45+
46+ type GestureCategory = {
47+ title : string
48+ items : GestureEntry [ ]
49+ }
50+
51+ const TOUCH_GESTURES : GestureCategory [ ] = [
52+ {
53+ title : "Navigation" ,
54+ items : [
55+ { gesture : "← swipe →" , action : "Switch workspace" } ,
56+ { gesture : "Tap 1–6" , action : "Go to workspace" } ,
57+ ] ,
58+ } ,
59+ {
60+ title : "Windows" ,
61+ items : [
62+ { gesture : "Tap window" , action : "Focus" } ,
63+ { gesture : "✕ close" , action : "Close window" } ,
64+ { gesture : "Drag header" , action : "Rearrange windows" } ,
65+ ] ,
66+ } ,
67+ {
68+ title : "Overlays" ,
69+ items : [
70+ { gesture : "Tap outside" , action : "Close" } ,
71+ ] ,
72+ } ,
73+ ]
74+
75+ function useMediaQuery ( query : string ) : boolean {
76+ const [ matches , setMatches ] = React . useState ( ( ) => {
77+ if ( typeof window === "undefined" ) return false
78+ return window . matchMedia ( query ) . matches
79+ } )
80+
81+ React . useEffect ( ( ) => {
82+ const mq = window . matchMedia ( query )
83+ const handler = ( e : MediaQueryListEvent ) => setMatches ( e . matches )
84+ setMatches ( mq . matches )
85+ mq . addEventListener ( "change" , handler )
86+ return ( ) => mq . removeEventListener ( "change" , handler )
87+ } , [ query ] )
88+
89+ return matches
90+ }
91+
4192function useFocusTrap (
4293 containerRef : React . RefObject < HTMLElement | null > ,
4394 isActive : boolean ,
@@ -76,6 +127,7 @@ function useFocusTrap(
76127export function KeybindHelp ( ) {
77128 const { state, dispatch } = useWorkspace ( )
78129 const containerRef = React . useRef < HTMLDivElement > ( null )
130+ const isMobile = useMediaQuery ( "(max-width: 767px)" )
79131
80132 React . useEffect ( ( ) => {
81133 if ( state . helpOpen ) {
@@ -108,9 +160,9 @@ export function KeybindHelp() {
108160 ref = { containerRef }
109161 role = "dialog"
110162 aria-modal = "true"
111- aria-label = " Keybind reference"
163+ aria-label = { isMobile ? "Touch gestures" : " Keybind reference"}
112164 tabIndex = { - 1 }
113- className = "fixed inset-0 z-200 flex items-start justify-center pt-[12vh] outline-none"
165+ className = "fixed inset-0 z-200 flex items-start justify-center pt-[12vh] max-md:pt-0 max-md:items-end max-md:pb-0 outline-none"
114166 style = { {
115167 background : "rgba(26,21,16,0.70)" ,
116168 backdropFilter : "blur(12px)" ,
@@ -121,12 +173,20 @@ export function KeybindHelp() {
121173 } }
122174 >
123175 < div
124- className = "w-full max-w-[520px] overflow-hidden rounded-[12px] shadow-2xl"
176+ className = "w-full max-w-[520px] max-md:max-w-full max-md:rounded-b-none overflow-hidden rounded-[12px] shadow-2xl max-md:max-h-[85vh] max-md:overflow-y-auto "
125177 style = { {
126178 background : "var(--bg-overlay)" ,
127179 border : "1px solid rgba(184,127,255,0.15)" ,
128180 } }
129181 >
182+ { /* Drag handle for mobile swipe-down */ }
183+ < div className = "hidden max-md:flex justify-center pt-2 pb-0" >
184+ < div
185+ className = "h-1 w-10 rounded-full"
186+ style = { { background : "rgba(245,230,200,0.2)" } }
187+ />
188+ </ div >
189+
130190 < div
131191 className = "flex items-center gap-3 px-5 py-4"
132192 style = { {
@@ -146,55 +206,96 @@ export function KeybindHelp() {
146206 className = "text-sm font-medium"
147207 style = { { color : "var(--text-primary)" } }
148208 >
149- Keybinds
209+ { isMobile ? "Touch Gestures" : " Keybinds" }
150210 </ span >
151211 </ div >
152212
153- < div className = "max-h-[50vh] overflow-y-auto px-5 py-3" >
154- { CATEGORIES . map ( ( cat ) => (
155- < div key = { cat . title } className = "mb-4 last:mb-0" >
156- < div
157- className = "mb-2 text-[11px] font-semibold uppercase tracking-wider"
158- style = { { color : "var(--neon-violet)" } }
159- >
160- { cat . title }
161- </ div >
162-
163- { cat . binds . map ( ( bind ) => (
213+ { /* ─── Mobile: touch gestures ─── */ }
214+ { isMobile ? (
215+ < div className = "max-h-[55vh] overflow-y-auto px-5 py-3 overflow-scroll-touch" >
216+ { TOUCH_GESTURES . map ( ( cat ) => (
217+ < div key = { cat . title } className = "mb-5 last:mb-0" >
164218 < div
165- key = { bind . keys }
166- className = "flex items-center justify-between rounded-md px-3 py-2 text-sm"
167- style = { {
168- borderBottom : "1px solid rgba(245,230,200,0.04)" ,
169- } }
219+ className = "mb-2 text-[11px] font-semibold uppercase tracking-wider"
220+ style = { { color : "var(--neon-violet)" } }
170221 >
171- < span style = { { color : "var(--text-subtle)" } } >
172- { bind . action }
173- </ span >
174- < kbd
175- className = "rounded-md px-2.5 py-1 text-xs font-mono tracking-wide"
222+ { cat . title }
223+ </ div >
224+
225+ { cat . items . map ( ( item ) => (
226+ < div
227+ key = { item . gesture }
228+ className = "flex items-center justify-between rounded-md px-3 py-2.5 text-sm"
176229 style = { {
177- background : "rgba(184,127,255,0.08)" ,
178- color : "var(--neon-teal)" ,
179- border : "1px solid rgba(0,229,200,0.12)" ,
230+ borderBottom : "1px solid rgba(245,230,200,0.04)" ,
180231 } }
181232 >
182- { bind . keys }
183- </ kbd >
233+ < span style = { { color : "var(--text-subtle)" } } >
234+ { item . action }
235+ </ span >
236+ < span
237+ className = "rounded-md px-2.5 py-1 text-xs font-mono tracking-wide whitespace-nowrap"
238+ style = { {
239+ background : "rgba(184,127,255,0.08)" ,
240+ color : "var(--neon-teal)" ,
241+ border : "1px solid rgba(0,229,200,0.12)" ,
242+ } }
243+ >
244+ { item . gesture }
245+ </ span >
246+ </ div >
247+ ) ) }
248+ </ div >
249+ ) ) }
250+ </ div >
251+ ) : (
252+ /* ─── Desktop: keybinds ─── */
253+ < div className = "max-h-[50vh] overflow-y-auto px-5 py-3" >
254+ { CATEGORIES . map ( ( cat ) => (
255+ < div key = { cat . title } className = "mb-4 last:mb-0" >
256+ < div
257+ className = "mb-2 text-[11px] font-semibold uppercase tracking-wider"
258+ style = { { color : "var(--neon-violet)" } }
259+ >
260+ { cat . title }
184261 </ div >
185- ) ) }
186- </ div >
187- ) ) }
188- </ div >
262+
263+ { cat . binds . map ( ( bind ) => (
264+ < div
265+ key = { bind . keys }
266+ className = "flex items-center justify-between rounded-md px-3 py-2 text-sm"
267+ style = { {
268+ borderBottom : "1px solid rgba(245,230,200,0.04)" ,
269+ } }
270+ >
271+ < span style = { { color : "var(--text-subtle)" } } >
272+ { bind . action }
273+ </ span >
274+ < kbd
275+ className = "rounded-md px-2.5 py-1 text-xs font-mono tracking-wide"
276+ style = { {
277+ background : "rgba(184,127,255,0.08)" ,
278+ color : "var(--neon-teal)" ,
279+ border : "1px solid rgba(0,229,200,0.12)" ,
280+ } }
281+ >
282+ { bind . keys }
283+ </ kbd >
284+ </ div >
285+ ) ) }
286+ </ div >
287+ ) ) }
288+ </ div >
289+ ) }
189290
190291 < div
191- className = "flex items-center gap-4 px-5 py-2.5 text-[11px]"
292+ className = "flex items-center gap-4 px-5 py-2.5 text-[11px] max-md:pb-[calc(0.625rem+56px)] "
192293 style = { {
193294 color : "var(--text-subtle)" ,
194295 borderTop : "1px solid rgba(184,127,255,0.08)" ,
195296 } }
196297 >
197- < span > Esc close</ span >
298+ < span > { isMobile ? "Tap outside or swipe down" : " Esc" } close</ span >
198299 </ div >
199300 </ div >
200301 </ div >
0 commit comments