Files
HMDesign/src/components/progress.stories.tsx
T
shkim4 4c29894e56 refactor(storybook): simplify component and preview examples
Drop page / section descriptions, state notes and repeated captions from
the representative Stories of 44 components, the Patterns, Center
Graphic and OrgChart pages. Value readouts, spec lists and captions hide
only on the representative page; hidden verification Stories keep them.
Tabs Default API and the Filter Bar draft / applied counts move to the
hidden DefaultApi and DraftApplied Stories; Center Overview is hidden
with its direct URL kept. Story IDs are unchanged.

Align the component, pattern and OrgChart docs with the pages, point the
quality reference check at the DraftApplied Story, and make the P8
styles check expect no section header on the preview-only Expanded page.
2026-10-23 00:28:05 +09:00

91 lines
4.1 KiB
TypeScript

import { useState } from "react"
import type { Meta, StoryObj } from "@storybook/react-vite"
import { Button, Progress } from "@hm-design/ui"
import { StateBoard } from "@/components/story-board"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
// Progress follows `value` (0..max). The representative Story shows fixed values plus one interactive value; the same specimens
// also exist as Stories tagged `verification` (hidden from the sidebar). No status colors are defined, so there is one look.
// `showValue={false}` hides the value readout on the representative page; the verification Stories keep it.
function Interactive({ showValue = true }: { showValue?: boolean }) {
const [value, setValue] = useState(30)
return (
<div className="grid gap-3">
<Progress value={value} aria-label="진행률" />
{showValue ? <p className="text-sm text-muted-foreground" data-testid="value">{`value = ${value}`}</p> : null}
<div className="flex gap-2">
<Button variant="cancel" appearance="solid" size="sm" onClick={() => setValue((v) => Math.max(0, v - 25))}>
-25
</Button>
<Button variant="primary" appearance="solid" size="sm" onClick={() => setValue((v) => Math.min(100, v + 25))}>
+25
</Button>
</div>
</div>
)
}
function CustomMaximum() {
return (
<div className="grid gap-3">
<Progress value={0} max={200} aria-label="0 of 200" />
<Progress value={50} max={200} aria-label="50 of 200" />
<Progress value={200} max={200} aria-label="200 of 200" />
<Progress max={200} aria-label="Indeterminate" />
</div>
)
}
const sections = [
{ id: "zero", label: "0%", node: <Progress value={0} aria-label="0%" /> },
{ id: "middle", label: "50%", node: <Progress value={50} aria-label="50%" /> },
{ id: "full", label: "100%", node: <Progress value={100} aria-label="100%" /> },
{ id: "custom-maximum", label: "max = 200", node: <CustomMaximum /> },
{ id: "interactive", label: "Interactive", node: <Interactive showValue={false} /> },
]
const meta = { title: "Components/Data & Display/Progress", component: Progress, parameters: { layout: "padded" } } satisfies Meta<typeof Progress>
export default meta
type Story = StoryObj<typeof meta>
const board = (...ids: string[]) => (
<StateBoard min={320} states={sections.filter((s) => ids.includes(s.id)).map((s) => ({ id: s.id, label: s.label, children: s.node }))} />
)
// Representative Story: the common Story page; the sections group the same specimens.
export const All: Story = {
name: "Progress",
parameters: { layout: "fullscreen", controls: { disable: true } },
render: () => (
<StoryPage>
<StoryPageHeader title="Progress" />
<StorySection title="Overview">{board("middle")}</StorySection>
<StorySection title="States">{board("zero", "full")}</StorySection>
<StorySection title="Usage">{board("custom-maximum", "interactive")}</StorySection>
</StoryPage>
),
}
export const Zero: Story = { tags: ["verification"], render: () => <Progress value={0} aria-label="0%" /> }
export const Middle: Story = { tags: ["verification"], render: () => <Progress value={50} aria-label="50%" /> }
export const Full: Story = { tags: ["verification"], render: () => <Progress value={100} aria-label="100%" /> }
export const NonDefaultMax: Story = {
tags: ["verification"],
render: () => <CustomMaximum />,
play: async ({ canvasElement }) => {
const bars = canvasElement.querySelectorAll('[role="progressbar"]')
const expected = ["translateX(-100%)", "translateX(-75%)", "translateX(0%)", "translateX(-100%)"]
bars.forEach((bar, index) => {
const indicator = bar.querySelector<HTMLElement>('[data-slot="progress-indicator"]')
if (bar.getAttribute("aria-valuemax") !== "200" || indicator?.style.transform !== expected[index]) {
throw new Error(`Progress max=200: ARIA/fill mismatch in specimen ${index}`)
}
})
if (bars.length !== 4 || bars[1]?.getAttribute("aria-valuenow") !== "50" || bars[3]?.hasAttribute("aria-valuenow")) {
throw new Error("Progress max=200: incorrect determinate/indeterminate ARIA state")
}
},
}