feat: add sheet component and docs qa baseline

This commit is contained in:
2026-03-19 18:46:20 +08:00
parent 71ebb010b9
commit f318f94c9a
28 changed files with 1799 additions and 91 deletions
+130 -17
View File
@@ -10,10 +10,48 @@ import {
} from "@ai-ui/ui";
import type { Meta, StoryObj } from "@storybook/react";
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 (
<Dialog>
<DialogTrigger asChild>
<Button>{triggerLabel}</Button>
</DialogTrigger>
<DialogContent size={size}>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="ghost">Cancel</Button>
<Button>Confirm launch</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
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"]
@@ -24,23 +62,98 @@ export default meta;
type Story = StoryObj<typeof meta>;
export const Playground: Story = {
render: () => <LaunchDialog />
};
export const Sizes: Story = {
render: () => (
<Dialog>
<DialogTrigger asChild>
<Button>Open approval dialog</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Launch this release?</DialogTitle>
<DialogDescription>
This will notify the routing team and publish the release note to the activity feed.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="ghost">Cancel</Button>
<Button>Confirm launch</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<div className="grid w-[720px] gap-3 sm:grid-cols-2">
<LaunchDialog
description="Use the compact size for short confirmations that only need a title, one supporting sentence, and one primary action."
size="sm"
title="Publish summary?"
triggerLabel="Compact dialog"
/>
<LaunchDialog
description="Use the large size when the flow needs denser copy, audit context, or multi-step review detail before a final action."
size="lg"
title="Review rollout checklist"
triggerLabel="Large dialog"
/>
</div>
)
};
export const Anatomy: Story = {
render: () => (
<div className="w-[700px] rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 text-[var(--color-foreground)] shadow-[var(--shadow-sm)]">
<div className="space-y-3">
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
Dialog anatomy
</p>
<LaunchDialog triggerLabel="Preview dialog structure" />
<div className="grid gap-3 text-sm leading-6 text-[var(--color-muted-foreground)]">
<p>
<code className="text-[var(--color-foreground)]">data-slot="overlay"</code> sits
behind the surface and carries the backdrop motion.
</p>
<p>
<code className="text-[var(--color-foreground)]">data-slot="content"</code> wraps
the modal panel and exposes <code className="text-[var(--color-foreground)]">data-size</code>.
</p>
<p>
<code className="text-[var(--color-foreground)]">data-slot="header"</code>,{" "}
<code className="text-[var(--color-foreground)]">data-slot="footer"</code>,{" "}
<code className="text-[var(--color-foreground)]">data-slot="label"</code>, and{" "}
<code className="text-[var(--color-foreground)]">data-slot="description"</code>
provide stable hooks for structure and docs.
</p>
<p>
The close button is built into <code className="text-[var(--color-foreground)]">DialogContent</code>,
so every dialog gets a dismiss affordance even when the footer stays minimal.
</p>
</div>
</div>
</div>
)
};
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: () => (
<div className="grid w-[760px] gap-4 lg:grid-cols-[minmax(0,0.95fr)_minmax(0,1.05fr)]">
<article className="rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 shadow-[var(--shadow-sm)]">
<h3 className="text-lg font-semibold tracking-[var(--tracking-tight)]">
Accessibility notes
</h3>
<div className="mt-4 grid gap-3 text-sm leading-6 text-[var(--color-muted-foreground)]">
<p>Keep the title outcome-oriented so assistive tech announces the decision clearly.</p>
<p>
Use the description for the consequence or next step, not decorative copy.
</p>
<p>
Keep the trigger specific. &quot;Open approval dialog&quot; is more useful than a generic
&quot;Open&quot;.
</p>
<p>
Reserve dialogs for blocking work. If the content should not trap focus, prefer
a popover instead.
</p>
</div>
</article>
<div className="flex items-center justify-center rounded-[var(--radius-lg)] border border-dashed border-[var(--color-border-strong)] bg-[var(--color-background)] p-6">
<LaunchDialog
description="Keyboard focus moves into the surface, Escape closes it, and the trigger regains focus after dismissal."
triggerLabel="Open accessible dialog"
/>
</div>
</div>
)
};