Files
HMDesign/src/components/checkbox.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

131 lines
5.5 KiB
TypeScript

import { useId, useState, type ReactNode } from "react"
import type { Meta, StoryObj } from "@storybook/react-vite"
import { Checkbox, Label } from "@hm-design/ui"
import { StateBoard } from "@/components/story-board"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
// Current HM Checkbox. ui-viz has no Checkbox spec, so it was reference only.
// The option label is the HM Label; its foreground role for Checkbox options is still TBD in ui_viz.
function Option({ label, children }: { label: string; children: (id: string) => ReactNode }) {
const id = useId()
return (
<div className="flex items-center gap-2">
{children(id)}
<Label htmlFor={id}>{label}</Label>
</div>
)
}
// Starts indeterminate; a click makes it checked (Radix), so the Indeterminate example is also the interactive one.
function Controlled() {
const [checked, setChecked] = useState<boolean | "indeterminate">("indeterminate")
return (
<Option label="Option">
{(id) => <Checkbox id={id} checked={checked} onCheckedChange={setChecked} />}
</Option>
)
}
const meta = {
title: "Components/Form/Checkbox",
component: Checkbox,
parameters: {
layout: "padded",
docs: {
description: {
component:
"현재 HM 규격: 18px 박스, radius 4, border 1px(stroke), Checked/Indeterminate는 state-active 테두리·면 + on-state-active 아이콘. Radix Checkbox의 data-state, indeterminate, 키보드(Space)와 접근성을 유지합니다. ui-viz에는 Checkbox 규격이 없어 참고만 했습니다.",
},
},
},
} satisfies Meta<typeof Checkbox>
export default meta
type Story = StoryObj<typeof meta>
export const All: Story = {
name: "Checkbox",
parameters: { layout: "fullscreen", controls: { disable: true } },
render: () => (
<StoryPage>
<StoryPageHeader title="Checkbox" />
<StorySection title="Overview">
<StateBoard
min={240}
states={[
{ id: "unchecked", label: "Unchecked", children: <Option label="Option">{(id) => <Checkbox id={id} />}</Option> },
{ id: "checked", label: "Checked", children: <Option label="Option">{(id) => <Checkbox id={id} defaultChecked />}</Option> },
{ id: "indeterminate", label: "Indeterminate", children: <Controlled /> },
]}
/>
</StorySection>
<StorySection title="States">
<StateBoard
min={240}
states={[
{
id: "hover",
label: "Hover",
// data-sb-hover pins :hover (Storybook only, .storybook/pseudo-states.ts).
children: (
<div className="grid gap-3">
<Option label="Unchecked">{(id) => <Checkbox id={id} data-sb-hover="" />}</Option>
<Option label="Checked">{(id) => <Checkbox id={id} defaultChecked data-sb-hover="" />}</Option>
</div>
),
},
{ id: "focus", label: "Focus", children: <Option label="Option">{(id) => <Checkbox id={id} />}</Option> },
{
id: "disabled",
label: "Disabled",
children: (
<div className="grid gap-3">
<Option label="Unchecked">{(id) => <Checkbox id={id} disabled />}</Option>
<Option label="Checked">{(id) => <Checkbox id={id} disabled defaultChecked />}</Option>
</div>
),
},
]}
/>
</StorySection>
<StorySection title="Validation">
<StateBoard min={240} states={[{ id: "error", label: "Error", children: <Option label="Option">{(id) => <Checkbox id={id} aria-invalid />}</Option> }]} />
</StorySection>
<StorySection title="Usage">
<StateBoard
min={240}
states={[
{
id: "long",
label: "Long Label",
children: (
<div className="max-w-[220px]">
<div className="flex items-start gap-2">
<Checkbox id="cb-long" className="mt-0.5" />
<Label htmlFor="cb-long">A very long checkbox label that wraps onto multiple lines</Label>
</div>
</div>
),
},
]}
/>
</StorySection>
</StoryPage>
),
play: async ({ canvasElement }) => {
// preventScroll: the page opens at its header, not at the States section.
canvasElement.querySelector<HTMLButtonElement>('[data-overview-state="focus"] [role="checkbox"]')?.focus({ preventScroll: true })
},
}
export const Unchecked: Story = { tags: ["verification"], render: () => <Option label="Option">{(id) => <Checkbox id={id} />}</Option> }
export const Checked: Story = { tags: ["verification"], render: () => <Option label="Option">{(id) => <Checkbox id={id} defaultChecked />}</Option> }
export const Indeterminate: Story = { tags: ["verification"], render: () => <Option label="Option">{(id) => <Checkbox id={id} checked="indeterminate" />}</Option> }
export const Disabled: Story = { tags: ["verification"], render: () => <Option label="Option">{(id) => <Checkbox id={id} disabled defaultChecked />}</Option> }
export const Error: Story = { tags: ["verification"], render: () => <Option label="Option">{(id) => <Checkbox id={id} aria-invalid />}</Option> }
export const Playground: Story = {
tags: ["verification"],
args: { disabled: false, defaultChecked: false },
render: (args) => <Option label="Option">{(id) => <Checkbox id={id} {...args} />}</Option>,
}