style(storybook): group overlay, disclosure and navigation story pages
- Sheet + Drawer, Tooltip + Hover Card, Accordion + Collapsible, Dialog + Alert Dialog, Dropdown Menu + Context Menu on one page each (specimens moved to *-examples.tsx; absorbed Stories keep their IDs) - Popover, Tabs content spacing, Breadcrumb, Menubar and Command tidied - Component docs updated to the new representative pages
This commit is contained in:
1 parent
71d504407d
commit
bad2acfc49
35 files changed
+649
-521
No files matched your search
@@ -9,7 +9,7 @@ import { StateBoard, type BoardState } from "@/components/story-board"
|
||||
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
|
||||
|
||||
// Representative "Tabs" Story shows default Primary, explicit API comparisons and Secondary; Legacy stays verification-only.
|
||||
// D-01 applies to every Fill specimen: flat panels use the actual parent surface and padding 24px 0.
|
||||
// Fill panels use the actual parent surface without a nested box. The representative page sets its own content padding.
|
||||
// D-02 is shipped by the public Tabs theme mapping (Primary top White alpha .8). Legacy stays compatibility-only.
|
||||
|
||||
type Variant = "primary" | "secondary"
|
||||
@@ -21,6 +21,8 @@ const variantProps = {
|
||||
secondary: { listProps: { fill: true, panelSurface: "surface" }, surface: "bg-card" },
|
||||
} as const
|
||||
|
||||
const previewPanelSpacing = "px-4 pt-3 pb-6"
|
||||
|
||||
function Specimen({
|
||||
variant = "primary",
|
||||
defaultValue = "menu2",
|
||||
@@ -28,6 +30,7 @@ function Specimen({
|
||||
count = 3,
|
||||
labels,
|
||||
listProps,
|
||||
panelSpacing = "px-0 py-6",
|
||||
}: {
|
||||
variant?: Variant
|
||||
defaultValue?: string
|
||||
@@ -35,6 +38,7 @@ function Specimen({
|
||||
count?: number
|
||||
labels?: string[]
|
||||
listProps?: ListProps
|
||||
panelSpacing?: string
|
||||
}) {
|
||||
const { listProps: defaults, surface } = variantProps[variant]
|
||||
const effectiveListProps: ListProps = listProps ?? defaults
|
||||
@@ -51,7 +55,7 @@ function Specimen({
|
||||
))}
|
||||
</TabsList>
|
||||
{items.map(({ value }, i) => (
|
||||
<TabsContent key={value} value={value} className={legacy ? "rounded-b p-4 text-sm border bg-card" : "px-0 py-6 text-sm"}>
|
||||
<TabsContent key={value} value={value} className={legacy ? "rounded-b p-4 text-sm border bg-card" : `${panelSpacing} text-sm`}>
|
||||
메뉴 {i + 1} 콘텐츠
|
||||
</TabsContent>
|
||||
))}
|
||||
@@ -67,13 +71,16 @@ function SurfaceUsage({ inCard = false }: { inCard?: boolean }) {
|
||||
const id = useId()
|
||||
const [project, setProject] = useState("")
|
||||
const [purpose, setPurpose] = useState("")
|
||||
// 2026-11-13: both examples use the same panel inset (16 / 12 / 24). In the Card, the Card / CardContent padding stays as is;
|
||||
// the extra inset applies only to the panel below the tab header, so the header keeps its size and position.
|
||||
const panelSpacing = previewPanelSpacing
|
||||
const tabs = (
|
||||
<Tabs defaultValue="information" className="min-w-0">
|
||||
<TabsList panelSurface={inCard ? "surface" : "content"} aria-label={inCard ? "Card 내부 프로젝트 정보" : "페이지 프로젝트 정보"}>
|
||||
<TabsTrigger value="information">기본 정보</TabsTrigger>
|
||||
<TabsTrigger value="schedule">일정·산출물</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="information" className="px-0 py-6">
|
||||
<TabsContent value="information" className={panelSpacing}>
|
||||
<div className="grid gap-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor={`${id}-project`}>프로젝트명 (필수)</Label>
|
||||
@@ -88,7 +95,7 @@ function SurfaceUsage({ inCard = false }: { inCard?: boolean }) {
|
||||
{/* 2026-10-23: this panel can be narrower than the Table default 520px minimum (--hm-table-min-width), which made the
|
||||
table container scroll. The documented token is reset for this panel only: the table follows the panel width
|
||||
and long cell text wraps (Table overflow-wrap). */}
|
||||
<TabsContent value="schedule" className="px-0 py-6" style={{ "--hm-table-min-width": "0px" } as CSSProperties}>
|
||||
<TabsContent value="schedule" className={panelSpacing} style={{ "--hm-table-min-width": "0px" } as CSSProperties}>
|
||||
<Table aria-label="프로젝트 산출물 일정">
|
||||
<TableHeader><TableRow><TableHead scope="col">산출물</TableHead><TableHead scope="col">마감일</TableHead></TableRow></TableHeader>
|
||||
<TableBody><TableRow><TableCell>화면 설계서</TableCell><TableCell>10월 14일</TableCell></TableRow></TableBody>
|
||||
@@ -127,17 +134,18 @@ function LongLabelSpecimens() {
|
||||
)
|
||||
}
|
||||
|
||||
// Equal two columns on PC (one column below 900px). Each board holds a single specimen: StateBoard's own auto-fill grid
|
||||
// would leave an empty third column for two items, so `min` is forced above any column width (same approach as Button).
|
||||
// 2026-11-13 (user request): Variants, Behavior and Usage share one grid, equal two columns (24px gap) when the section's
|
||||
// actual content width is 720px or more, one column below. The breakpoint is a container query on the wrapper (its content box,
|
||||
// after the common board indent), so it follows the Storybook preview's usable width, not the iframe viewport.
|
||||
// Each board holds a single specimen: StateBoard's own auto-fill grid would leave an empty column for two items, so `min` is
|
||||
// forced above any column width (same approach as Button). A single child (Behavior) takes the left column; `min-w-0` lets the
|
||||
// Overflow Tabs keep its own internal scroll (previous / next buttons) inside its column instead of widening the page.
|
||||
const TwoColumns = ({ children }: { children: ReactNode }) => (
|
||||
<div className="grid items-start gap-6 min-[900px]:grid-cols-[repeat(2,minmax(0,1fr))]">{children}</div>
|
||||
<div className="@container min-w-0">
|
||||
<div className="grid items-start gap-6 @min-[720px]:grid-cols-[repeat(2,minmax(0,1fr))]">{children}</div>
|
||||
</div>
|
||||
)
|
||||
const SingleBoard = ({ states }: { states: BoardState[] }) => <StateBoard min={10000} states={states} />
|
||||
// Variants: equal three columns on PC (iframe width 1000px and up); fewer columns below that (240px minimum). `min-w-0` lets the
|
||||
// Overflow Tabs keep its own internal scroll (previous / next buttons) inside its column instead of widening the page.
|
||||
const ThreeColumns = ({ children }: { children: ReactNode }) => (
|
||||
<div className="grid items-start gap-6 [grid-template-columns:repeat(auto-fill,minmax(min(100%,240px),1fr))] min-[1000px]:grid-cols-3">{children}</div>
|
||||
)
|
||||
|
||||
const meta = {
|
||||
title: "Components/Navigation/Tabs",
|
||||
@@ -147,7 +155,7 @@ const meta = {
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
"기본 TabsList는 Primary(fill=true, panelSurface=\"content\")입니다. 대표 Story는 기본 Primary, Secondary(fill + panelSurface=\"surface\"), Primary Overflow를 비교하며 명시적 Primary는 숨김 Default API에서 확인합니다. 모든 Fill 예제는 중첩 박스 없는 TabsContent(px-0 py-6)를 사용하며 Usage는 Card 내부 surface와 페이지 content 위의 조합입니다. panelSurface는 List의 선택 면만 바꾸므로 실제 상위 배경에 맞춰 선택합니다. Primary 선택 탭의 gradient는 상단 White alpha 0.8 → content이며 전체 opacity는 1입니다. Secondary는 기존 불투명 White를 유지합니다. Legacy non-fill은 fill={false}로 유지하며 숨김 Compatibility에서 확인합니다. 높이 34, 좌우 16, 상단 radius 4, 16/24/700과 8px 곡선 연결은 기존 값 그대로입니다. Hover는 비선택에만 Black 20%이며 넘칠 때만 이전/다음 이동 버튼이 나타납니다. aria-label과 연결된 입력 Label을 제공하고, 방향키·Home/End·포커스·Disabled·모바일 내부 스크롤을 확인합니다. 곡선 반경 8px과 비선택 합성색의 과거 REVIEW / TEMPORARY 이력은 유지합니다.",
|
||||
"기본 TabsList는 Primary(fill=true, panelSurface=\"content\")입니다. 대표 Story는 Variants에서 기본 Primary와 Secondary(fill + panelSurface=\"surface\")를 비교하고 Behavior에서 Primary Overflow를 확인하며 명시적 Primary는 숨김 Default API에서 확인합니다. 대표 Story의 평면 패널은 좌우 16px, 상단 12px, 하단 24px 여백을 사용합니다. Usage의 Card 예제도 기존 Card 여백은 그대로 두고 탭 헤더 아래 패널에 같은 좌우 16px, 상단 12px, 하단 24px 여백을 적용합니다. panelSurface는 List의 선택 면만 바꾸므로 실제 상위 배경에 맞춰 선택합니다. Primary 선택 탭의 gradient는 상단 White alpha 0.8 → content이며 전체 opacity는 1입니다. Secondary는 기존 불투명 White를 유지합니다. Legacy non-fill은 fill={false}로 유지하며 숨김 Compatibility에서 확인합니다. 높이 34, 좌우 16, 상단 radius 4, 16/24/700과 8px 곡선 연결은 기존 값 그대로입니다. Hover는 비선택에만 Black 20%이며 넘칠 때만 이전/다음 이동 버튼이 나타납니다. aria-label과 연결된 입력 Label을 제공하고, 방향키·Home/End·포커스·Disabled·모바일 내부 스크롤을 확인합니다. 곡선 반경 8px과 비선택 합성색의 과거 REVIEW / TEMPORARY 이력은 유지합니다.",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -159,16 +167,19 @@ export const All: Story = {
|
||||
name: "Tabs",
|
||||
parameters: { layout: "fullscreen", controls: { disable: true } },
|
||||
render: () => (
|
||||
// The common compact Story page (16–24px gutter, 24px section gap) fits this page on a 900px-high PC screen without an override.
|
||||
// The common compact Story page (16–24px gutter, 28px section gap) fits this page on a 900px-high PC screen without an override.
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="Tabs" />
|
||||
<StorySection title="Variants">
|
||||
{/* The former States section (Overflow) is the third Variants item. */}
|
||||
<ThreeColumns>
|
||||
<SingleBoard states={[{ id: "primary", label: "Primary", children: <Specimen /> }]} />
|
||||
<SingleBoard states={[{ id: "secondary", label: "Secondary", children: <Specimen variant="secondary" /> }]} />
|
||||
<SingleBoard states={[{ id: "overflow", label: "Overflow", children: <div className="w-full min-w-0"><Specimen count={20} /></div> }]} />
|
||||
</ThreeColumns>
|
||||
<TwoColumns>
|
||||
<SingleBoard states={[{ id: "primary", label: "Primary", children: <Specimen panelSpacing={previewPanelSpacing} /> }]} />
|
||||
<SingleBoard states={[{ id: "secondary", label: "Secondary", children: <Specimen variant="secondary" panelSpacing={previewPanelSpacing} /> }]} />
|
||||
</TwoColumns>
|
||||
</StorySection>
|
||||
<StorySection title="Behavior">
|
||||
<TwoColumns>
|
||||
<SingleBoard states={[{ id: "overflow", label: "Overflow", children: <div className="w-full min-w-0"><Specimen count={20} panelSpacing={previewPanelSpacing} /></div> }]} />
|
||||
</TwoColumns>
|
||||
</StorySection>
|
||||
<StorySection title="Usage">
|
||||
<TwoColumns>
|
||||
|
||||
Reference in new issue
Block a user