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.
131 lines
5.5 KiB
TypeScript
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>,
|
|
}
|