swarm-ui: move agent creation into the agents page as a dialog
Adds a generic ui/dialog/Dialog primitive (native <dialog>, no third-party modal lib and no shadow-DOM custom element per the esbuild gap on those) and wires a "+ agent" button into AgentsPage that opens the existing create-agent form inside it, content unchanged from its former life as a standalone /create-agent route/nav item. Removes the "new agent" top-level nav entry and the /create-agent route entirely -- creation now only reachable from the roster that gets populated by it. CreateAgentPage.tsx/css renamed to CreateAgentForm.tsx/css to match its new role as a mounted component rather than a page. Screenshot-verified the dialog open/closed states against a mock server.
This commit is contained in:
parent
dabd0fc823
commit
13d7c73c12
7 changed files with 165 additions and 23 deletions
76
frontend/packages/swarm-ui/src/ui/dialog/Dialog.tsx
Normal file
76
frontend/packages/swarm-ui/src/ui/dialog/Dialog.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// <Dialog> — a generic modal overlay, native `<dialog>` element rather
|
||||
// than a hand-rolled focus-trap + backdrop: `showModal()` gives focus
|
||||
// trapping, Escape-to-close, and a real `::backdrop` for free, and
|
||||
// `@hive/shared`'s own shadow-DOM `hive-dialog` custom element isn't an
|
||||
// option here — swarm-ui's esbuild config can't consume those custom
|
||||
// elements at all (a separate, already-tracked gap).
|
||||
// No opinion on content beyond a close button; the caller's own markup
|
||||
// supplies whatever heading/layout it needs (its first real caller,
|
||||
// the create-agent form, already has an established two-panel layout
|
||||
// from its former life as a standalone route — reusing that unchanged
|
||||
// is simpler and lower-risk than re-deriving a modal-specific layout
|
||||
// for content mara has already reviewed).
|
||||
import { useEffect, useRef } from 'preact/hooks';
|
||||
import type { ComponentChildren } from 'preact';
|
||||
import './Dialog.css';
|
||||
|
||||
export function Dialog({
|
||||
open,
|
||||
onClose,
|
||||
label,
|
||||
children,
|
||||
}: {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
// `aria-label` only, no visible title row — see file-top comment on
|
||||
// why content owns its own heading.
|
||||
label: string;
|
||||
children: ComponentChildren;
|
||||
}) {
|
||||
const ref = useRef<HTMLDialogElement>(null);
|
||||
|
||||
// Drives the native open/closed state from the `open` prop rather
|
||||
// than mounting/unmounting the element — `showModal()`/`close()` are
|
||||
// imperative, there's no declarative `<dialog open>` equivalent that
|
||||
// also gets you the backdrop + focus trap.
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
if (open && !el.open) el.showModal();
|
||||
else if (!open && el.open) el.close();
|
||||
}, [open]);
|
||||
|
||||
// The dialog's own `close` event fires on Escape (and would fire on
|
||||
// a native <form method="dialog"> submit, unused here) — syncing it
|
||||
// back to the caller's state keeps `open` truthful even when nothing
|
||||
// in this component's own JS drove the close.
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
function handleClose() {
|
||||
onClose();
|
||||
}
|
||||
el.addEventListener('close', handleClose);
|
||||
return () => el.removeEventListener('close', handleClose);
|
||||
}, [onClose]);
|
||||
|
||||
return (
|
||||
<dialog
|
||||
ref={ref}
|
||||
class="ui-dialog"
|
||||
aria-label={label}
|
||||
// A click lands on the `<dialog>` element itself (not any child)
|
||||
// exactly when it's outside the rendered content box — inside
|
||||
// the box, the click target is always some descendant. Standard
|
||||
// "click the backdrop to close" trick for native `<dialog>`.
|
||||
onClick={(e) => {
|
||||
if (e.target === ref.current) onClose();
|
||||
}}
|
||||
>
|
||||
<button type="button" class="ui-dialog-close" aria-label="close" onClick={onClose}>
|
||||
✕
|
||||
</button>
|
||||
<div class="ui-dialog-body">{children}</div>
|
||||
</dialog>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue