From bef164489b1e7f140a9aedd9b381300fce3e3292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=88=98=ED=98=84?= Date: Mon, 9 Nov 2026 16:15:22 +0900 Subject: [PATCH] style(storybook): refine data display story layout --- src/components/aspect-ratio.stories.tsx | 37 ++++++++++++++++++---- src/components/avatar.stories.tsx | 36 +++++++++++++++++---- src/components/badge.stories.tsx | 33 ++++++++++++++----- src/components/card.stories.tsx | 32 +++++++++++++------ src/components/carousel.stories.tsx | 37 ++++++++++++++++++---- src/components/progress.stories.tsx | 36 +++++++++++++++++---- src/components/scroll-area.stories.tsx | 36 +++++++++++++++++---- src/components/separator.stories.tsx | 35 ++++++++++++++------- src/components/skeleton.stories.tsx | 32 ++++++++++++++++--- src/components/table.stories.tsx | 42 ++++++++++++++++++++----- 10 files changed, 286 insertions(+), 70 deletions(-) diff --git a/src/components/aspect-ratio.stories.tsx b/src/components/aspect-ratio.stories.tsx index b2ecacf..3fcbf40 100644 --- a/src/components/aspect-ratio.stories.tsx +++ b/src/components/aspect-ratio.stories.tsx @@ -57,20 +57,45 @@ const meta = { title: "Components/Data & Display/Aspect Ratio", component: Aspec 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 +// (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/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: the common Story page; the sections group the same specimens. +// Representative Story: Overview / States / Usage are regrouped into Variants (Basic, Selected row) and Usage (Long text, +// Horizontal overflow), two equal columns each on PC. export const All: Story = { name: "Table", parameters: { layout: "fullscreen", controls: { disable: true } }, render: () => ( - {board("basic")} - {board("selected")} - {board("long", "wide")} + + + + + + ), }