Skip to content

Commit af47e75

Browse files
vancuraclaude
andauthored
feat(storybook): add comprehensive stories for LinearProgress component (podman-desktop#17504)
Expand LinearProgress Storybook stories from a single basic story to five stories documenting all behaviors and usage contexts: Basic, Page Header (active/idle states), Form Page (deploy/YAML scenarios), Accessibility (ARIA attributes, HC guide line, reduced motion), and Comparison (LinearProgress vs ProgressBar side-by-side). Signed-off-by: Vaclav Vancura <commit@vancura.dev> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fd26152 commit af47e75

1 file changed

Lines changed: 279 additions & 5 deletions

File tree

Lines changed: 279 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,295 @@
11
<script context="module" lang="ts">
22
import LinearProgress from '@podman-desktop/ui-svelte/LinearProgress';
3-
import { type Args, defineMeta, type StoryContext } from '@storybook/addon-svelte-csf';
3+
import ProgressBar from '@podman-desktop/ui-svelte/ProgressBar';
4+
import { defineMeta } from '@storybook/addon-svelte-csf';
45
56
/**
6-
* These are the stories for the `LinearProgress` component.
7-
* Displays undeterminate progres through a line.
7+
* Stories for the `LinearProgress` component from `packages/ui`.
8+
*
9+
* A full-width, indeterminate linear progress indicator used in page headers
10+
* to signal that an async operation is in progress. Unlike `ProgressBar`, this
11+
* component has no determinate mode, no percentage text, and always spans the
12+
* full container width at 2px height.
13+
*
14+
* **Usage**: Rendered inside `Page.svelte` when `inProgress` is `true`,
15+
* appearing between the page header and the tab bar. Propagated through
16+
* `FormPage` and `EngineFormPage` to pages like DeployPodToKube,
17+
* PodCreateFromContainers, and KubePlayYAML.
18+
*
19+
* **Accessibility**: Uses `role="progressbar"` with `aria-valuemin` and
20+
* `aria-valuemax`. Supports `prefers-reduced-motion` to disable animation.
21+
* Additional ARIA attributes (e.g. `aria-label`) are spread onto the outer
22+
* wrapper element.
23+
*
24+
* **Theming**: Uses CSS custom properties `--pd-progressBar-bg`,
25+
* `--pd-progressBar-in-progress-bg`, `--pd-progressBar-in-progress-border`,
26+
* and `--pd-progressBar-hc-line-bg` from the color registry.
827
*/
928
const { Story } = defineMeta({
1029
component: LinearProgress,
1130
render: template,
1231
title: 'Progress/LinearProgress',
1332
tags: ['autodocs'],
33+
argTypes: {
34+
class: {
35+
control: 'text',
36+
description: 'Additional CSS classes on the wrapper element',
37+
},
38+
'aria-label': {
39+
control: 'text',
40+
description: 'Accessible label for the progress indicator',
41+
},
42+
kind: {
43+
table: { disable: true },
44+
},
45+
},
1446
});
1547
</script>
1648

17-
{#snippet template({ _children, ...args }: Args<typeof Story>, _context: StoryContext<typeof Story>)}
18-
<LinearProgress {...args} />
49+
{#snippet template({ _children, ...args })}
50+
{#if args.kind === 'pageHeader'}
51+
<div class="flex flex-col gap-6">
52+
<div class="text-sm text-(--pd-content-text)">
53+
LinearProgress is used inside <code>Page.svelte</code> between the header and tab bar.
54+
It appears when the <code>inProgress</code> prop is set to <code>true</code>.
55+
</div>
56+
57+
<div class="flex flex-col gap-2">
58+
<div class="text-xs font-semibold text-(--pd-content-header)">Active (inProgress=true)</div>
59+
60+
<div class="rounded border border-(--pd-content-divider) overflow-hidden">
61+
<div class="flex flex-col bg-(--pd-content-bg)">
62+
<div class="flex flex-row items-center px-5 pt-4 pb-2">
63+
<div class="flex flex-col w-full">
64+
<div class="flex items-center text-sm text-(--pd-content-breadcrumb)">
65+
<span>Containers</span>
66+
<span class="mx-2">&gt;</span>
67+
<span class="font-extralight">Create a container</span>
68+
</div>
69+
<h1 class="text-xl font-bold text-(--pd-content-header) pt-1">Create a container</h1>
70+
</div>
71+
</div>
72+
73+
<LinearProgress />
74+
75+
<div class="flex flex-row px-2 border-b border-(--pd-content-divider)">
76+
<div class="px-4 py-2 text-sm text-(--pd-content-header) border-b-2 border-(--pd-content-header)">Details</div>
77+
<div class="px-4 py-2 text-sm text-(--pd-content-text)">Networking</div>
78+
<div class="px-4 py-2 text-sm text-(--pd-content-text)">Volumes</div>
79+
</div>
80+
81+
<div class="p-5 text-sm text-(--pd-content-text) h-24">
82+
Tab content area
83+
</div>
84+
</div>
85+
</div>
86+
</div>
87+
88+
<div class="flex flex-col gap-2">
89+
<div class="text-xs font-semibold text-(--pd-content-header)">Idle (inProgress=false)</div>
90+
91+
<div class="rounded border border-(--pd-content-divider) overflow-hidden">
92+
<div class="flex flex-col bg-(--pd-content-bg)">
93+
<div class="flex flex-row items-center px-5 pt-4 pb-2">
94+
<div class="flex flex-col w-full">
95+
<div class="flex items-center text-sm text-(--pd-content-breadcrumb)">
96+
<span>Containers</span>
97+
<span class="mx-2">&gt;</span>
98+
<span class="font-extralight">Create a container</span>
99+
</div>
100+
<h1 class="text-xl font-bold text-(--pd-content-header) pt-1">Create a container</h1>
101+
</div>
102+
</div>
103+
104+
<div class="flex flex-row px-2 border-b border-(--pd-content-divider)">
105+
<div class="px-4 py-2 text-sm text-(--pd-content-header) border-b-2 border-(--pd-content-header)">Details</div>
106+
<div class="px-4 py-2 text-sm text-(--pd-content-text)">Networking</div>
107+
<div class="px-4 py-2 text-sm text-(--pd-content-text)">Volumes</div>
108+
</div>
109+
110+
<div class="p-5 text-sm text-(--pd-content-text) h-24">
111+
Tab content area
112+
</div>
113+
</div>
114+
</div>
115+
</div>
116+
</div>
117+
{:else if args.kind === 'formPage'}
118+
<div class="flex flex-col gap-6">
119+
<div class="text-sm text-(--pd-content-text)">
120+
FormPage and EngineFormPage pass <code>inProgress</code> to <code>Page</code>, which renders
121+
LinearProgress. These pages are used for operations like deploying to Kubernetes, creating
122+
pods from containers, and running Kubernetes YAML files.
123+
</div>
124+
125+
<div class="flex flex-col gap-2">
126+
<div class="text-xs font-semibold text-(--pd-content-header)">Deploy to Kubernetes (deploying)</div>
127+
128+
<div class="rounded border border-(--pd-content-divider) overflow-hidden">
129+
<div class="flex flex-col bg-(--pd-content-bg)">
130+
<div class="flex flex-row items-center px-5 pt-4 pb-2">
131+
<div class="flex flex-col w-full">
132+
<div class="flex items-center text-sm text-(--pd-content-breadcrumb)">
133+
<span>Pods</span>
134+
<span class="mx-2">&gt;</span>
135+
<span class="font-extralight">Deploy generated pod to Kubernetes</span>
136+
</div>
137+
<h1 class="text-xl font-bold text-(--pd-content-header) pt-1">Deploy generated pod to Kubernetes</h1>
138+
</div>
139+
</div>
140+
141+
<LinearProgress aria-label="Deploying pod to Kubernetes" />
142+
143+
<div class="flex flex-row px-2 border-b border-(--pd-content-divider)"></div>
144+
145+
<div class="p-5 text-sm text-(--pd-content-text) h-24">
146+
Form content area
147+
</div>
148+
</div>
149+
</div>
150+
</div>
151+
152+
<div class="flex flex-col gap-2">
153+
<div class="text-xs font-semibold text-(--pd-content-header)">Create pods from YAML (running)</div>
154+
155+
<div class="rounded border border-(--pd-content-divider) overflow-hidden">
156+
<div class="flex flex-col bg-(--pd-content-bg)">
157+
<div class="flex flex-row items-center px-5 pt-4 pb-2">
158+
<div class="flex flex-col w-full">
159+
<div class="flex items-center text-sm text-(--pd-content-breadcrumb)">
160+
<span>Pods</span>
161+
<span class="mx-2">&gt;</span>
162+
<span class="font-extralight">Create pods from a Kubernetes YAML file</span>
163+
</div>
164+
<h1 class="text-xl font-bold text-(--pd-content-header) pt-1">Create pods from a Kubernetes YAML file</h1>
165+
</div>
166+
</div>
167+
168+
<LinearProgress aria-label="Creating pods from YAML" />
169+
170+
<div class="flex flex-row px-2 border-b border-(--pd-content-divider)"></div>
171+
172+
<div class="p-5 text-sm text-(--pd-content-text) h-24">
173+
Form content area
174+
</div>
175+
</div>
176+
</div>
177+
</div>
178+
</div>
179+
{:else if args.kind === 'accessibility'}
180+
<div class="flex flex-col gap-4">
181+
<div class="text-sm text-(--pd-content-text)">
182+
The inner animated bar element uses <code>role="progressbar"</code> with <code>aria-valuemin</code>
183+
and <code>aria-valuemax</code>. Since LinearProgress is always indeterminate, <code>aria-valuenow</code>
184+
is never set. Additional ARIA props (e.g. <code>aria-label</code>) land on the outer wrapper via
185+
<code>restProps</code>. The animation respects <code>prefers-reduced-motion: reduce</code>.
186+
</div>
187+
188+
<div class="grid grid-cols-2 gap-4">
189+
<div class="flex flex-col gap-2 rounded border border-(--pd-content-divider) p-3">
190+
<div class="text-xs font-semibold text-(--pd-content-header)">Default</div>
191+
192+
<div class="py-2">
193+
<LinearProgress />
194+
</div>
195+
196+
<code class="text-[10px] text-(--pd-content-text) break-all">role="progressbar" aria-valuemin="0" aria-valuemax="100"</code>
197+
</div>
198+
199+
<div class="flex flex-col gap-2 rounded border border-(--pd-content-divider) p-3">
200+
<div class="text-xs font-semibold text-(--pd-content-header)">With aria-label</div>
201+
202+
<div class="py-2">
203+
<LinearProgress aria-label="Loading page content" />
204+
</div>
205+
206+
<code class="text-[10px] text-(--pd-content-text) break-all">aria-label="Loading page content" on wrapper</code>
207+
</div>
208+
</div>
209+
210+
<div class="flex flex-col gap-2 rounded border border-(--pd-content-divider) p-3">
211+
<div class="text-xs font-semibold text-(--pd-content-header)">High-contrast guide line</div>
212+
213+
<div class="text-sm text-(--pd-content-text)">
214+
A 1px guide line using <code>--pd-progressBar-hc-line-bg</code> is rendered behind the animated
215+
bar. In standard themes it is transparent. In high-contrast themes it becomes visible
216+
(white in HC Dark, black in HC Light) to ensure the bar track is perceivable.
217+
</div>
218+
</div>
219+
220+
<div class="flex flex-col gap-2 rounded border border-(--pd-content-divider) p-3">
221+
<div class="text-xs font-semibold text-(--pd-content-header)">Reduced motion</div>
222+
223+
<div class="text-sm text-(--pd-content-text)">
224+
When <code>prefers-reduced-motion: reduce</code> is active, the sweep animation is disabled
225+
entirely. The bar remains visible as a static filled indicator.
226+
</div>
227+
</div>
228+
</div>
229+
{:else if args.kind === 'comparison'}
230+
<div class="flex flex-col gap-6">
231+
<div class="text-sm text-(--pd-content-text)">
232+
LinearProgress and ProgressBar serve different purposes. LinearProgress is a page-level
233+
indicator (full-width, thin, indeterminate-only). ProgressBar is a general-purpose component
234+
used in tables, status bars, and dialogs (configurable dimensions, supports determinate mode
235+
with percentage text).
236+
</div>
237+
238+
<div class="grid grid-cols-1 gap-4">
239+
<div class="flex flex-col gap-2 rounded border border-(--pd-content-divider) p-4">
240+
<div class="text-xs font-semibold text-(--pd-content-header)">LinearProgress (page-level, indeterminate only)</div>
241+
242+
<div class="py-2">
243+
<LinearProgress />
244+
</div>
245+
246+
<code class="text-[10px] text-(--pd-content-text)">Full width, h-0.5 (2px), no rounded corners, no text</code>
247+
</div>
248+
249+
<div class="flex flex-col gap-2 rounded border border-(--pd-content-divider) p-4">
250+
<div class="text-xs font-semibold text-(--pd-content-header)">ProgressBar - indeterminate (component-level)</div>
251+
252+
<div class="py-2">
253+
<ProgressBar />
254+
</div>
255+
256+
<code class="text-[10px] text-(--pd-content-text)">Configurable width (default w-36), h-2 (8px), rounded, no text</code>
257+
</div>
258+
259+
<div class="flex flex-col gap-2 rounded border border-(--pd-content-divider) p-4">
260+
<div class="text-xs font-semibold text-(--pd-content-header)">ProgressBar - determinate (component-level)</div>
261+
262+
<div class="py-2">
263+
<ProgressBar progress={65} />
264+
</div>
265+
266+
<code class="text-[10px] text-(--pd-content-text)">Configurable width (default w-36), h-2 (8px), rounded, shows 65%</code>
267+
</div>
268+
</div>
269+
</div>
270+
{:else}
271+
<LinearProgress {...args} />
272+
{/if}
19273
{/snippet}
20274

21275
<Story name="Basic" />
276+
<Story
277+
name="Page Header"
278+
args={{
279+
kind: 'pageHeader',
280+
}} />
281+
<Story
282+
name="Form Page"
283+
args={{
284+
kind: 'formPage',
285+
}} />
286+
<Story
287+
name="Accessibility"
288+
args={{
289+
kind: 'accessibility',
290+
}} />
291+
<Story
292+
name="Comparison"
293+
args={{
294+
kind: 'comparison',
295+
}} />

0 commit comments

Comments
 (0)