import { Button, EmptyState, EmptyStateActions, EmptyStateDescription, EmptyStateEyebrow, EmptyStateHeader, EmptyStateMedia, EmptyStateTitle } from "@ai-ui/ui"; import type { Meta, StoryObj } from "@storybook/react"; import type { ReactNode } from "react"; function EmptyStateGlyph() { return (
); } function WorkspaceSetupGlyph() { return (
); } function ReleaseEmptyState({ actionsLayout = "inline", align = "center", className, description = "Adjust the current filters or create a new release to start routing work.", eyebrow = "No results", glyph, layout = "default", mediaSize = "default", tone = "default", title = "No matching releases" }: { actionsLayout?: "inline" | "stack"; align?: "center" | "start"; className?: string; description?: string; eyebrow?: string; glyph?: ReactNode; layout?: "compact" | "default" | "split"; mediaSize?: "compact" | "default" | "hero"; title?: string; tone?: "default" | "subtle" | "accent"; }) { return ( {glyph ?? } {eyebrow} {title} {description} ); } const meta = { title: "Components/EmptyState", component: ReleaseEmptyState, parameters: { docs: { description: { component: "EmptyState is the system surface for no-results, first-run, and no-content moments that should still feel intentional. It gives teams one stable composition for media, framing copy, and next-step actions instead of improvising ad hoc placeholder cards." } }, layout: "centered" }, tags: ["autodocs"] } satisfies Meta; export default meta; type Story = StoryObj; export const Playground: Story = {}; export const Scenarios: Story = { render: () => (
) }; export const CompactQueue: Story = { parameters: { docs: { description: { story: "Use the compact layout when an empty state lives inside a dense operational card, list, or queue panel and should preserve surrounding information density." } } }, render: () => (
) }; export const SplitWorkspace: Story = { parameters: { docs: { description: { story: "Use the split layout when the empty state needs more narrative weight, richer actions, or a larger visual anchor inside a dashboard or setup surface." } } }, render: () => (
} layout="split" mediaSize="hero" title="This command center needs its first operator" tone="accent" />
) }; export const Motion: Story = { parameters: { docs: { description: { story: "EmptyState keeps motion local to the decorative media and the call-to-action group. The slab itself stays anchored while the media drifts gently and the actions enter in a short sequence. In static motion mode those effects collapse back to near-instant feedback." } } }, render: () => (
} layout="split" mediaSize="hero" title="Invite the first operator to activate this command center" tone="accent" />
) }; export const Anatomy: Story = { render: () => (

Empty state anatomy

data-slot=\"media\" holds the decorative or explanatory visual.

data-slot=\"header\",{" "} data-slot=\"eyebrow\",{" "} data-slot=\"label\", and{" "} data-slot=\"description\"{" "} structure the framing copy.

data-slot=\"actions\" groups the primary next step and any secondary recovery action.

data-tone exposes whether the surface stays neutral, subtle, or accent-led.

layout,{" "} align,{" "} size, and{" "} actions layout let the same composition flex between dense cards and broader setup moments.

) }; export const Accessibility: Story = { parameters: { docs: { description: { story: "Use empty states to explain what happened and what the user can do next. Keep the title concrete, the description actionable, and ensure the primary action is a real next step rather than decorative reassurance." } } }, render: () => (

Accessibility notes

Describe the absence clearly. "No matching releases" is better than "Nothing here".

Use actions that recover the user, such as clearing filters or creating the first item.

Do not hide critical instructions inside decorative media. The copy should stand on its own.

) };