Post-React-migration regressions: spawn submit button moved to modal footer, sidebar footer restored (help/theme/palette/status/spawn), session card stripped of redundant Tailwind, action buttons use proper leaf-act/leaf-actions CSS structure for hover-only visibility. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
85 lines
3.2 KiB
TypeScript
85 lines
3.2 KiB
TypeScript
// React port of ../../help.ts: the `?` overlay renders the command registry
|
|
// (commands.ts) directly — the same list that dispatches the keys — so a
|
|
// binding can never be documented as one thing and do another.
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { createPortal } from "react-dom";
|
|
|
|
import { CommandSet, defineCommands, keyCaps } from "../../commands.js";
|
|
import { TOGGLE_HELP_EVENT } from "../../uiEvents.js";
|
|
import { groupedCommands } from "./model.js";
|
|
|
|
export function HelpOverlay() {
|
|
const [open, setOpen] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const helpKeys = new CommandSet(defineCommands({ "help.toggle": () => setOpen((o) => !o) }));
|
|
const overlayKeys = new CommandSet(defineCommands({ "help.close": () => setOpen(false) }));
|
|
const onKeydown = (e: KeyboardEvent) => {
|
|
if (open && overlayKeys.handle(e)) return;
|
|
helpKeys.handle(e);
|
|
};
|
|
const onToggle = () => setOpen((o) => !o);
|
|
window.addEventListener("keydown", onKeydown);
|
|
document.addEventListener(TOGGLE_HELP_EVENT, onToggle);
|
|
return () => {
|
|
window.removeEventListener("keydown", onKeydown);
|
|
document.removeEventListener(TOGGLE_HELP_EVENT, onToggle);
|
|
};
|
|
}, [open]);
|
|
|
|
if (!open) return null;
|
|
|
|
const close = () => setOpen(false);
|
|
|
|
return createPortal(
|
|
<div
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-label="Keyboard shortcuts"
|
|
className="help-overlay fixed inset-0 z-modal flex items-center justify-center bg-bg/70"
|
|
onClick={(e) => {
|
|
if (e.target === e.currentTarget) close();
|
|
}}
|
|
>
|
|
<div className="help-panel max-h-[80vh] w-full max-w-[560px] overflow-auto rounded-r-3 border border-border bg-surface p-sp-4 shadow-2">
|
|
<header className="help-head flex items-center justify-between">
|
|
<h2 className="help-title m-0 text-fs-15 font-semibold text-ink">Keyboard shortcuts</h2>
|
|
<button
|
|
type="button"
|
|
aria-label="Close"
|
|
className="help-close rounded-r-1 px-sp-2 py-1 text-fs-15 text-muted hover:text-ink"
|
|
onClick={close}
|
|
>
|
|
✕
|
|
</button>
|
|
</header>
|
|
{groupedCommands().map((group) => (
|
|
<section key={group.title} className="help-group mt-sp-3">
|
|
<h3 className="help-group-title text-fs-12 font-semibold text-muted">{group.title}</h3>
|
|
<dl className="help-list mt-sp-1">
|
|
{group.items.map((cmd) => (
|
|
<div
|
|
key={cmd.id}
|
|
className="help-row flex items-center justify-between gap-sp-3 py-sp-1"
|
|
data-command-id={cmd.id}
|
|
>
|
|
<dt className="help-keys m-0 flex gap-sp-1">
|
|
{keyCaps(cmd.keys[0]).map((cap, i) => (
|
|
<kbd key={i} className="rounded-r-1 border border-border px-sp-1 text-fs-11">
|
|
{cap}
|
|
</kbd>
|
|
))}
|
|
</dt>
|
|
<dd className="help-desc m-0 text-fs-12 text-ink">{cmd.label}</dd>
|
|
</div>
|
|
))}
|
|
</dl>
|
|
</section>
|
|
))}
|
|
</div>
|
|
</div>,
|
|
document.body,
|
|
);
|
|
}
|