feat: add command palette, accessibility, scroll animations, and keyboard navigation

Implements COMP-139 (command palette), COMP-140 (accessibility), COMP-141 (scroll animations), COMP-145 (keyboard navigation)

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-Claude)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Vectry
2026-02-10 18:06:47 +00:00
parent 38d5b4806c
commit 7ff493a89a
13 changed files with 1308 additions and 79 deletions

View File

@@ -0,0 +1,121 @@
"use client";
import { useEffect, useRef } from "react";
import { X } from "lucide-react";
interface KeyboardShortcutsHelpProps {
open: boolean;
onClose: () => void;
}
function Shortcut({ keys, label }: { keys: string[]; label: string }) {
return (
<div className="flex items-center justify-between py-2">
<span className="text-sm text-zinc-300">{label}</span>
<div className="flex items-center gap-1">
{keys.map((key) => (
<kbd
key={key}
className="inline-flex h-6 min-w-[1.5rem] items-center justify-center rounded border border-white/10 bg-white/[0.05] px-1.5 font-mono text-xs text-zinc-400"
>
{key}
</kbd>
))}
</div>
</div>
);
}
export function KeyboardShortcutsHelp({
open,
onClose,
}: KeyboardShortcutsHelpProps) {
const overlayRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!open) return;
const handler = (e: KeyboardEvent) => {
if (e.key === "Escape" || e.key === "?") {
e.preventDefault();
onClose();
}
};
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}, [open, onClose]);
if (!open) return null;
return (
<div className="fixed inset-0 z-[90]" ref={overlayRef}>
<div
className="fixed inset-0 bg-black/60 backdrop-blur-sm"
onClick={onClose}
aria-hidden="true"
/>
<div className="fixed inset-0 flex items-center justify-center px-4">
<div
role="dialog"
aria-label="Keyboard shortcuts"
className="w-full max-w-md rounded-xl border border-white/10 bg-[#111113] shadow-2xl shadow-black/50 animate-scale-in"
>
<div className="flex items-center justify-between px-5 py-4 border-b border-white/[0.06]">
<h2 className="text-base font-semibold text-white">
Keyboard Shortcuts
</h2>
<button
onClick={onClose}
className="p-1.5 rounded-lg text-zinc-400 hover:text-white hover:bg-white/[0.06] transition-colors"
aria-label="Close shortcuts help"
>
<X className="w-4 h-4" />
</button>
</div>
<div className="px-5 py-4 space-y-4">
<div>
<h3 className="text-xs font-medium text-zinc-500 uppercase tracking-wider mb-2">
Navigation
</h3>
<div className="divide-y divide-white/[0.04]">
<Shortcut keys={["j"]} label="Move down" />
<Shortcut keys={["k"]} label="Move up" />
<Shortcut keys={["Enter"]} label="Open selected" />
<Shortcut keys={["Esc"]} label="Clear selection" />
</div>
</div>
<div>
<h3 className="text-xs font-medium text-zinc-500 uppercase tracking-wider mb-2">
Go To
</h3>
<div className="divide-y divide-white/[0.04]">
<Shortcut keys={["g", "h"]} label="Go to Home" />
<Shortcut keys={["g", "g"]} label="Go to Generate" />
</div>
</div>
<div>
<h3 className="text-xs font-medium text-zinc-500 uppercase tracking-wider mb-2">
General
</h3>
<div className="divide-y divide-white/[0.04]">
<Shortcut
keys={["\u2318", "K"]}
label="Command palette"
/>
<Shortcut keys={["?"]} label="Show shortcuts" />
</div>
</div>
</div>
<div className="px-5 py-3 border-t border-white/[0.06]">
<p className="text-xs text-zinc-500 text-center">
Press <kbd className="inline-flex h-4 items-center rounded border border-white/10 bg-white/[0.05] px-1 font-mono text-[10px] text-zinc-400">?</kbd> or <kbd className="inline-flex h-4 items-center rounded border border-white/10 bg-white/[0.05] px-1 font-mono text-[10px] text-zinc-400">Esc</kbd> to close
</p>
</div>
</div>
</div>
</div>
);
}