import { Progress } from "@ai-ui/ui"; import type { Meta, StoryObj } from "@storybook/react"; function ProgressCard({ accent, label, note, pattern = "linear", segmentCount, tone = "default", value, variant = "default" }: { accent: string; label: string; note: string; pattern?: "linear" | "segmented"; segmentCount?: number; tone?: "default" | "subtle"; value: number | null; variant?: "default" | "success" | "warning" | "destructive"; }) { const displayValue = value == null ? "Syncing" : `${value}%`; return (

{label}

{note}

{displayValue}
); } const meta = { title: "Components/Progress", component: Progress, args: { pattern: "linear", segmentCount: 24, size: "md", value: 64, variant: "default" }, argTypes: { className: { control: false }, size: { control: "radio", options: ["sm", "md", "lg"] }, pattern: { control: "radio", options: ["linear", "segmented"] }, segmentCount: { control: { type: "range", min: 6, max: 36, step: 1 } }, tone: { control: "radio", options: ["default", "subtle"] }, value: { control: { type: "range", min: 0, max: 100, step: 1 } }, variant: { control: "radio", options: ["default", "success", "warning", "destructive"] } }, parameters: { docs: { description: { component: "Progress is the system's inline status channel for work that is actively advancing inside the current view. Use the default linear pattern for classic completion bars, or switch to the segmented pattern when a dashboard metric needs a more discrete meter like rollout targets, cost reduction goals, and operational scorecards." } }, layout: "centered" }, tags: ["autodocs"] } satisfies Meta; export default meta; type Story = StoryObj; export const Playground: Story = { render: (args) => (

Asset Upload

Publishing release visuals

Use the same component for classic uploads and segmented dashboard meters by swapping only the pattern instead of reaching for a separate one-off implementation.

{args.value}%
) }; export const Patterns: Story = { render: () => (
) }; export const States: Story = { render: () => (
) }; export const Anatomy: Story = { render: () => (

Progress anatomy

The public styling contract stays intentionally small: a root track and one indicator layer, with the data attributes carrying the meaningful state.

data-slot="root" is the tonal track. It exposes data-size,{" "} data-pattern,{" "} data-tone,{" "} data-variant, and{" "} data-state so consumers can respond to contract-level state instead of incidental class names.

data-slot="indicator" is the filled value layer for the linear pattern. It mirrors pattern, size, variant, and state so tests and downstream themes can target the active bar without reaching into private markup.

In segmented mode, data-slot="segments"{" "} wraps the repeated meter cells and each{" "} data-slot="segment" exposes active state for dashboard-style styling and assertions.

) }; export const Motion: Story = { parameters: { docs: { description: { story: "Determinate values should feel calm and weighted, with the filled slab scaling into place instead of animating layout width. Indeterminate progress keeps a restrained sweep in interactive mode, then falls back to a static reading when reduced or static motion is active." } } }, render: () => (
) };