Replace keyboard system with TanStack Hotkeys and jotai atoms

Remove react-hotkeys-hook, KeyboardProvider context, useRegisterShortcut
wrapper, and KeyboardHelpDialog component. Components now call TanStack
useHotkey/useHotkeySequence directly. Shared state uses jotai atoms in
lib/atoms/, shortcut metadata lives as a static const in lib/constants/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 11:48:14 -05:00
co-authored by Claude Opus 4.6
parent d5e985efd9
commit ec256b7a96
11 changed files with 337 additions and 441 deletions
+2 -5
View File
@@ -1,8 +1,6 @@
import { headers } from "next/headers";
import { redirect } from "next/navigation";
import { CommandPalette } from "@/components/command-palette";
import { KeyboardHelpDialog } from "@/components/keyboard-help-dialog";
import { KeyboardProvider } from "@/components/keyboard-provider";
import { MobileTabBar } from "@/components/mobile-tab-bar";
import { NavBar } from "@/components/nav-bar";
import { auth } from "@/lib/auth/server";
@@ -16,7 +14,7 @@ export default async function PagesLayout({
if (!session) redirect("/login");
return (
<KeyboardProvider>
<>
<div className="min-h-screen overflow-x-hidden pb-14 sm:pb-0">
<NavBar />
{/* Ambient glow */}
@@ -27,7 +25,6 @@ export default async function PagesLayout({
</div>
<MobileTabBar />
<CommandPalette />
<KeyboardHelpDialog />
</KeyboardProvider>
</>
);
}
@@ -1,8 +1,11 @@
"use client";
import type { Hotkey } from "@tanstack/react-hotkeys";
import { useHotkey } from "@tanstack/react-hotkeys";
import { useAtomValue } from "jotai";
import { useRouter } from "next/navigation";
import { useMemo } from "react";
import { useRegisterShortcut } from "@/hooks/use-register-shortcut";
import { commandPaletteOpenAtom } from "@/lib/atoms/command-palette";
import { useTitleInteraction } from "./title-interaction-provider";
const statusCycle = ["watchlist", "in_progress", "completed"] as const;
@@ -17,6 +20,9 @@ export function TitleKeyboardShortcuts() {
handleWatchMovie,
} = useTitleInteraction();
const commandPaletteOpen = useAtomValue(commandPaletteOpenAtom);
const enabled = !commandPaletteOpen;
const nextStatus = useMemo(() => {
const currentIdx = statusCycle.indexOf(
userStatus as (typeof statusCycle)[number],
@@ -26,38 +32,19 @@ export function TitleKeyboardShortcuts() {
: statusCycle[currentIdx + 1];
}, [userStatus]);
useRegisterShortcut("title-cycle-status", {
keys: ["w"],
description: "Cycle status",
action: () => handleStatusChange(nextStatus),
scope: "Title",
});
useRegisterShortcut("title-mark-watched", {
keys: ["m"],
description: "Mark watched",
action: () => {
useHotkey("W", () => handleStatusChange(nextStatus), { enabled });
useHotkey(
"M",
() => {
if (titleType === "movie") handleWatchMovie();
},
scope: "Title",
});
{ enabled },
);
useHotkey("Escape", () => router.back(), { enabled });
useRegisterShortcut("title-escape", {
keys: ["Escape"],
description: "Go back",
action: () => router.back(),
scope: "Title",
});
// Rating shortcuts 1-5
for (const n of [1, 2, 3, 4, 5]) {
// biome-ignore lint/correctness/useHookAtTopLevel: loop is stable
useRegisterShortcut(`title-rate-${n}`, {
keys: [String(n)],
description: `Rate ${n} star${n > 1 ? "s" : ""}`,
action: () => handleRating(n),
scope: "Title",
});
// biome-ignore lint/correctness/useHookAtTopLevel: loop is stable (always 5 iterations)
useHotkey(String(n) as Hotkey, () => handleRating(n), { enabled });
}
return null;