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>
122 lines
4.1 KiB
TypeScript
122 lines
4.1 KiB
TypeScript
"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>
|
|
);
|
|
}
|