Files
cadence-ui/apps/docs/src/components/progress.stories.tsx
T

307 lines
10 KiB
TypeScript

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 (
<article className="rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-5 shadow-[var(--shadow-sm)]">
<div className="flex items-start justify-between gap-4">
<div className="space-y-1">
<p
className="text-xs uppercase tracking-[var(--tracking-caps)]"
style={{ color: accent }}
>
{label}
</p>
<p className="text-sm leading-6 text-[var(--color-muted-foreground)]">{note}</p>
</div>
<span className="text-sm font-medium text-[var(--color-foreground)]">{displayValue}</span>
</div>
<Progress
aria-label={label}
className="mt-4"
pattern={pattern}
segmentCount={segmentCount}
tone={tone}
value={value}
variant={variant}
/>
</article>
);
}
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<typeof Progress>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Playground: Story = {
render: (args) => (
<div className="w-[380px] 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="flex items-start justify-between gap-4">
<div className="space-y-1">
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-primary)]">
Asset Upload
</p>
<h3 className="text-lg font-semibold tracking-[var(--tracking-tight)]">
Publishing release visuals
</h3>
<p className="text-sm leading-6 text-[var(--color-muted-foreground)]">
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.
</p>
</div>
<span className="rounded-[var(--radius-full)] bg-[color-mix(in_oklch,var(--color-primary-container)_70%,white_30%)] px-3 py-1 text-sm font-medium text-[var(--color-on-primary-container)]">
{args.value}%
</span>
</div>
<Progress aria-label="Upload progress" className="mt-5" {...args} />
</div>
)
};
export const Patterns: Story = {
render: () => (
<div className="grid w-[720px] gap-4 md:grid-cols-2">
<ProgressCard
accent="var(--color-primary)"
label="Release Assets"
note="The marketing bundle is halfway through the upload queue."
value={46}
/>
<ProgressCard
accent="var(--color-foreground)"
label="Operational Cost Reduction"
note="Segmented meters work better for dashboard targets where discrete progress reads faster than a single slab."
pattern="segmented"
segmentCount={24}
tone="subtle"
value={42}
/>
<ProgressCard
accent="color-mix(in oklch, var(--color-success) 78%, var(--color-foreground))"
label="Audience Sync"
note="Subscriber segments are reconciling against the latest CRM export."
tone="subtle"
value={74}
variant="success"
/>
<ProgressCard
accent="color-mix(in oklch, var(--color-warning) 72%, var(--color-foreground))"
label="Compliance Review"
note="A few rule checks are still pending before the rollout can advance."
value={58}
variant="warning"
/>
<ProgressCard
accent="var(--color-destructive)"
label="Recovery Job"
note="The pipeline is retrying a failed package publish after signature drift."
tone="subtle"
value={91}
variant="destructive"
/>
</div>
)
};
export const States: Story = {
render: () => (
<div className="grid w-[720px] gap-4 md:grid-cols-3">
<ProgressCard
accent="var(--color-primary)"
label="Determinate"
note="Linear progress remains the best fit for straightforward task completion."
value={68}
/>
<ProgressCard
accent="var(--color-foreground)"
label="Segmented Goal"
note="The segmented pattern makes discrete target progress easier to scan in dense dashboards."
pattern="segmented"
segmentCount={20}
tone="subtle"
value={42}
/>
<ProgressCard
accent="color-mix(in oklch, var(--color-success) 78%, var(--color-foreground))"
label="Complete"
note="Completion stays quiet and stable instead of introducing a second celebratory treatment."
value={100}
variant="success"
/>
</div>
)
};
export const Anatomy: Story = {
render: () => (
<div className="w-[760px] 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-4">
<div className="space-y-1">
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
Progress anatomy
</p>
<p className="text-sm leading-6 text-[var(--color-muted-foreground)]">
The public styling contract stays intentionally small: a root track and one indicator
layer, with the data attributes carrying the meaningful state.
</p>
</div>
<div className="rounded-[var(--radius-md)] border border-dashed border-[var(--color-border-strong)] bg-[var(--color-background)] p-5">
<div className="grid gap-4">
<Progress aria-label="Progress anatomy example" size="lg" value={68} />
<Progress
aria-label="Progress anatomy segmented example"
pattern="segmented"
segmentCount={20}
size="lg"
tone="subtle"
value={42}
/>
</div>
</div>
<div className="grid gap-3 text-sm leading-6 text-[var(--color-muted-foreground)]">
<p>
<code className="text-[var(--color-foreground)]">data-slot="root"</code> is the tonal
track. It exposes <code className="text-[var(--color-foreground)]">data-size</code>,{" "}
<code className="text-[var(--color-foreground)]">data-pattern</code>,{" "}
<code className="text-[var(--color-foreground)]">data-tone</code>,{" "}
<code className="text-[var(--color-foreground)]">data-variant</code>, and{" "}
<code className="text-[var(--color-foreground)]">data-state</code> so consumers can
respond to contract-level state instead of incidental class names.
</p>
<p>
<code className="text-[var(--color-foreground)]">data-slot="indicator"</code> 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.
</p>
<p>
In segmented mode, <code className="text-[var(--color-foreground)]">data-slot="segments"</code>{" "}
wraps the repeated meter cells and each{" "}
<code className="text-[var(--color-foreground)]">data-slot="segment"</code> exposes
active state for dashboard-style styling and assertions.
</p>
</div>
</div>
</div>
)
};
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: () => (
<div className="grid w-[720px] gap-4 md:grid-cols-3">
<ProgressCard
accent="var(--color-primary)"
label="Determinate"
note="A known value keeps the indicator anchored and scales the filled slab instead of animating layout width."
value={68}
/>
<ProgressCard
accent="var(--color-foreground)"
label="Segmented Meter"
note="Discrete bars stay crisp at a glance and match dashboard KPI treatments."
pattern="segmented"
segmentCount={24}
tone="subtle"
value={42}
/>
<ProgressCard
accent="color-mix(in oklch, var(--color-success) 78%, var(--color-foreground))"
label="Segmented Indeterminate"
note="Unknown duration shifts the segmented pattern into a staggered pulse instead of pretending there is a precise width."
pattern="segmented"
segmentCount={20}
tone="subtle"
value={null}
variant="success"
/>
</div>
)
};