This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/web/src/components/dialogs/HelpOverlay.tsx
T
Miguel Palhas 8a05ef2eb7 feat(maestro): wire React app as sole web entry point
Deletes the vanilla shell and mounts App.tsx on #root. main.ts and the
23 views it drove are gone; App.tsx composes the ported components —
sidebar, multi-view, command bar, spawn modal, help and restart
overlays, daemon link, drawer — around the catalog and layout stores.

The pure parts of four deleted modules moved next to the components
that still use them: the spawn-form model and spawnSimilar into
components/spawn, the transcript's markdown/format helpers and the diff
into components/structured, the help overlay's grouping into
components/dialogs. Document-level event names now live in uiEvents.ts.

Deduplicated two pairs the migration left side by side: the sidebar
uses dialogs/KillDialog and common/ContextMenu. It also listens for the
command bar's kill/rename events, which nothing had picked up since
catalog.ts went away.

styles.css stays as the token and base layer, with `#app` renamed to
`#root` and the pane-body rules re-keyed off the classes the React
panes actually carry.

Closes #253
2026-09-09 02:27:42 +01:00

79 lines
2.9 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 { 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);
};
window.addEventListener("keydown", onKeydown);
return () => window.removeEventListener("keydown", onKeydown);
}, [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,
);
}