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>
90 lines
3.1 KiB
TypeScript
90 lines
3.1 KiB
TypeScript
// React port of ../../spawnModal.ts: dialog chrome around SpawnForm. Escape,
|
|
// the ✕ and a click on the backdrop all close it; SpawnForm's own DirAutocomplete
|
|
// swallows Escape first, so dismissing a dropdown never also discards the form.
|
|
|
|
import { useEffect, useRef } from "react";
|
|
import { createPortal } from "react-dom";
|
|
|
|
import type { Session } from "../../types.js";
|
|
import { SpawnForm, type SpawnFormProps } from "./SpawnForm.js";
|
|
|
|
export interface SpawnModalProps {
|
|
seed?: SpawnFormProps["seed"];
|
|
/** Called with the created session after a successful submit — the modal
|
|
* has already closed by then, so the caller decides where it lands. */
|
|
onSuccess(session: Session): void;
|
|
onClose(): void;
|
|
}
|
|
|
|
export function SpawnModal({ seed, onSuccess, onClose }: SpawnModalProps) {
|
|
const filterInputRef = useRef<HTMLInputElement>(null);
|
|
|
|
const handleSuccess = (session: Session) => {
|
|
onClose();
|
|
onSuccess(session);
|
|
};
|
|
|
|
// The project filter owns the keyboard on open: typing narrows the list and
|
|
// Tab from here reaches the harness tiles, matching the TUI's draft pane.
|
|
useEffect(() => {
|
|
const host = filterInputRef.current;
|
|
host?.querySelector<HTMLInputElement>("input[placeholder='Filter projects…']")?.focus();
|
|
}, []);
|
|
|
|
return createPortal(
|
|
<div
|
|
className="modal-backdrop fixed inset-0 z-modal flex items-center justify-center bg-bg/70 backdrop-blur-sm"
|
|
onClick={(e) => {
|
|
if (e.target === e.currentTarget) onClose();
|
|
}}
|
|
>
|
|
<div
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="spawn-title"
|
|
className="flex max-h-[92vh] w-full max-w-[560px] flex-col rounded-r-3 border border-border bg-surface shadow-2"
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Escape" && !e.defaultPrevented) {
|
|
e.preventDefault();
|
|
onClose();
|
|
}
|
|
}}
|
|
>
|
|
<header className="flex items-center justify-between border-b border-border p-sp-3">
|
|
<h2 id="spawn-title" className="m-0 text-fs-15 font-semibold text-ink">
|
|
{seed ? "Spawn similar" : "Spawn session"}
|
|
</h2>
|
|
<button
|
|
type="button"
|
|
aria-label="Close"
|
|
className="rounded-r-1 px-sp-2 py-1 text-fs-15 text-muted hover:text-ink"
|
|
onClick={onClose}
|
|
>
|
|
✕
|
|
</button>
|
|
</header>
|
|
<div ref={filterInputRef} className="min-h-0 flex-1 overflow-auto">
|
|
<SpawnForm seed={seed} onSuccess={handleSuccess} externalSubmit />
|
|
</div>
|
|
<div className="flex justify-end gap-sp-2 border-t border-border p-sp-3">
|
|
<button
|
|
type="button"
|
|
className="rounded-r-1 border border-border px-sp-3 py-sp-1 text-fs-13 text-ink"
|
|
onClick={onClose}
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
form="spawn-form"
|
|
className="btn btn-primary rounded-r-1 px-sp-4 py-sp-1 text-fs-13 font-semibold"
|
|
>
|
|
Spawn
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>,
|
|
document.body,
|
|
);
|
|
}
|