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.
161 lines
6.7 KiB
TypeScript
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>
|
|
),
|
|
}
|