Skip to content

Commit 45ef60d

Browse files
committed
refactor: replace custom useKeyboardShortcuts hook with direct react-hotkeys-hook usage and consolidate navigation shortcuts
1 parent 7280f34 commit 45ef60d

4 files changed

Lines changed: 19 additions & 122 deletions

File tree

apps/web/app/components/layout/AppLayout.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useCallback, useMemo, useEffect } from "react";
1+
import { useState, useCallback, useMemo } from "react";
22
import { Outlet, isRouteErrorResponse } from "react-router";
33
import { PlusIcon } from "lucide-react";
44
import { Button, cn } from "@kreozalabs/kei-ui";
@@ -12,8 +12,8 @@ import { useSettings } from "@/providers/SettingsContext";
1212
import { MobileFABProvider, useMobileFAB } from "@/components/MobileFAB";
1313
import { HeaderPortalContext } from "./HeaderPortalContext";
1414
import { DbSyncStatus } from "./DbSyncStatus";
15-
import { useKeyboardShortcuts } from "@/hooks/useKeyboardShortcuts";
1615
import { useFullscreen } from "@/hooks/useFullscreen";
16+
import { useHotkeys } from "react-hotkeys-hook";
1717

1818
export interface AppLayoutContext {
1919
isSidebarOpen: boolean;
@@ -39,10 +39,8 @@ function AppLayoutContent({ error }: { error?: unknown }) {
3939
const toggleSidebar = useCallback(() => setIsSidebarOpen((prev) => !prev), []);
4040
const { toggleFullscreen } = useFullscreen();
4141

42-
useKeyboardShortcuts([
43-
{ key: "b", ctrlOrMeta: true, handler: toggleSidebar, description: "Toggle Sidebar" },
44-
{ key: "f", handler: toggleFullscreen, description: "Toggle Fullscreen" },
45-
]);
42+
useHotkeys("mod+b", toggleSidebar, { preventDefault: true });
43+
useHotkeys("f", toggleFullscreen, { preventDefault: true });
4644

4745
const contextValue: AppLayoutContext = useMemo(
4846
() => ({
Lines changed: 1 addition & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,93 +1 @@
1-
import { useMemo } from "react";
2-
import { useHotkeys } from "react-hotkeys-hook";
3-
4-
interface ShortcutConfig {
5-
/**
6-
* The key to trigger the shortcut (e.g., 'b', 'k', 'Enter').
7-
* Case-insensitive. Space-separated for sequences.
8-
*/
9-
key: string;
10-
/**
11-
* Whether the Ctrl key (Windows/Linux) or Command key (Mac) must be pressed.
12-
*/
13-
ctrlOrMeta?: boolean;
14-
/**
15-
* Whether the Alt key must be pressed.
16-
*/
17-
alt?: boolean;
18-
/**
19-
* Whether the Shift key must be pressed.
20-
*/
21-
shift?: boolean;
22-
/**
23-
* Callback when the shortcut is triggered.
24-
*/
25-
handler: (event: KeyboardEvent) => void;
26-
/**
27-
* Optional description for documentation or UI hints.
28-
*/
29-
description?: string;
30-
/**
31-
* Whether to allow triggering even when focused on an input.
32-
* Default is false.
33-
*/
34-
allowInInputs?: boolean;
35-
}
36-
37-
function getHotkeyValue(shortcut: ShortcutConfig): string {
38-
const parts: string[] = [];
39-
if (shortcut.ctrlOrMeta) {
40-
parts.push("mod");
41-
}
42-
if (shortcut.alt) {
43-
parts.push("alt");
44-
}
45-
if (shortcut.shift) {
46-
parts.push("shift");
47-
}
48-
49-
const cleanKey = shortcut.key.trim().toLowerCase();
50-
if (cleanKey.includes(" ")) {
51-
parts.push(cleanKey.split(/\s+/).join(">"));
52-
} else {
53-
parts.push(cleanKey);
54-
}
55-
return parts.join("+");
56-
}
57-
58-
/**
59-
* A hook to register global keyboard shortcuts using react-hotkeys-hook.
60-
*/
61-
export function useKeyboardShortcuts(shortcuts: ShortcutConfig[]) {
62-
const hotkeyMap = useMemo(() => {
63-
return shortcuts.map((s) => ({
64-
hotkey: getHotkeyValue(s),
65-
shortcut: s,
66-
}));
67-
}, [shortcuts]);
68-
69-
const hotkeysList = useMemo(() => hotkeyMap.map((h) => h.hotkey), [hotkeyMap]);
70-
71-
useHotkeys(
72-
hotkeysList,
73-
(event, handler) => {
74-
const target = event.target as HTMLElement;
75-
const isInput =
76-
target?.tagName === "INPUT" || target?.tagName === "TEXTAREA" || target?.isContentEditable;
77-
78-
const matched = hotkeyMap.find((h) => h.hotkey === handler.hotkey);
79-
if (matched) {
80-
if (isInput && !matched.shortcut.allowInInputs) {
81-
return;
82-
}
83-
event.preventDefault();
84-
matched.shortcut.handler(event);
85-
}
86-
},
87-
{
88-
enableOnFormTags: true,
89-
enableOnContentEditable: true,
90-
},
91-
[hotkeyMap]
92-
);
93-
}
1+
export {};

apps/web/app/root.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { STORAGE_KEYS } from "@kreozalabs/kei-core";
2222
import { registerPWA } from "./utils/pwa";
2323
import { SyncListener } from "./components/SyncListener";
2424
import { ActionInputModalProvider } from "./providers/ActionInputModalContext";
25-
import { useKeyboardShortcuts } from "./hooks/useKeyboardShortcuts";
25+
import { useHotkeys } from "react-hotkeys-hook";
2626

2727
export function Layout({ children }: { children: React.ReactNode }) {
2828
useEffect(() => {
@@ -70,19 +70,19 @@ export function Layout({ children }: { children: React.ReactNode }) {
7070
export default function App() {
7171
const navigate = useNavigate();
7272

73-
useKeyboardShortcuts([
74-
{
75-
key: "g>s>t",
76-
handler: () => navigate("/app/settings"),
77-
description: "Go to Settings",
78-
},
79-
{
80-
key: "k",
81-
ctrlOrMeta: true,
82-
handler: () => console.log("Search..."),
83-
description: "Search",
84-
},
85-
]);
73+
// Navigation shortcuts
74+
useHotkeys("g>s>t", () => navigate("/app/settings"), { preventDefault: true });
75+
useHotkeys("g>d>t", () => navigate("/app/calendar/day"), { preventDefault: true });
76+
useHotkeys("g>w>t", () => navigate("/app/calendar/week"), { preventDefault: true });
77+
useHotkeys("g>c>t", () => navigate("/app/calendar/month"), { preventDefault: true });
78+
useHotkeys("g>y>t", () => navigate("/app/calendar/year"), { preventDefault: true });
79+
useHotkeys("g>i>t", () => navigate("/app/calendar/inbox"), { preventDefault: true });
80+
useHotkeys("g>t>t", () => navigate("/app/calendar/day"), { preventDefault: true });
81+
useHotkeys("g>a>t", () => navigate("/app/calendar/agenda"), { preventDefault: true });
82+
useHotkeys("g>l>t", () => navigate("/app/calendar/lists"), { preventDefault: true });
83+
84+
// Other shortcuts
85+
useHotkeys("mod+k", () => console.log("Search..."), { preventDefault: true });
8686

8787
return (
8888
<QueryProvider>

apps/web/app/routes/app/dashboard.tsx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { CalendarView } from "./dashboard/views/CalendarView";
1212
import { DashboardHeader } from "@/components/dashboard/DashboardHeader";
1313
import { AppPage } from "@/components/layout/AppPage";
1414
import { CalendarSkeleton } from "@/components/CalendarSkeleton";
15-
import { useKeyboardShortcuts } from "@/hooks/useKeyboardShortcuts";
1615

1716
const DashboardShell = memo(function DashboardShell() {
1817
const {
@@ -30,14 +29,6 @@ const DashboardShell = memo(function DashboardShell() {
3029

3130
const { setViewMode } = useDashboardContext();
3231

33-
useKeyboardShortcuts([
34-
{ key: "g>d>t", handler: () => setViewMode("day"), description: "Go to Day View" },
35-
{ key: "g>w>t", handler: () => setViewMode("week"), description: "Go to Week View" },
36-
{ key: "g>c>t", handler: () => setViewMode("month"), description: "Go to Calendar" },
37-
{ key: "g>i>t", handler: () => setViewMode("inbox"), description: "Go to Inbox" },
38-
{ key: "g>t>t", handler: () => setViewMode("day"), description: "Go to Timeline" },
39-
]);
40-
4132
useEffect(() => {
4233
document.title = `Kei︱Timeline — ${formatTitleDate(parseDateString(selectedDate))}`; // TODO: Add Weekday
4334
}, [selectedDate]);

0 commit comments

Comments
 (0)