import type { Meta, StoryObj } from "@storybook/react-vite" import { Label, RadioGroup, RadioGroupItem } from "@hm-design/ui" import type { CSSProperties } from "react" import { StateBoard, type BoardState } from "@/components/story-board" import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page" // Current HM Radio Group. ui-viz has no Radio spec, so it was reference only. // Story-only Label color. The shared Label sets `color: var(--hm-label-text)` from an unlayered theme rule, so a utility // like `text-foreground` loses to it; overriding the Label's own token on the element works. Option labels use the default // text color; a Label next to a disabled item (`peer-disabled`) keeps the secondary color. State titles are untouched. const optionLabel = "[--hm-label-text:var(--foreground)] peer-disabled:[--hm-label-text:var(--hm-sem-fg-secondary)]" // Representative-page layout (Storybook presentation only): equal columns on PC (iframe width 1000px and up), shrinking to // fit below that (`min` is the narrowest column). Each specimen sits in its own single-column StateBoard (min larger than // any column), so StateBoard's auto-fill grid cannot leave an empty track. Full class names are listed so Tailwind sees them. const specimenColumns = { 2: "min-[1000px]:grid-cols-2", 4: "min-[1000px]:grid-cols-4", } as const function SpecimenGrid({ states, columns, min = 220 }: { states: BoardState[]; columns: keyof typeof specimenColumns; min?: number }) { return (
{states.map((state) => ( ))}
) } function Group({ prefix, defaultValue = "a", disabled, disabledItem, invalid, orientation, hoverItem, }: { prefix: string defaultValue?: string disabled?: boolean disabledItem?: string invalid?: boolean orientation?: "horizontal" | "vertical" // Pins :hover on one item with the Storybook-only data-sb-hover attribute (.storybook/pseudo-states.ts). hoverItem?: string }) { return ( {["a", "b", "c"].map((v) => (
))}
) } const meta = { title: "Components/Form/Radio Group", component: RadioGroup, parameters: { layout: "padded", docs: { description: { component: "현재 HM 규격: 18px 원, border 1px(stroke), Checked/Selected는 state-active 테두리 + 8px state-active dot, group gap 12(horizontal 16). Radix RadioGroup의 roving focus, 방향키 선택, ARIA를 유지합니다. ui-viz에는 Radio 규격이 없어 참고만 했습니다.", }, }, }, } satisfies Meta export default meta type Story = StoryObj export const All: Story = { name: "Radio Group", parameters: { layout: "fullscreen", controls: { disable: true } }, render: () => ( {/* Overview is merged into Variants: its Vertical example is the first Variants item. */} }, { id: "horizontal", label: "Horizontal", children: }, ]} /> }, { id: "focus", label: "Focus", children: }, { id: "disabled-item", label: "Disabled · 항목", children: }, { id: "disabled-group", label: "Disabled · 그룹", children: }, ]} /> {/* Same 4-column track as States, so the Error example keeps the width of one States column. */} }]} /> ), play: async ({ canvasElement }) => { // preventScroll: the page opens at its header, not at the States section. canvasElement.querySelector('[data-overview-state="focus"] [role="radio"][data-state="checked"]')?.focus({ preventScroll: true }) }, } export const Vertical: Story = { tags: ["verification"], render: () => } export const Horizontal: Story = { tags: ["verification"], render: () => } export const Disabled: Story = { tags: ["verification"], render: () => } export const Error: Story = { tags: ["verification"], render: () => }