From ea448279030a1ca846df4b59ce4ae6596ac74ace Mon Sep 17 00:00:00 2001 From: ibelick Date: Sun, 15 Feb 2026 10:50:16 +0100 Subject: [PATCH] refactor(chat): migrate session keyboard shortcuts to @tanstack/react-hotkey --- apps/webclaw/package.json | 1 + .../chat/hooks/use-session-shortcuts.ts | 55 +++++++++---------- pnpm-lock.yaml | 31 +++++++++++ 3 files changed, 57 insertions(+), 30 deletions(-) diff --git a/apps/webclaw/package.json b/apps/webclaw/package.json index fca8b00..74d3627 100644 --- a/apps/webclaw/package.json +++ b/apps/webclaw/package.json @@ -18,6 +18,7 @@ "@shikijs/langs": "^3.21.0", "@shikijs/themes": "^3.21.0", "@tailwindcss/vite": "^4.1.18", + "@tanstack/react-hotkeys": "^0.1.0", "@tanstack/react-query": "^5.84.1", "@tanstack/react-router": "^1.132.0", "@tanstack/react-router-devtools": "^1.132.0", diff --git a/apps/webclaw/src/screens/chat/hooks/use-session-shortcuts.ts b/apps/webclaw/src/screens/chat/hooks/use-session-shortcuts.ts index 58730e0..0b34ee1 100644 --- a/apps/webclaw/src/screens/chat/hooks/use-session-shortcuts.ts +++ b/apps/webclaw/src/screens/chat/hooks/use-session-shortcuts.ts @@ -1,4 +1,4 @@ -import { useEffect } from 'react' +import { useHotkey } from '@tanstack/react-hotkeys' type SessionShortcutOptions = { onNewSession: () => void @@ -9,37 +9,32 @@ function useSessionShortcuts({ onNewSession, onSearchSessions, }: SessionShortcutOptions) { - useEffect(() => { - function handleKeyDown(event: KeyboardEvent) { - if (event.defaultPrevented) return - if (event.altKey) return - if (!(event.metaKey || event.ctrlKey)) return - const target = event.target as HTMLElement | null - if (!target) return - const tag = target.tagName.toLowerCase() - if ( - tag === 'input' || - tag === 'textarea' || - tag === 'select' || - target.isContentEditable - ) { - return - } + useHotkey( + 'Mod+K', + function handleSearchHotkey(event) { + if (event.altKey || isEditableTarget(event.target)) return + event.preventDefault() + onSearchSessions() + }, + { preventDefault: true }, + ) - if (event.key.toLowerCase() === 'k' && !event.shiftKey) { - event.preventDefault() - onSearchSessions() - } + useHotkey( + 'Mod+Shift+O', + function handleNewSessionHotkey(event) { + if (event.altKey || isEditableTarget(event.target)) return + event.preventDefault() + onNewSession() + }, + { preventDefault: true }, + ) +} - if (event.key.toLowerCase() === 'o' && event.shiftKey) { - event.preventDefault() - onNewSession() - } - } - - window.addEventListener('keydown', handleKeyDown) - return () => window.removeEventListener('keydown', handleKeyDown) - }, [onNewSession, onSearchSessions]) +function isEditableTarget(target: EventTarget | null): boolean { + if (!(target instanceof HTMLElement)) return false + const tag = target.tagName.toLowerCase() + if (tag === 'input' || tag === 'textarea' || tag === 'select') return true + return target.isContentEditable } export { useSessionShortcuts } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5ceb61a..ec41d4c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,9 +60,18 @@ importers: '@hugeicons/react': specifier: ^1.1.4 version: 1.1.4(react@19.2.4) + '@shikijs/langs': + specifier: ^3.21.0 + version: 3.22.0 + '@shikijs/themes': + specifier: ^3.21.0 + version: 3.22.0 '@tailwindcss/vite': specifier: ^4.1.18 version: 4.1.18(vite@7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)) + '@tanstack/react-hotkeys': + specifier: ^0.1.0 + version: 0.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-query': specifier: ^5.84.1 version: 5.90.20(react@19.2.4) @@ -1332,9 +1341,20 @@ packages: resolution: {integrity: sha512-xyIfof8eHBuub1CkBnbKNKQXeRZC4dClhmzePHVOEel4G7lk/dW+TQ16da7CFdeNLv6u6Owf5VoBQxoo6DFTSA==} engines: {node: '>=12'} + '@tanstack/hotkeys@0.1.0': + resolution: {integrity: sha512-2ypLO3C2vKxMbCjiOfA3RzDaUIUYsQtyLc+Qwl6oKzQvWxReGlQN7DOQZSeKyX9D395d3E65e2v7Fm14BzUGew==} + engines: {node: '>=18'} + '@tanstack/query-core@5.90.20': resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} + '@tanstack/react-hotkeys@0.1.0': + resolution: {integrity: sha512-Hteo+hdPKdR2G7uAwTmScu1JUHM/f03lVihxVfR/A3XMyVJmbzi4SJieRk/kLgYKeXFRGIUx/xSI3mHeVc6DbQ==} + engines: {node: '>=18'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + '@tanstack/react-query@5.90.20': resolution: {integrity: sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==} peerDependencies: @@ -4528,8 +4548,19 @@ snapshots: '@tanstack/history@1.154.14': {} + '@tanstack/hotkeys@0.1.0': + dependencies: + '@tanstack/store': 0.8.0 + '@tanstack/query-core@5.90.20': {} + '@tanstack/react-hotkeys@0.1.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@tanstack/hotkeys': 0.1.0 + '@tanstack/react-store': 0.8.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + '@tanstack/react-query@5.90.20(react@19.2.4)': dependencies: '@tanstack/query-core': 5.90.20