import type { Meta, StoryObj } from "@storybook/react-vite" import type { ComponentProps } from "react" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@hm-design/ui" import { StateBoard } from "@/components/story-board" import { STORY_KICKER, StoryPage, StoryPageHeader, StorySection } from "@/components/story-page" // Representative "Tabs" Story shows default Primary, explicit API comparisons and Secondary; Legacy stays verification-only. // Selected connects to the content surface, so every specimen puts the Tabs on a plain bordered panel supplied by the consumer // (TabsContent here). The panel is layout, not part of Tabs. type Variant = "primary" | "secondary" type ListProps = Pick, "fill" | "panelSurface"> // Primary = Fill + panelSurface="content", Secondary = Fill + panelSurface="surface" (2026-10-06 사용자 결정). const variantProps = { primary: { listProps: {}, panel: "border bg-background" }, secondary: { listProps: { fill: true, panelSurface: "surface" }, panel: "border bg-card" }, } as const function Specimen({ variant = "primary", defaultValue = "menu2", disabledSecond, count = 3, labels, listProps, }: { variant?: Variant defaultValue?: string disabledSecond?: boolean count?: number labels?: string[] listProps?: ListProps }) { const { listProps: defaults, panel } = variantProps[variant] const items = labels?.map((label, i) => ({ value: `menu${i + 1}`, label })) ?? Array.from({ length: count }, (_, i) => ({ value: `menu${i + 1}`, label: `메뉴 ${i + 1}` })) return ( {items.map(({ value, label }, i) => ( {label} ))} {items.map(({ value }, i) => ( 메뉴 {i + 1} 콘텐츠 ))} ) } const longLabels = ["기본 정보", "보상 협의 및 계약 진행 현황", "토지 및 지장물 조사 내역"] function LongLabelSpecimens() { return (

짧은 label

긴 label 1개

긴 label 여러 개

좁은 container · nowrap · overflow controls

) } const meta = { title: "Components/Navigation/Tabs", component: Tabs, parameters: { layout: "padded", docs: { description: { component: "기본 TabsList는 Primary(fill=true, panelSurface=\"content\")입니다. 대표 Story는 기본 API와 명시적 Primary, Secondary(fill + panelSurface=\"surface\"), Primary Overflow를 비교합니다. Legacy non-fill은 fill={false}로 유지하며 숨김 Compatibility에서 확인합니다. 높이 34, 좌우 16, 상단 radius 4, 16/24/700과 8px 곡선 연결은 기존 값 그대로입니다. Selected는 패널 배경과 이어지고 Hover는 비선택에만 Black 20%입니다. 넘칠 때만 이전/다음 이동 버튼이 나타납니다. 곡선 반경 8px과 비선택 합성색의 과거 REVIEW / TEMPORARY 이력은 유지합니다.", }, }, }, } satisfies Meta export default meta type Story = StoryObj export const All: Story = { name: "Tabs", parameters: { layout: "fullscreen", controls: { disable: true } }, render: () => ( }, ]} /> }, { id: "primary-explicit", label: "Primary · fill + panelSurface content", noteAfter: true, note: "두 Props를 명시해도 기본 API와 같은 Primary입니다.", children: }, ]} /> }, ]} /> }, ]} /> ), } // Verification: Primary 기준 상태와 Secondary 비교. Secondary도 같은 CSS 경로를 쓴다. export const Secondary: Story = { tags: ["verification"], render: () =>
} export const SelectedFirst: Story = { tags: ["verification"], name: "Selected First", render: () =>
} export const SelectedLast: Story = { tags: ["verification"], name: "Selected Last", render: () =>
} export const Focus: Story = { tags: ["verification"], render: () =>
, play: async ({ canvasElement }) => canvasElement.querySelector('[role="tab"][data-state="active"]')?.focus(), } export const Disabled: Story = { tags: ["verification"], render: () =>
} export const Overflow: Story = { tags: ["verification"], render: () =>
} export const SecondaryOverflow: Story = { tags: ["verification"], name: "Secondary Overflow", render: () =>
} export const LongLabel: Story = { tags: ["verification"], name: "Long Label", render: () => } export const Compatibility: Story = { tags: ["verification"], render: () => ( }, { id: "legacy-surface", label: "TabsList fill={false} panelSurface surface", noteAfter: true, note: "이전 기본값을 모두 명시한 경우에도 같은 Legacy 디자인입니다.", children: }, ]} /> }, ]} /> ), }