import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@ai-ui/ui"; import type { Meta, StoryObj } from "@storybook/react"; async function waitForCondition( predicate: () => boolean, message: string, timeoutMs = 1500 ) { const startedAt = Date.now(); while (Date.now() - startedAt < timeoutMs) { if (predicate()) { return; } await new Promise((resolve) => window.setTimeout(resolve, 16)); } throw new Error(message); } type LaunchDialogProps = { description?: string; size?: "sm" | "md" | "lg"; title?: string; triggerLabel?: string; }; function LaunchDialog({ description = "This will notify the routing team and publish the release note to the activity feed.", size = "md", title = "Launch this release?", triggerLabel = "Open approval dialog" }: LaunchDialogProps) { return ( {title} {description} ); } const meta = { title: "Components/Dialog", component: Dialog, parameters: { docs: { description: { component: "Dialog is the system's blocking overlay for focused decisions, confirmation flows, and dense tasks that must temporarily interrupt the surrounding page. It ships with a portal, overlay, close affordance, semantic title and description wiring, and token-driven motion on both the surface and backdrop." } }, layout: "centered" }, tags: ["autodocs"] } satisfies Meta; export default meta; type Story = StoryObj; export const Playground: Story = { render: () => , play: async ({ canvasElement }) => { const trigger = [...canvasElement.querySelectorAll("button")].find((element) => element.textContent?.includes("Open approval dialog") ); if (!(trigger instanceof HTMLButtonElement)) { throw new Error("Expected the dialog trigger to render."); } trigger.click(); await waitForCondition( () => document.body.querySelector('[role="dialog"]') instanceof HTMLElement, "Expected the dialog to open." ); const closeButton = document.body.querySelector('[aria-label="Close dialog"]'); if (!(closeButton instanceof HTMLButtonElement)) { throw new Error("Expected the dialog close control to render."); } closeButton.click(); await waitForCondition( () => document.body.querySelector('[role="dialog"]') === null, "Expected the dialog to close." ); } }; export const Sizes: Story = { render: () => (
) }; export const Anatomy: Story = { render: () => (

Dialog anatomy

data-slot="overlay" sits behind the surface and carries the backdrop motion.

data-slot="content" wraps the modal panel and exposes data-size.

data-slot="header",{" "} data-slot="footer",{" "} data-slot="label", and{" "} data-slot="description" provide stable hooks for structure and docs.

The close button is built into DialogContent, so every dialog gets a dismiss affordance even when the footer stays minimal.

) }; export const Accessibility: Story = { parameters: { docs: { description: { story: "Use dialog only when the user must resolve or dismiss a blocking task. Focus is trapped while open, Escape closes the surface, and the title and description are announced through the Radix dialog semantics." } } }, render: () => (

Accessibility notes

Keep the title outcome-oriented so assistive tech announces the decision clearly.

Use the description for the consequence or next step, not decorative copy.

Keep the trigger specific. "Open approval dialog" is more useful than a generic "Open".

Reserve dialogs for blocking work. If the content should not trap focus, prefer a popover instead.

) };