style(storybook): refine data display story layout
This commit is contained in:
1 parent
3868ca09c7
commit
bef164489b
10 files changed
+286
-70
No files matched your search
@@ -57,20 +57,45 @@ const meta = { title: "Components/Data & Display/Aspect Ratio", component: Aspec
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const board = (...ids: string[]) => (
|
||||
<StateBoard min={340} states={sections.filter((s) => 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<string, 2> }) {
|
||||
return (
|
||||
<div className={`grid items-start gap-6 [grid-template-columns:repeat(auto-fill,minmax(min(100%,200px),1fr))] ${specimenColumns[columns]}`}>
|
||||
{ids.map((id) => {
|
||||
const s = sections.find((section) => section.id === id)!
|
||||
return (
|
||||
<div key={id} className={spans[id] ? "min-[1000px]:col-span-2" : undefined}>
|
||||
<StateBoard min={10000} states={[{ id: s.id, label: s.label, children: s.node }]} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 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: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="Aspect Ratio" />
|
||||
<StorySection title="Overview">{board("r169")}</StorySection>
|
||||
<StorySection title="Variants">{board("r43", "r11", "video")}</StorySection>
|
||||
<StorySection title="Usage">{board("card")}</StorySection>
|
||||
<StorySection title="Variants">
|
||||
<SpecimenGrid ids={["r169", "r43", "r11", "video"]} columns={4} />
|
||||
</StorySection>
|
||||
<StorySection title="Usage">
|
||||
<SpecimenGrid ids={["card"]} columns={4} spans={{ card: 2 }} />
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
@@ -76,20 +76,44 @@ const meta = { title: "Components/Data & Display/Avatar", component: Avatar, par
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const board = (...ids: string[]) => (
|
||||
<StateBoard min={320} states={sections.filter((s) => 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<string, 2> }) {
|
||||
return (
|
||||
<div className={`grid items-start gap-6 [grid-template-columns:repeat(auto-fill,minmax(min(100%,240px),1fr))] ${specimenColumns[columns]}`}>
|
||||
{ids.map((id) => {
|
||||
const s = sections.find((section) => section.id === id)!
|
||||
return (
|
||||
<div key={id} className={spans[id] ? "min-[1000px]:col-span-2" : undefined}>
|
||||
<StateBoard min={10000} states={[{ id: s.id, label: s.label, children: s.node }]} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 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: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="Avatar" />
|
||||
<StorySection title="Overview">{board("image")}</StorySection>
|
||||
<StorySection title="States">{board("fallback")}</StorySection>
|
||||
<StorySection title="Usage">{board("composition")}</StorySection>
|
||||
<StorySection title="Variants">
|
||||
<SpecimenGrid ids={["image", "fallback"]} columns={3} />
|
||||
</StorySection>
|
||||
<StorySection title="Usage">
|
||||
<SpecimenGrid ids={["composition"]} columns={3} />
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
@@ -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<typeof meta>
|
||||
|
||||
// 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 (
|
||||
<div className={`grid items-start gap-6 [grid-template-columns:repeat(auto-fill,minmax(min(100%,280px),1fr))] ${specimenColumns[columns]}`}>
|
||||
{states.map((state) => (
|
||||
<StateBoard key={state.id} min={10000} states={[state]} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 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: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="Badge" />
|
||||
<StorySection title="Overview">
|
||||
<Badge>Badge</Badge>
|
||||
</StorySection>
|
||||
<StorySection title="Variants">
|
||||
<StateBoard
|
||||
min={280}
|
||||
<SpecimenGrid
|
||||
columns={2}
|
||||
states={[
|
||||
{
|
||||
id: "variants",
|
||||
@@ -67,8 +84,8 @@ export const All: Story = {
|
||||
/>
|
||||
</StorySection>
|
||||
<StorySection title="Usage">
|
||||
<StateBoard
|
||||
min={280}
|
||||
<SpecimenGrid
|
||||
columns={2}
|
||||
states={[
|
||||
{ id: "long", label: "Long text", children: <div className="max-w-[160px]"><Badge>A very long badge label</Badge></div> },
|
||||
{
|
||||
|
||||
@@ -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 (
|
||||
<div className="grid grid-cols-1 items-start gap-x-6 gap-y-8 min-[640px]:grid-cols-2 min-[900px]:grid-cols-3">
|
||||
{states.map(({ id, label, children }) => (
|
||||
<section className="grid min-w-0 content-start gap-3" key={id} data-overview-state={id} aria-label={label}>
|
||||
<header className="grid gap-2 border-b border-black/10 pb-2">
|
||||
<h3 className="text-sm font-medium text-muted-foreground">{label}</h3>
|
||||
</header>
|
||||
{children}
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function FormSpecimen() {
|
||||
return (
|
||||
<Card>
|
||||
@@ -42,7 +59,7 @@ function FormSpecimen() {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid gap-2">
|
||||
<label htmlFor="card-form-name" className="text-sm font-medium text-muted-foreground">
|
||||
<label htmlFor="card-form-name" className="text-sm font-medium text-foreground">
|
||||
프로젝트명
|
||||
</label>
|
||||
<Input id="card-form-name" size="medium" placeholder="프로젝트명을 입력하세요" />
|
||||
@@ -63,7 +80,7 @@ const meta = {
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
// Representative Story: the common Story page (Overview → Variants → Usage) with every ui_viz case. Named after the component, as in ui_viz.
|
||||
// Representative Story: the common Story page (Variants → Usage) with every ui_viz case. Named after the component, as in ui_viz.
|
||||
// It is the only Card entry in the default sidebar, so Storybook shows Card as a single 1-depth item.
|
||||
export const All: Story = {
|
||||
name: "Card",
|
||||
@@ -71,20 +88,17 @@ export const All: Story = {
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="Card" />
|
||||
<StorySection title="Overview">
|
||||
<StateBoard min={360} states={[{ id: "surface", label: "White", children: <Specimen /> }]} />
|
||||
</StorySection>
|
||||
<StorySection title="Variants">
|
||||
<StateBoard
|
||||
min={360}
|
||||
<CardBoard
|
||||
states={[
|
||||
{ id: "surface", label: "White", children: <Specimen /> },
|
||||
{ id: "content", label: "Content (Beige)", children: <Specimen surface="content" /> },
|
||||
{ id: "action", label: "Title + Action", children: <Specimen action /> },
|
||||
]}
|
||||
/>
|
||||
</StorySection>
|
||||
<StorySection title="Usage">
|
||||
<StateBoard min={360} states={[{ id: "form", label: "본문 조합 · Input", children: <FormSpecimen /> }]} />
|
||||
<CardBoard states={[{ id: "form", label: "본문 조합 · Input", children: <FormSpecimen /> }]} />
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
|
||||
@@ -142,20 +142,45 @@ const meta = { title: "Components/Data & Display/Carousel", component: Carousel,
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const board = (...ids: string[]) => (
|
||||
<StateBoard min={460} states={sections.filter((s) => 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 Media example spans two columns; carousels 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<string, 2> }) {
|
||||
return (
|
||||
<div className={`grid items-start gap-6 [grid-template-columns:repeat(auto-fill,minmax(min(100%,200px),1fr))] ${specimenColumns[columns]}`}>
|
||||
{ids.map((id) => {
|
||||
const s = sections.find((section) => section.id === id)!
|
||||
return (
|
||||
<div key={id} className={spans[id] ? "min-[1000px]:col-span-2" : undefined}>
|
||||
<StateBoard min={10000} states={[{ id: s.id, label: s.label, children: s.node }]} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Representative Story: the common Story page; the sections group the same specimens.
|
||||
// Representative Story: Overview (Basic) is merged into Variants (Basic, Multiple items, Loop, Vertical), 4 columns on PC; Media stays under Usage.
|
||||
export const All: Story = {
|
||||
name: "Carousel",
|
||||
parameters: { layout: "fullscreen", controls: { disable: true } },
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="Carousel" />
|
||||
<StorySection title="Overview">{board("basic")}</StorySection>
|
||||
<StorySection title="Variants">{board("multiple", "loop", "vertical")}</StorySection>
|
||||
<StorySection title="Usage">{board("media")}</StorySection>
|
||||
<StorySection title="Variants">
|
||||
<SpecimenGrid ids={["basic", "multiple", "loop", "vertical"]} columns={4} />
|
||||
</StorySection>
|
||||
<StorySection title="Usage">
|
||||
<SpecimenGrid ids={["media"]} columns={4} spans={{ media: 2 }} />
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
@@ -49,20 +49,44 @@ const meta = { title: "Components/Data & Display/Progress", component: Progress,
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const board = (...ids: string[]) => (
|
||||
<StateBoard min={320} states={sections.filter((s) => 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<string, 2> }) {
|
||||
return (
|
||||
<div className={`grid items-start gap-6 [grid-template-columns:repeat(auto-fill,minmax(min(100%,240px),1fr))] ${specimenColumns[columns]}`}>
|
||||
{ids.map((id) => {
|
||||
const s = sections.find((section) => section.id === id)!
|
||||
return (
|
||||
<div key={id} className={spans[id] ? "min-[1000px]:col-span-2" : undefined}>
|
||||
<StateBoard min={10000} states={[{ id: s.id, label: s.label, children: s.node }]} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Representative Story: the common Story page; the sections group the same specimens.
|
||||
// Representative Story: Overview (50%) is merged into States (0%, 50%, 100%); max = 200 and Interactive stay under Usage. 3 columns on PC.
|
||||
export const All: Story = {
|
||||
name: "Progress",
|
||||
parameters: { layout: "fullscreen", controls: { disable: true } },
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="Progress" />
|
||||
<StorySection title="Overview">{board("middle")}</StorySection>
|
||||
<StorySection title="States">{board("zero", "full")}</StorySection>
|
||||
<StorySection title="Usage">{board("custom-maximum", "interactive")}</StorySection>
|
||||
<StorySection title="States">
|
||||
<SpecimenGrid ids={["zero", "middle", "full"]} columns={3} />
|
||||
</StorySection>
|
||||
<StorySection title="Usage">
|
||||
<SpecimenGrid ids={["custom-maximum", "interactive"]} columns={3} />
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
@@ -77,20 +77,44 @@ const meta = { title: "Components/Data & Display/Scroll Area", component: Scroll
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const board = (...ids: string[]) => (
|
||||
<StateBoard min={320} states={sections.filter((s) => 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
|
||||
// (300px 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<string, 2> }) {
|
||||
return (
|
||||
<div className={`grid items-start gap-6 [grid-template-columns:repeat(auto-fill,minmax(min(100%,300px),1fr))] ${specimenColumns[columns]}`}>
|
||||
{ids.map((id) => {
|
||||
const s = sections.find((section) => section.id === id)!
|
||||
return (
|
||||
<div key={id} className={spans[id] ? "min-[1000px]:col-span-2" : undefined}>
|
||||
<StateBoard min={10000} states={[{ id: s.id, label: s.label, children: s.node }]} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Representative Story: the common Story page; the sections group the same specimens.
|
||||
// Representative Story: Overview (Vertical) is merged into Variants (Vertical, Horizontal, Both axes); Table-like stays under Usage. 3 columns on PC.
|
||||
export const All: Story = {
|
||||
name: "Scroll Area",
|
||||
parameters: { layout: "fullscreen", controls: { disable: true } },
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="Scroll Area" />
|
||||
<StorySection title="Overview">{board("vertical")}</StorySection>
|
||||
<StorySection title="Variants">{board("horizontal", "both")}</StorySection>
|
||||
<StorySection title="Usage">{board("table-like")}</StorySection>
|
||||
<StorySection title="Variants">
|
||||
<SpecimenGrid ids={["vertical", "horizontal", "both"]} columns={3} />
|
||||
</StorySection>
|
||||
<StorySection title="Usage">
|
||||
<SpecimenGrid ids={["table-like"]} columns={3} />
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import { Separator } 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 meta = {
|
||||
@@ -20,15 +20,35 @@ const meta = {
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
// 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({ states, columns }: { states: BoardState[]; columns: 2 | 3 | 4 }) {
|
||||
return (
|
||||
<div className={`grid items-start gap-6 [grid-template-columns:repeat(auto-fill,minmax(min(100%,240px),1fr))] ${specimenColumns[columns]}`}>
|
||||
{states.map((state) => (
|
||||
<StateBoard key={state.id} min={10000} states={[state]} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Representative Story: Overview (Horizontal) and Variants (Vertical, Semantic) are merged into one Variants section. 3 columns on PC.
|
||||
export const All: Story = {
|
||||
name: "Separator",
|
||||
parameters: { layout: "fullscreen", controls: { disable: true } },
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="Separator" />
|
||||
<StorySection title="Overview">
|
||||
<StateBoard
|
||||
min={280}
|
||||
<StorySection title="Variants">
|
||||
<SpecimenGrid
|
||||
columns={3}
|
||||
states={[
|
||||
{
|
||||
id: "horizontal",
|
||||
@@ -41,13 +61,6 @@ export const All: Story = {
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</StorySection>
|
||||
<StorySection title="Variants">
|
||||
<StateBoard
|
||||
min={280}
|
||||
states={[
|
||||
{
|
||||
id: "vertical",
|
||||
label: "Vertical",
|
||||
|
||||
@@ -53,19 +53,41 @@ const meta = { title: "Components/Data & Display/Skeleton", component: Skeleton,
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const board = (...ids: string[]) => (
|
||||
<StateBoard min={320} states={sections.filter((s) => 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<string, 2> }) {
|
||||
return (
|
||||
<div className={`grid items-start gap-6 [grid-template-columns:repeat(auto-fill,minmax(min(100%,240px),1fr))] ${specimenColumns[columns]}`}>
|
||||
{ids.map((id) => {
|
||||
const s = sections.find((section) => section.id === id)!
|
||||
return (
|
||||
<div key={id} className={spans[id] ? "min-[1000px]:col-span-2" : undefined}>
|
||||
<StateBoard min={10000} states={[{ id: s.id, label: s.label, children: s.node }]} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Representative Story: the common Story page; the sections group the same specimens.
|
||||
// Representative Story: Overview / Usage are merged into one Variants section (Text, Avatar + text, Card / list loading). 3 columns on PC.
|
||||
export const All: Story = {
|
||||
name: "Skeleton",
|
||||
parameters: { layout: "fullscreen", controls: { disable: true } },
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="Skeleton" />
|
||||
<StorySection title="Overview">{board("text")}</StorySection>
|
||||
<StorySection title="Usage">{board("avatar", "card")}</StorySection>
|
||||
<StorySection title="Variants">
|
||||
<SpecimenGrid ids={["text", "avatar", "card"]} columns={3} />
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, type ReactNode } from "react"
|
||||
import { useState, type CSSProperties, type ReactNode } from "react"
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import { Badge, Checkbox, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow } from "@hm-design/ui"
|
||||
import { StateBoard } from "@/components/story-board"
|
||||
@@ -144,20 +144,48 @@ const meta = { title: "Components/Data & Display/Table", component: Table, param
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const board = (...ids: string[]) => (
|
||||
<StateBoard min={460} states={sections.filter((s) => ids.includes(s.id)).map((s) => ({ id: s.id, label: s.label, wide: true, children: box(s.node) }))} />
|
||||
)
|
||||
// Representative-page layout (Storybook presentation only): equal columns on PC (iframe width 1000px and up), fewer below that
|
||||
// (400px 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 default 520px Table minimum would make the 445px columns scroll sideways, so Basic, Selected row and Long text reset the
|
||||
// documented `--hm-table-min-width` token for their own cell (same as the Tabs usage panel): the table then follows its column
|
||||
// and long text wraps there. Horizontal overflow keeps its inline `minWidth: 1100`, so it still overflows and scrolls inside its box.
|
||||
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<string, 2> }) {
|
||||
return (
|
||||
<div className={`grid items-start gap-6 [grid-template-columns:repeat(auto-fill,minmax(min(100%,400px),1fr))] ${specimenColumns[columns]}`}>
|
||||
{ids.map((id) => {
|
||||
const s = sections.find((section) => section.id === id)!
|
||||
return (
|
||||
<div key={id} className={spans[id] ? "min-[1000px]:col-span-2" : undefined}>
|
||||
<StateBoard min={10000} states={[{ id: s.id, label: s.label, children: s.id === "wide" ? box(s.node) : <div style={{ "--hm-table-min-width": "0px" } as CSSProperties}>{box(s.node)}</div> }]} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 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: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="Table" />
|
||||
<StorySection title="Overview">{board("basic")}</StorySection>
|
||||
<StorySection title="States">{board("selected")}</StorySection>
|
||||
<StorySection title="Usage">{board("long", "wide")}</StorySection>
|
||||
<StorySection title="Variants">
|
||||
<SpecimenGrid ids={["basic", "selected"]} columns={2} />
|
||||
</StorySection>
|
||||
<StorySection title="Usage">
|
||||
<SpecimenGrid ids={["long", "wide"]} columns={2} />
|
||||
</StorySection>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user