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

161 lines
6.7 KiB
TypeScript

import { useState, type ReactNode } from "react"
import type { Meta, StoryObj } from "@storybook/react-vite"
import { AlignCenterIcon, AlignLeftIcon, AlignRightIcon, BoldIcon, ItalicIcon, UnderlineIcon } from "lucide-react"
import { ToggleGroup, ToggleGroupItem } from "@hm-design/ui"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
// Toggle Group is a group of pressed-state items (toolbar-style), not a content switcher (that is Tabs). The representative Story
// shows single / multiple / icon-only / disabled / vertical / spaced groups with real controls; the same specimens also exist as
// Stories tagged `verification` (hidden from the sidebar). Icon-only items carry an `aria-label`.
// `showValue={false}` hides the selected-value readout on the representative page; the verification Stories keep it.
function Single({ showValue = true }: { showValue?: boolean }) {
const [value, setValue] = useState("left")
return (
<div className="grid gap-2">
<ToggleGroup type="single" variant="outline" value={value} onValueChange={(v) => v && setValue(v)} aria-label="정렬">
<ToggleGroupItem value="left">왼쪽</ToggleGroupItem>
<ToggleGroupItem value="center">가운데</ToggleGroupItem>
<ToggleGroupItem value="right">오른쪽</ToggleGroupItem>
</ToggleGroup>
{showValue ? <span className="text-sm text-muted-foreground" data-testid="single">{`value = ${value}`}</span> : null}
</div>
)
}
function Multiple({ showValue = true }: { showValue?: boolean }) {
const [value, setValue] = useState<string[]>(["bold"])
return (
<div className="grid gap-2">
<ToggleGroup type="multiple" variant="outline" value={value} onValueChange={setValue} aria-label="서식">
<ToggleGroupItem value="bold" aria-label="굵게">
<BoldIcon />
</ToggleGroupItem>
<ToggleGroupItem value="italic" aria-label="기울임">
<ItalicIcon />
</ToggleGroupItem>
<ToggleGroupItem value="underline" aria-label="밑줄">
<UnderlineIcon />
</ToggleGroupItem>
</ToggleGroup>
{showValue ? <span className="text-sm text-muted-foreground" data-testid="multiple">{`value = [${value.join(", ")}]`}</span> : null}
</div>
)
}
function Disabled() {
return (
<div className="grid gap-3">
<ToggleGroup type="single" variant="outline" defaultValue="a" disabled aria-label="비활성 그룹">
<ToggleGroupItem value="a">A</ToggleGroupItem>
<ToggleGroupItem value="b">B</ToggleGroupItem>
</ToggleGroup>
<ToggleGroup type="single" variant="outline" defaultValue="a" aria-label="비활성 항목">
<ToggleGroupItem value="a">A</ToggleGroupItem>
<ToggleGroupItem value="b" disabled>
B (비활성)
</ToggleGroupItem>
<ToggleGroupItem value="c">C</ToggleGroupItem>
</ToggleGroup>
</div>
)
}
function Vertical() {
return (
<ToggleGroup type="single" variant="outline" orientation="vertical" defaultValue="left" aria-label="세로 정렬">
<ToggleGroupItem value="left" aria-label="왼쪽">
<AlignLeftIcon />
</ToggleGroupItem>
<ToggleGroupItem value="center" aria-label="가운데">
<AlignCenterIcon />
</ToggleGroupItem>
<ToggleGroupItem value="right" aria-label="오른쪽">
<AlignRightIcon />
</ToggleGroupItem>
</ToggleGroup>
)
}
function Spaced() {
return (
<ToggleGroup type="multiple" variant="outline" spacing={2} aria-label="간격 있는 그룹">
<ToggleGroupItem value="a">첫째</ToggleGroupItem>
<ToggleGroupItem value="b">둘째</ToggleGroupItem>
<ToggleGroupItem value="c">셋째</ToggleGroupItem>
</ToggleGroup>
)
}
function Many() {
return (
<ToggleGroup type="multiple" variant="outline" spacing={1} className="flex-wrap" aria-label="많은 항목">
{Array.from({ length: 10 }, (_, i) => (
<ToggleGroupItem key={i} value={`${i}`}>{`옵션 ${i + 1}`}</ToggleGroupItem>
))}
</ToggleGroup>
)
}
function Sizes() {
return (
<div className="grid gap-3">
{(["sm", "default", "lg"] as const).map((size) => (
<ToggleGroup key={size} type="multiple" size={size} variant="outline" aria-label={`${size} content kinds`}>
<ToggleGroupItem value="icon" aria-label={`${size} icon-only`}><BoldIcon /></ToggleGroupItem>
<ToggleGroupItem value="mixed"><BoldIcon />굵게</ToggleGroupItem>
<ToggleGroupItem value="text">{size}</ToggleGroupItem>
</ToggleGroup>
))}
</div>
)
}
// No `component`: ToggleGroup props are a union (single | multiple), so Stories without args would not type-check.
const meta = { title: "Components/Form/Toggle Group", parameters: { layout: "padded" } } satisfies Meta
export default meta
type Story = StoryObj<typeof meta>
// The representative page shows the groups without example titles: the section title names them.
const examples = (...nodes: ReactNode[]) => (
<div className="flex flex-wrap items-start gap-x-10 gap-y-6">
{nodes.map((node, i) => (
<div key={i}>{node}</div>
))}
</div>
)
// Representative Story: the common Story page; the sections group the same specimens.
export const All: Story = {
name: "Toggle Group",
parameters: { layout: "fullscreen", controls: { disable: true } },
render: () => (
<StoryPage>
<StoryPageHeader title="Toggle Group" />
<StorySection title="Overview">{examples(<Single showValue={false} />)}</StorySection>
<StorySection title="Sizes">
<Sizes />
</StorySection>
<StorySection title="Variants">{examples(<Multiple showValue={false} />, <Vertical />, <Spaced />)}</StorySection>
<StorySection title="States">{examples(<Disabled />)}</StorySection>
<StorySection title="Usage">{examples(<div className="max-w-sm"><Many /></div>)}</StorySection>
</StoryPage>
),
}
export const SingleSelection: Story = { tags: ["verification"], name: "Single", render: () => <Single /> }
export const MultipleSelection: Story = { tags: ["verification"], name: "Multiple", render: () => <Multiple /> }
export const VerticalGroup: Story = { tags: ["verification"], name: "Vertical", render: () => <Vertical /> }
export const ContentNodes: Story = {
tags: ["verification"],
render: () => (
<ToggleGroup type="multiple" variant="outline" aria-label="Content nodes">
<ToggleGroupItem value="array" aria-label="Array icon">{[<BoldIcon key="icon" />]}</ToggleGroupItem>
<ToggleGroupItem value="fragment" aria-label="Fragment icon"><><BoldIcon /></></ToggleGroupItem>
<ToggleGroupItem value="mixed"><><BoldIcon />굵게</></ToggleGroupItem>
<ToggleGroupItem value="span"><BoldIcon /><span>굵게</span></ToggleGroupItem>
</ToggleGroup>
),
}