mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-08-29 06:15:39 -04:00
- Move dashboard page to app/(pages)/dashboard, redirect / to /dashboard for authenticated users, update all nav/breadcrumb/auth hrefs - Mark all episodes watched when a TV show status is set to "completed" (markAllEpisodesWatched skips already-watched episodes) - Refactor KeyboardProvider to store shortcuts in a ref instead of state, eliminating unnecessary re-renders; use react-hotkeys-hook for Cmd+K so it works reliably in form inputs - Fix useRegisterShortcut: re-register on every render (stable ref read) with a separate cleanup effect, removing brittle key memoization - Search page: silently navigate to title detail on import instead of showing "Added to library" toast
78 lines
2.5 KiB
TypeScript
78 lines
2.5 KiB
TypeScript
"use client";
|
|
|
|
import { useKeyboard } from "@/components/keyboard-provider";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
import { Kbd } from "@/components/ui/kbd";
|
|
|
|
export function KeyboardHelpDialog() {
|
|
const { shortcutsRef, helpOpen, setHelpOpen } = useKeyboard();
|
|
|
|
// Group shortcuts by scope
|
|
const grouped: Record<string, { description: string; keys: string[] }[]> = {};
|
|
for (const def of shortcutsRef.current.values()) {
|
|
const scope = def.scope ?? "Global";
|
|
if (!grouped[scope]) grouped[scope] = [];
|
|
grouped[scope].push({ description: def.description, keys: def.keys });
|
|
}
|
|
|
|
return (
|
|
<Dialog open={helpOpen} onOpenChange={setHelpOpen}>
|
|
<DialogContent className="sm:max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle>Keyboard Shortcuts</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="space-y-5 py-2">
|
|
{Object.entries(grouped).map(([scope, items]) => (
|
|
<div key={scope} className="space-y-2">
|
|
<h3 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
{scope}
|
|
</h3>
|
|
<div className="space-y-1">
|
|
{items.map((item) => (
|
|
<div
|
|
key={item.description}
|
|
className="flex items-center justify-between rounded-md px-2 py-1.5"
|
|
>
|
|
<span className="text-xs text-foreground">
|
|
{item.description}
|
|
</span>
|
|
<div className="flex items-center gap-1">
|
|
{item.keys.map((key, i) => (
|
|
<span key={key} className="flex items-center gap-1">
|
|
{i > 0 && (
|
|
<span className="text-[10px] text-muted-foreground">
|
|
then
|
|
</span>
|
|
)}
|
|
<Kbd>{formatKey(key)}</Kbd>
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|
|
|
|
function formatKey(key: string): string {
|
|
const map: Record<string, string> = {
|
|
" ": "Space",
|
|
Escape: "Esc",
|
|
ArrowUp: "↑",
|
|
ArrowDown: "↓",
|
|
ArrowLeft: "←",
|
|
ArrowRight: "→",
|
|
};
|
|
return map[key] ?? key.toUpperCase();
|
|
}
|