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" import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page" // Table is a static display primitive, so the representative Story is a board of specimens. Checkbox and Badge appear only as // cell content (Story composition); Table has no dependency on them and no selection, sorting or paging behavior. // The Story keeps the selected rows in local state and passes `data-state="selected"` itself. const projects = [ { id: "P-001", name: "한맥 노선 정비", status: "진행", amount: 1280000 }, { id: "P-002", name: "시설물 점검 리포트", status: "완료", amount: 450000 }, { id: "P-003", name: "교량 안전 진단", status: "대기", amount: 9800000 }, ] const won = (n: number) => n.toLocaleString("ko-KR") // `showCaption={false}` drops the visible caption on the representative page; the caption text becomes the table's // accessible name. The verification Story keeps the TableCaption. function BasicTable({ showCaption = true }: { showCaption?: boolean }) { return ( 번호 프로젝트 상태 금액 {projects.map((p) => ( {p.id} {p.name} {p.status} {won(p.amount)} ))} 합계 {won(projects.reduce((s, p) => s + p.amount, 0))} {showCaption ? 프로젝트별 계약 금액(원) : null}
) } function SelectableTable() { const [selected, setSelected] = useState(["P-002"]) const toggle = (id: string) => setSelected((s) => (s.includes(id) ? s.filter((x) => x !== id) : [...s, id])) return ( setSelected(v ? projects.map((p) => p.id) : [])} /> 프로젝트 금액 {projects.map((p) => ( toggle(p.id)} /> {p.name} {won(p.amount)} ))}
) } function LongTextTable() { return ( 제목 비고 긴 텍스트는 셀 안에서 줄바꿈되고 행 높이만 늘어납니다. 공백 없는 문자열도 열 폭을 밀어내지 않습니다. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 짧은 행 -
) } function WideTable() { const cols = Array.from({ length: 10 }, (_, i) => `컬럼 ${i + 1}`) return ( {cols.map((c) => ( {c} ))} {Array.from({ length: 3 }, (_, r) => ( {cols.map((c) => ( {`값 ${r + 1}-${c.slice(3)}`} ))} ))}
) } const box = (node: ReactNode) =>
{node}
const sections = [ { id: "basic", label: "Basic", node: }, { id: "selected", label: "Selected row", node: }, { id: "long", label: "Long text", node: }, { id: "wide", label: "Horizontal overflow", node: }, ] const meta = { title: "Components/Data & Display/Table", component: Table, parameters: { layout: "padded" } } satisfies 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 // (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 }) { return (
{ids.map((id) => { const s = sections.find((section) => section.id === id)! return (
{box(s.node)}
}]} />
) })} ) } // 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: () => ( ), } export const Basic: Story = { tags: ["verification"], render: () => box() } export const Selected: Story = { tags: ["verification"], render: () => box() } export const LongText: Story = { tags: ["verification"], name: "Long Text", render: () => box() } export const Overflow: Story = { tags: ["verification"], name: "Horizontal Overflow", render: () => box() }