Initial commit: HMDesignSystem baseline

This commit is contained in:
shkim4 committed 2026-10-08 16:58:09 +09:00
commit b6a0a61103
486 files changed
+68432

No files matched your search

+87
View File
@@ -0,0 +1,87 @@
import { useId, useState, type ReactNode } from "react"
import type { Meta, StoryObj } from "@storybook/react-vite"
import { Label, Switch } from "@hm-design/ui"
import { StateBoard } from "@/components/story-board"
import { STORY_KICKER, StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
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 [on, setOn] = useState(false)
return <Option label={`Notifications (${on ? "on" : "off"})`}>{(id) => <Switch id={id} checked={on} onCheckedChange={setOn} />}</Option>
}
const meta = {
title: "Components/Form/Switch",
component: Switch,
parameters: {
layout: "padded",
docs: {
description: {
component:
"현재 HM 규격: 트랙 36×20, 썸 16, On은 state-active, Off는 stroke 색. ui-viz에는 Switch 규격이 없어 참고만 했습니다. Radix Switch의 role=switch, 키보드, 접근성과 shadcn 기본 transition을 유지합니다.",
},
},
},
} satisfies Meta<typeof Switch>
export default meta
type Story = StoryObj<typeof meta>
export const All: Story = {
name: "Switch",
parameters: { layout: "fullscreen", controls: { disable: true } },
render: () => (
<StoryPage>
<StoryPageHeader
kicker={STORY_KICKER.components}
title="Switch"
description="설정을 즉시 켜고 끄는 컨트롤입니다. 트랙 36×20, 썸 16이며 On은 state-active 색입니다."
/>
<StorySection title="Overview">
<StateBoard
min={240}
states={[
{ id: "off", label: "Off", children: <Option label="Option">{(id) => <Switch id={id} />}</Option> },
{ id: "on", label: "On", children: <Option label="Option">{(id) => <Switch id={id} defaultChecked />}</Option> },
{ id: "interactive", label: "Controlled · click toggles", children: <Controlled /> },
]}
/>
</StorySection>
<StorySection title="States">
<StateBoard
min={240}
states={[
{ id: "hover", label: "Hover", noteAfter: true, note: "Black 20% overlay. 포인터를 올려 확인합니다.", children: <Option label="Option">{(id) => <Switch id={id} />}</Option> },
{ id: "focus", label: "Focus", children: <Option label="Option">{(id) => <Switch id={id} />}</Option> },
{
id: "disabled",
label: "Disabled",
children: (
<div className="grid gap-3">
<Option label="Off">{(id) => <Switch id={id} disabled />}</Option>
<Option label="On">{(id) => <Switch id={id} disabled defaultChecked />}</Option>
</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="switch"]')?.focus({ preventScroll: true })
},
}
export const Off: Story = { tags: ["verification"], render: () => <Option label="Option">{(id) => <Switch id={id} />}</Option> }
export const On: Story = { tags: ["verification"], render: () => <Option label="Option">{(id) => <Switch id={id} defaultChecked />}</Option> }
export const Disabled: Story = { tags: ["verification"], render: () => <Option label="Option">{(id) => <Switch id={id} disabled defaultChecked />}</Option> }