diff --git a/src/components/accordion.stories.tsx b/src/components/accordion.stories.tsx index 3ccbd66..7e9763e 100644 --- a/src/components/accordion.stories.tsx +++ b/src/components/accordion.stories.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from "react" import type { Meta, StoryObj } from "@storybook/react-vite" import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@hm-design/ui" -import { StateBoard } from "@/components/story-board" +import { StateBoard, type BoardState } from "@/components/story-board" import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page" // The representative "Accordion" Story puts each Radix mode on one page with real, clickable items. The same sections also exist @@ -115,8 +115,21 @@ const meta = { title: "Components/Disclosure/Accordion", parameters: { layout: " export default meta type Story = StoryObj -const board = (...ids: string[]) => ( - ids.includes(s.id)).map((s) => ({ id: s.id, label: s.label, children: s.node }))} /> +// Representative-page layout (Storybook presentation only): equal columns on PC (iframe width 1000px and up), one column +// below that. Each specimen sits in its own single-column StateBoard (min larger than any column), so the title, divider and +// specimen start line up, unused columns stay empty and `items-start` lets an opened item grow without touching its neighbors. +function StateGrid({ states, cols }: { states: BoardState[]; cols: 2 | 3 }) { + return ( +
+ {states.map((state) => ( + + ))} +
+ ) +} + +const board = (cols: 2 | 3, ...ids: string[]) => ( + ids.includes(s.id)).map((s) => ({ id: s.id, label: s.label, children: s.node }))} /> ) // Representative Story: the common Story page; the sections group the same specimens. @@ -126,12 +139,8 @@ export const All: Story = { render: () => ( - {board("single")} - - {board("collapsible-single", "multiple")} - - {board("disabled")} - {board("long")} + {board(3, "single", "collapsible-single", "multiple")} + {board(3, "disabled", "long")} ), } diff --git a/src/components/alert-dialog.stories.tsx b/src/components/alert-dialog.stories.tsx index 9742f39..70a70fa 100644 --- a/src/components/alert-dialog.stories.tsx +++ b/src/components/alert-dialog.stories.tsx @@ -11,6 +11,7 @@ import { AlertDialogTrigger, Button, } from "@hm-design/ui" +import { StateBoard, type BoardState } from "@/components/story-board" import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page" // Alert Dialog is a modal Radix overlay (Portal) for a decision that must be confirmed or cancelled: no Close (X), no outside @@ -68,17 +69,34 @@ export default meta type Story = StoryObj // Representative Story: the common Story page; each trigger opens the real dialog. +// Representative-page layout (Storybook presentation only): equal columns on PC (iframe width 1000px and up), fewer below that +// (200px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle and divider +// has the width of its column; a section with fewer specimens than columns leaves the remaining columns empty. The trigger keeps its +// own size (the wrapper div stretches, the inline-flex trigger does not). Overlays open in a portal, so this layout never affects them. +function SpecimenGrid({ states, columns = 4 }: { states: BoardState[]; columns?: 4 | 5 }) { + return ( +
+ {states.map((state) => ( + {state.children}
}]} /> + ))} + + ) +} + +// Representative Story: Overview and Variants are merged into one Variants section. export const All: Story = { name: "Alert Dialog", parameters: { layout: "fullscreen", controls: { disable: true } }, render: () => ( - - - - + }, + { id: "destructive", label: "Destructive", children: }, + ]} + /> ), diff --git a/src/components/alert.stories.tsx b/src/components/alert.stories.tsx index e02feba..2ff3953 100644 --- a/src/components/alert.stories.tsx +++ b/src/components/alert.stories.tsx @@ -27,9 +27,9 @@ export const All: Story = { render: () => ( - + ), }, - ]} - /> - - - -const board = (...ids: string[]) => ( - ids.includes(s.id)).map((s) => ({ id: s.id, label: s.label, children: s.node }))} /> -) +// Representative-page layout (Storybook presentation only): equal columns on PC (iframe width 1000px and up), fewer below that +// (200px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle and divider +// has the width of its column; a section with fewer specimens than columns leaves the remaining columns empty. +// The Card example spans two columns (about its previous width); the ratio boxes fill their column by design. +const specimenColumns = { + 2: "min-[1000px]:grid-cols-2", + 3: "min-[1000px]:grid-cols-3", + 4: "min-[1000px]:grid-cols-4", +} as const + +function SpecimenGrid({ ids, columns, spans = {} }: { ids: string[]; columns: 2 | 3 | 4; spans?: Record }) { + return ( +
+ {ids.map((id) => { + const s = sections.find((section) => section.id === id)! + return ( +
+ +
+ ) + })} +
+ ) +} // Representative Story: the common Story page; the sections group the same specimens. +// Representative Story: Overview (16:9) is merged into Variants (16:9, 4:3, 1:1, Video), 4 columns on PC; Card stays under Usage. export const All: Story = { name: "Aspect Ratio", parameters: { layout: "fullscreen", controls: { disable: true } }, render: () => ( - {board("r169")} - {board("r43", "r11", "video")} - {board("card")} + + + + + + ), } diff --git a/src/components/avatar.stories.tsx b/src/components/avatar.stories.tsx index 92c3c17..1dd74b7 100644 --- a/src/components/avatar.stories.tsx +++ b/src/components/avatar.stories.tsx @@ -76,20 +76,44 @@ const meta = { title: "Components/Data & Display/Avatar", component: Avatar, par export default meta type Story = StoryObj -const board = (...ids: string[]) => ( - ids.includes(s.id)).map((s) => ({ id: s.id, label: s.label, children: s.node }))} /> -) +// Representative-page layout (Storybook presentation only): equal columns on PC (iframe width 1000px and up), fewer below that +// (240px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle and divider +// has the width of its column; a section with fewer specimens than columns leaves the remaining columns empty. +const specimenColumns = { + 2: "min-[1000px]:grid-cols-2", + 3: "min-[1000px]:grid-cols-3", + 4: "min-[1000px]:grid-cols-4", +} as const + +function SpecimenGrid({ ids, columns, spans = {} }: { ids: string[]; columns: 2 | 3 | 4; spans?: Record }) { + return ( +
+ {ids.map((id) => { + const s = sections.find((section) => section.id === id)! + return ( +
+ +
+ ) + })} +
+ ) +} // Representative Story: the common Story page; the sections group the same specimens. +// Representative Story: Overview / States are merged into Variants (Image, Fallback); Composition stays under Usage. 3 columns on PC. export const All: Story = { name: "Avatar", parameters: { layout: "fullscreen", controls: { disable: true } }, render: () => ( - {board("image")} - {board("fallback")} - {board("composition")} + + + + + + ), } diff --git a/src/components/badge.stories.tsx b/src/components/badge.stories.tsx index 2078e32..2fab003 100644 --- a/src/components/badge.stories.tsx +++ b/src/components/badge.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite" import { CheckIcon } from "lucide-react" import { Badge } from "@hm-design/ui" -import { StateBoard } from "@/components/story-board" +import { StateBoard, type BoardState } from "@/components/story-board" import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page" const variants = ["default", "secondary", "destructive", "outline"] as const @@ -23,18 +23,35 @@ const meta = { export default meta type Story = StoryObj +// Representative-page layout (Storybook presentation only): equal columns on PC (iframe width 1000px and up), fewer below that +// (280px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle and divider +// has the width of its column; a section with fewer specimens than columns leaves the remaining columns empty. +const specimenColumns = { + 2: "min-[1000px]:grid-cols-2", + 3: "min-[1000px]:grid-cols-3", + 4: "min-[1000px]:grid-cols-4", +} as const + +function SpecimenGrid({ states, columns }: { states: BoardState[]; columns: 2 | 3 | 4 }) { + return ( +
+ {states.map((state) => ( + + ))} +
+ ) +} + +// Representative Story: Overview (a plain Badge, already the default item of Variants / Text) is removed. 2 columns on PC. export const All: Story = { name: "Badge", parameters: { layout: "fullscreen", controls: { disable: true } }, render: () => ( - - Badge - - - A very long badge label }, { diff --git a/src/components/calendar.stories.tsx b/src/components/calendar.stories.tsx index b288222..205df44 100644 --- a/src/components/calendar.stories.tsx +++ b/src/components/calendar.stories.tsx @@ -48,22 +48,22 @@ function TwoMonths() { return box() } -const sections = [ - { id: "single", label: "Single", node: }, - { id: "disabled", label: "Disabled dates", node: }, - { id: "range", label: "Range", node: }, - { id: "months", label: "Two months", node: }, -] +// Same 3-column track as the Form pages (1 column on narrow screens). Each cell is a one-specimen StateBoard so subtitles, dividers and +// specimen tops line up; the wide Two months specimen takes two tracks (the full row on narrow screens). +const Grid = ({ children }: { children: ReactNode }) => ( +
{children}
+) +const Cell = ({ id, label, wide, children }: { id: string; label: string; wide?: boolean; children: ReactNode }) => ( +
+ +
+) // No `component`: Calendar props are a union of selection modes, so Stories without args would not type-check. const meta = { title: "Components/Search & Date/Calendar", parameters: { layout: "padded" } } satisfies Meta export default meta type Story = StoryObj -const board = (...ids: string[]) => ( - ids.includes(s.id)).map((s) => ({ id: s.id, label: s.label, wide: s.id === "months", children: s.node }))} /> -) - // Representative Story: the common Story page; the sections group the same specimens. export const All: Story = { name: "Calendar", @@ -71,9 +71,18 @@ export const All: Story = { render: () => ( - {board("single")} - {board("range", "months")} - {board("disabled")} + + + + + + + + + + + + ), } diff --git a/src/components/card.stories.tsx b/src/components/card.stories.tsx index b1f666b..f918d26 100644 --- a/src/components/card.stories.tsx +++ b/src/components/card.stories.tsx @@ -3,7 +3,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite" import { Button } from "@hm-design/ui" import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, type CardSurface } from "@hm-design/ui" import { Input } from "@hm-design/ui" -import { StateBoard } from "@/components/story-board" +import type { BoardState } from "@/components/story-board" import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page" // Story structure follows Button / Input / Select: one representative "Card" Story shows the ui_viz cases on one page, @@ -34,6 +34,23 @@ function Specimen({ surface = "surface", action, title = "프로젝트 정보", ) } +// Story-only equal-column board for this page: 3 columns on PC, 2 / 1 on narrower widths. Variants and Usage share it, +// so a Usage cell has the same width as one Variants cell. Label markup/colors match StateBoard (shared component untouched). +function CardBoard({ states }: { states: BoardState[] }) { + return ( +
+ {states.map(({ id, label, children }) => ( +
+
+

{label}

+
+ {children} +
+ ))} +
+ ) +} + function FormSpecimen() { return ( @@ -42,7 +59,7 @@ function FormSpecimen() {
-
+ ) +} + +// Representative Story: Overview is merged into Variants (Basic is its first item, then the four sides): 5 columns on PC. export const All: Story = { name: "Tooltip", parameters: { layout: "fullscreen", controls: { disable: true } }, @@ -77,17 +89,26 @@ export const All: Story = { - - - - + }, + { id: "top", label: "Top", children: }, + { id: "right", label: "Right", children: }, + { id: "bottom", label: "Bottom", children: }, + { id: "left", label: "Left", children: }, + ]} + /> -
- - -
+ }, + { id: "disabled", label: "Disabled", children: }, + ]} + />