feat(web): pop-out modal forms for setup/subscriptions/roles

Add a reusable ui/Modal (Radix Dialog + terminal chrome) and move the
add/edit forms in the Devices setup, Subscriptions and Roles screens into it,
leaving each list in the page behind the modal. The Devices wizard's per-
category device form is also fully translated (setup.* i18n keys).

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
2026-06-19 10:08:33 +02:00
parent 808fb26ab6
commit 8444bf34c3
4 changed files with 367 additions and 348 deletions
+45
View File
@@ -0,0 +1,45 @@
import * as Dialog from "@radix-ui/react-dialog";
import type { ReactNode } from "react";
// Reusable modal shell — a thin wrapper over Radix Dialog matching the terminal
// chrome (title bar + ✕, dark overlay, square panel). The same styling BoothPayModal
// uses inline, factored out so every popped-out form looks identical. Radix handles
// focus trap, Escape, and outside-click → onClose. `width` is a Tailwind max-width
// class (the panel is responsive: w-full up to that cap).
export function Modal({
open,
onClose,
title,
children,
width = "max-w-xl",
}: {
open: boolean;
onClose: () => void;
title: ReactNode;
children: ReactNode;
/** Tailwind max-width class for the panel (default max-w-xl). */
width?: string;
}) {
return (
<Dialog.Root open={open} onOpenChange={(o) => !o && onClose()}>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 z-40 bg-black/70" />
<Dialog.Content
className={`fixed left-1/2 top-1/2 z-50 max-h-[90vh] w-[95vw] ${width} -translate-x-1/2 -translate-y-1/2 overflow-y-auto rounded-term border border-term-border bg-term-panel font-mono text-term-text shadow-2xl`}
aria-describedby={undefined}
>
<div className="sticky top-0 flex items-center justify-between border-b border-term-border bg-term-panel-2 px-4 py-2">
<Dialog.Title className="m-0 text-[12px] font-semibold uppercase tracking-wider text-term-amber">
{title}
</Dialog.Title>
<Dialog.Close className="text-term-muted hover:text-term-text" aria-label="Close">
✕
</Dialog.Close>
</div>
<div className="p-4">{children}</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}