Initial commit: HMDesignSystem baseline
This commit is contained in:
commit
b6a0a61103
486 files changed
+68432
No files matched your search
@@ -0,0 +1,124 @@
|
||||
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 { STORY_KICKER, 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>
|
||||
)
|
||||
}
|
||||
|
||||
function Controlled() {
|
||||
const [checked, setChecked] = useState<boolean | "indeterminate">("indeterminate")
|
||||
return (
|
||||
<Option label={`Select all (${String(checked)})`}>
|
||||
{(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
|
||||
kicker={STORY_KICKER.components}
|
||||
title="Checkbox"
|
||||
description="항목을 선택하거나 해제하는 컨트롤입니다. 18px 박스이며 Checked·Indeterminate는 state-active 색입니다."
|
||||
/>
|
||||
<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: <Option label="Option">{(id) => <Checkbox id={id} checked="indeterminate" />}</Option> },
|
||||
{ id: "interactive", label: "Controlled · click toggles", noteAfter: true, note: "Indeterminate에서 클릭하면 checked가 됩니다(Radix).", children: <Controlled /> },
|
||||
]}
|
||||
/>
|
||||
</StorySection>
|
||||
<StorySection title="States">
|
||||
<StateBoard
|
||||
min={240}
|
||||
states={[
|
||||
{ id: "hover", label: "Hover", noteAfter: true, note: "Unchecked는 테두리가 primary 색, Checked는 Black 20% overlay. 포인터를 올려 확인합니다.", children: <Option label="Option">{(id) => <Checkbox id={id} />}</Option> },
|
||||
{ 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" description="aria-invalid로 Error 테두리를 표시합니다.">
|
||||
<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>,
|
||||
}
|
||||
Reference in new issue
Block a user