139 lines
5.7 KiB
TypeScript
139 lines
5.7 KiB
TypeScript
import type { Meta, StoryObj } from "@storybook/react-vite"
|
|
import { BoldIcon, ItalicIcon, StarIcon } from "lucide-react"
|
|
import { Toggle, ToggleGroup, ToggleGroupItem } from "@hm-design/ui"
|
|
import { StateBoard, type BoardState } from "@/components/story-board"
|
|
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
|
|
|
|
// Toggle is a persistent pressed-state control. The representative Story shows each state with real, clickable toggles; the same
|
|
// specimens also exist as Stories tagged `verification` (hidden from the sidebar). Icon-only toggles carry an `aria-label`.
|
|
|
|
function OffOn() {
|
|
return (
|
|
<div className="flex flex-wrap items-center gap-3">
|
|
<Toggle>Off</Toggle>
|
|
<Toggle defaultPressed>On</Toggle>
|
|
<Toggle variant="outline">Outline off</Toggle>
|
|
<Toggle variant="outline" defaultPressed>
|
|
Outline on
|
|
</Toggle>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function WithIcon() {
|
|
return (
|
|
<div className="flex flex-wrap items-center gap-3">
|
|
<Toggle aria-label="굵게">
|
|
<BoldIcon />
|
|
</Toggle>
|
|
<Toggle aria-label="기울임" defaultPressed>
|
|
<ItalicIcon />
|
|
</Toggle>
|
|
<Toggle variant="outline">
|
|
<StarIcon />
|
|
즐겨찾기
|
|
</Toggle>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function Sizes() {
|
|
return (
|
|
// 3 equal columns on PC (iframe width 1000px and up): sm / default / lg; fewer columns below that (240px minimum).
|
|
<div className="grid items-start gap-6 [grid-template-columns:repeat(auto-fill,minmax(min(100%,240px),1fr))] min-[1000px]:grid-cols-3">
|
|
{(["sm", "default", "lg"] as const).map((size) => (
|
|
<div key={size} data-toggle-size={size} className="flex flex-wrap items-center gap-3">
|
|
<Toggle size={size} variant="outline" aria-label={`${size} icon-only`}><StarIcon /></Toggle>
|
|
<Toggle size={size} variant="outline"><StarIcon />즐겨찾기</Toggle>
|
|
<Toggle size={size} variant="outline">{size}</Toggle>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function Disabled() {
|
|
return (
|
|
<div className="flex flex-wrap items-center gap-3">
|
|
<Toggle disabled>Disabled off</Toggle>
|
|
<Toggle disabled defaultPressed>
|
|
Disabled on
|
|
</Toggle>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const sections = [
|
|
{ id: "offon", label: "Off / On", node: <OffOn /> },
|
|
{ id: "disabled", label: "Disabled", node: <Disabled /> },
|
|
]
|
|
|
|
const meta = { title: "Components/Form/Toggle", component: Toggle, parameters: { layout: "padded" } } satisfies Meta<typeof Toggle>
|
|
export default meta
|
|
type Story = StoryObj<typeof meta>
|
|
|
|
// Representative-page layout (Storybook presentation only). StateBoard's own auto-fill grid cannot give equal columns that
|
|
// fill a cell, so each specimen sits in its own single-column StateBoard (min larger than any column) inside a grid.
|
|
const specimen = (id: string): BoardState => {
|
|
const s = sections.find((section) => section.id === id)!
|
|
return { id: s.id, label: s.label, children: s.node }
|
|
}
|
|
function SpecimenGrid({ states }: { states: BoardState[] }) {
|
|
return (
|
|
<div className="grid items-start gap-6 [grid-template-columns:repeat(auto-fill,minmax(min(100%,240px),1fr))] min-[1000px]:grid-cols-3">
|
|
{states.map((state) => (
|
|
<StateBoard key={state.id} min={10000} states={[state]} />
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Representative Story: the common Story page; the sections group the same specimens.
|
|
// Overview is renamed Variants. On PC (iframe width 1000px and up) Variants and States use the first two of the same 3 equal
|
|
// columns as the rows below, and the third column stays empty; below that they stack.
|
|
export const All: Story = {
|
|
name: "Toggle",
|
|
parameters: { layout: "fullscreen", controls: { disable: true } },
|
|
render: () => (
|
|
<StoryPage>
|
|
<StoryPageHeader title="Toggle" />
|
|
<div className="grid gap-x-6 min-[1000px]:grid-cols-3">
|
|
<StorySection title="Variants">
|
|
<StateBoard min={10000} states={[specimen("offon")]} />
|
|
</StorySection>
|
|
<StorySection title="States">
|
|
<StateBoard min={10000} states={[specimen("disabled")]} />
|
|
</StorySection>
|
|
</div>
|
|
<StorySection title="단독 · 연결 그룹">
|
|
<SpecimenGrid
|
|
states={[
|
|
{ id: "single-toggle", label: "단독", children: <Toggle variant="outline">알림</Toggle> },
|
|
{ id: "group-single", label: "연결 그룹 · 단일 선택", children: <ToggleGroup type="single" variant="outline" defaultValue="left" aria-label="정렬"><ToggleGroupItem value="left">왼쪽</ToggleGroupItem><ToggleGroupItem value="center">가운데</ToggleGroupItem><ToggleGroupItem value="right">오른쪽</ToggleGroupItem></ToggleGroup> },
|
|
{ id: "group-multiple", label: "연결 그룹 · 다중 선택", children: <ToggleGroup type="multiple" variant="outline" defaultValue={["bold"]} aria-label="글자 스타일"><ToggleGroupItem value="bold">굵게</ToggleGroupItem><ToggleGroupItem value="italic">기울임</ToggleGroupItem></ToggleGroup> },
|
|
]}
|
|
/>
|
|
</StorySection>
|
|
<StorySection title="Sizes">
|
|
<Sizes />
|
|
</StorySection>
|
|
</StoryPage>
|
|
),
|
|
}
|
|
|
|
export const States: Story = { tags: ["verification"], render: () => <OffOn /> }
|
|
export const IconOnly: Story = { tags: ["verification"], name: "Icon Only", render: () => <WithIcon /> }
|
|
export const DisabledToggle: Story = { tags: ["verification"], name: "Disabled", render: () => <Disabled /> }
|
|
|
|
export const ContentNodes: Story = {
|
|
tags: ["verification"],
|
|
render: () => (
|
|
<div className="flex flex-wrap gap-3">
|
|
<Toggle aria-label="Array icon">{[<StarIcon key="icon" />]}</Toggle>
|
|
<Toggle aria-label="Fragment icon"><><StarIcon /></></Toggle>
|
|
<Toggle><><StarIcon />즐겨찾기</></Toggle>
|
|
<Toggle><StarIcon /><span>즐겨찾기</span></Toggle>
|
|
</div>
|
|
),
|
|
}
|