style(storybook): unify page hierarchy and compact layouts

This commit is contained in:
shkim4 committed 2026-11-11 11:28:58 +09:00
1 parent cda60840d8
commit ff55a805d5
21 files changed
+115 -85

No files matched your search

+8
View File
@@ -1,5 +1,13 @@
# Working Context # Working Context
## 2026-11-11 — Storybook 페이지 위계·여백 공통화 (미커밋)
- 공통 프레임 `src/components/story-page.css`: 위·아래 여백 16–24px, H1 28/36/700 margin 0, 설명 Page Header description 토큰(8px·보조색), 제목→첫 내용·섹션 사이 24px(`--sb-page-section-gap`, header margin이 다음 블록 margin과 상쇄), H2 16/24/700(`--sb-page-section-title-*` = dialog body 토큰), 섹션 제목 구분선 8 + 12px. 좌우 여백·SW 기준 폭(2040/1125/787)은 그대로.
- `StorySectionGrid`(story-page.tsx) 추가: 나란히 놓인 섹션의 row gap을 grid가 소유. Chart 7개·Toggle이 사용. `StateBoard` 라벨 구분선 제거, 행 간격 40 → 24px.
- Button `compactPage`·Tabs `py-4!` 덮어쓰기 제거, Foundation Layout 중복 규칙 제거, 폰트 Weight 카드 박스 제거.
- Chart 섹션명을 카드 제목과 겹치지 않는 변형명(Basic·Label·Solid 등)으로 통일, SW 제목 2개(`SW Popup — 속성창`, `SW Viewer — 설정창`) 사이드바 기준으로 정리. SW Story 전용 묶음(`sw-story-stack` 섹션 묶음, Footer 너비 행)은 공통 섹션 간격을 쓰도록 조정 — SW 컴포넌트 CSS는 변경 없음.
- 기준 문서: `docs/guidelines/프로젝트-지침.md` §6.3 Storybook 페이지 프레임 항목.
## 2026-11-05 — Step Header 프로세스 영역 양 끝 직각 ## 2026-11-05 — Step Header 프로세스 영역 양 끝 직각
- 사용자 결정: `SWProcessHeader appearance="multi"`만 프로세스 영역(파일명 옆·창 영역 옆) 위 모서리를 직각으로(`default`는 Strana 6px·덮개 5px 유지 — 처음 두 타입 모두로 들었다가 사용자 정정). `sw-process-header.css` multi 블록에 바탕 `::before`·양 끝 덮개 `border-radius: 0` 1줄. 단계 탭 캡슐·화살표·사다리꼴 면·색·높이 변경 없음. Header 최외곽은 원래 직각. - 사용자 결정: `SWProcessHeader appearance="multi"`만 프로세스 영역(파일명 옆·창 영역 옆) 위 모서리를 직각으로(`default`는 Strana 6px·덮개 5px 유지 — 처음 두 타입 모두로 들었다가 사용자 정정). `sw-process-header.css` multi 블록에 바탕 `::before`·양 끝 덮개 `border-radius: 0` 1줄. 단계 탭 캡슐·화살표·사다리꼴 면·색·높이 변경 없음. Header 최외곽은 원래 직각.
+1
View File
@@ -157,6 +157,7 @@ HM 지정값이 있는 색상, radius, control height, typography 등은 Theme T
- 컴포넌트가 실제 지원하는 default, variant, size, disabled 및 주요 상태를 확인한다. invalid, open 등 해당 컴포넌트에 실제로 있는 상태만 다루고, 없는 상태를 문서화를 위해 만들지 않는다. - 컴포넌트가 실제 지원하는 default, variant, size, disabled 및 주요 상태를 확인한다. invalid, open 등 해당 컴포넌트에 실제로 있는 상태만 다루고, 없는 상태를 문서화를 위해 만들지 않는다.
- Storybook은 구현 Reference Catalog이자 컴포넌트 단위의 검수와 사용법 문서화 도구다. 신규 UI 구현 전 유사 대표/PRIMARY Story를 먼저 확인한다. `Demo/App` 화면은 여러 컴포넌트를 조합한 적용 결과 확인용이며, Demo의 페이지·타일 UI를 Story의 기준으로 쓰지 않는다. - Storybook은 구현 Reference Catalog이자 컴포넌트 단위의 검수와 사용법 문서화 도구다. 신규 UI 구현 전 유사 대표/PRIMARY Story를 먼저 확인한다. `Demo/App` 화면은 여러 컴포넌트를 조합한 적용 결과 확인용이며, Demo의 페이지·타일 UI를 Story의 기준으로 쓰지 않는다.
- 컴포넌트 Story는 현재 HM 대표 Story의 공통 구조를 따른다. 컴포넌트 이름의 대표 Story 하나에 종류·크기·상태를 한 화면으로 모으고, 같은 구역을 `verification` 태그 Story(예: Roles, Sizes, Brand States)로도 둔다. `verification` Story는 메뉴와 검색에서 기본으로 숨기고(`.storybook/main.ts`의 `tags`) 직접 URL과 사이드바 태그 필터로 연다. HM 전용 Story(Playground, Compatibility)도 같은 태그로 숨긴다. 기본 사이드바에는 컴포넌트가 하위 메뉴 없는 1뎁스 항목 하나로만 보이고(대표 Story 하나만 노출되면 Storybook이 컴포넌트 항목에 합친다), 클릭하면 한 페이지에서 전체 검수 내용이 열린다. Story 이름과 순서, 행·열 구성은 현재 HM 유사 대표 Story의 공통 구성을 먼저 따른다. 외부 ui_viz Story는 제공된 경우에만 충돌하지 않는 참고로 사용한다. - 컴포넌트 Story는 현재 HM 대표 Story의 공통 구조를 따른다. 컴포넌트 이름의 대표 Story 하나에 종류·크기·상태를 한 화면으로 모으고, 같은 구역을 `verification` 태그 Story(예: Roles, Sizes, Brand States)로도 둔다. `verification` Story는 메뉴와 검색에서 기본으로 숨기고(`.storybook/main.ts`의 `tags`) 직접 URL과 사이드바 태그 필터로 연다. HM 전용 Story(Playground, Compatibility)도 같은 태그로 숨긴다. 기본 사이드바에는 컴포넌트가 하위 메뉴 없는 1뎁스 항목 하나로만 보이고(대표 Story 하나만 노출되면 Storybook이 컴포넌트 항목에 합친다), 클릭하면 한 페이지에서 전체 검수 내용이 열린다. Story 이름과 순서, 행·열 구성은 현재 HM 유사 대표 Story의 공통 구성을 먼저 따른다. 외부 ui_viz Story는 제공된 경우에만 충돌하지 않는 참고로 사용한다.
- Storybook 페이지 프레임(2026-11-11 확정)은 `src/components/story-page.tsx`·`story-page.css`의 `StoryPage`·`StoryPageHeader`·`StorySection`·`StorySectionGrid`가 공통으로 소유한다. Web·Chart·OrgChart(Admin)의 문서 위계는 H1 페이지 제목 28/36/700 → 설명 14/20/400 보조색(제목 아래 8px) → 24px → H2 섹션 제목 16/24/700과 얇은 구분선 → 12px → 예제이며, 섹션 사이는 24px, `StateBoard`의 H3 라벨은 14/20/500 보조색이고 자체 구분선을 두지 않는다. 위·아래 여백은 16–24px, 좌우 여백은 기존 값(480px까지 16px, 768px부터 60px)을 유지한다. 값은 기존 Component Token을 `--sb-page-*`로 참조하며 새 Design Token이 아니다. 섹션을 나란히 놓을 때는 `StorySectionGrid`를 쓰고, Story별 `!important`·`[&_.sb-page-*]` 덮어쓰기로 간격을 바꾸지 않는다. SW와 노선도(CENTER)는 이 페이지 프레임과 섹션 간격만 공유하고 컴포넌트 크기·글꼴·캔버스·카드 규격은 각자 유지한다. 페이지 제목은 사이드바 이름을 따르며, 한 그룹에 Story가 여럿이면 `그룹 — Story 이름`(예: `SW Header — 화면 너비`)으로 쓴다. 섹션 제목은 페이지 제목과 예제 카드 제목을 반복하지 않는 짧은 변형명(예: Chart `Basic`, `Label`, `Solid`)으로 쓴다.
- 대표 Story의 Focus 구역은 pseudo-state 복제 대신 `play` 함수로 실제 요소에 focus를 준다(ui_viz도 Focus는 정적 specimen을 만들지 않는다). hover 같은 상태가 ui_viz에 없으면 `.storybook/pseudo-states.ts`를 쓰지 않는다. 구역 배치 helper는 `src/components/story-board.tsx`의 `StateBoard`를 쓴다. - 대표 Story의 Focus 구역은 pseudo-state 복제 대신 `play` 함수로 실제 요소에 focus를 준다(ui_viz도 Focus는 정적 specimen을 만들지 않는다). hover 같은 상태가 ui_viz에 없으면 `.storybook/pseudo-states.ts`를 쓰지 않는다. 구역 배치 helper는 `src/components/story-board.tsx`의 `StateBoard`를 쓴다.
- hover, pressed 같은 pseudo state는 Storybook이 `:hover`/`:active` 규칙을 `data-sb-hover`/`data-sb-active` 규칙으로 복제해 고정한다(`.storybook/pseudo-states.ts`). 복제만 하고 값을 새로 정의하지 않으므로 실제 디자인을 보정하는 용도로 쓰지 않는다. Story에서는 `data-sb-*` 속성만 붙인다. - hover, pressed 같은 pseudo state는 Storybook이 `:hover`/`:active` 규칙을 `data-sb-hover`/`data-sb-active` 규칙으로 복제해 고정한다(`.storybook/pseudo-states.ts`). 복제만 하고 값을 새로 정의하지 않으므로 실제 디자인을 보정하는 용도로 쓰지 않는다. Story에서는 `data-sb-*` 속성만 붙인다.
- ui_viz에서 값이 `TBD`인 상태(예: Button Focus)는 별도 specimen을 만들지 않고 키보드로 확인하는 안내만 둔다. ui_viz에 없는 상태(예: Button `aria-invalid`)와 `REFERENCE_ONLY` 항목은 검수 Story에 넣지 않는다. shadcn 호환 API는 `Compatibility` Story로 분리한다. - ui_viz에서 값이 `TBD`인 상태(예: Button Focus)는 별도 specimen을 만들지 않고 키보드로 확인하는 안내만 둔다. ui_viz에 없는 상태(예: Button `aria-invalid`)와 `REFERENCE_ONLY` 항목은 검수 Story에 넣지 않는다. shadcn 호환 API는 `Compatibility` Story로 분리한다.
+3 -3
View File
@@ -9,7 +9,7 @@ import {
AreaStraightColorLabelExample, AreaStraightColorLabelExample,
AreaStraightExample, AreaStraightExample,
} from "@hm-design/chart/examples" } from "@hm-design/chart/examples"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page" import { StoryPage, StoryPageHeader, StorySection, StorySectionGrid } from "@/components/story-page"
// Sample data only. The eight examples share the Line Chart data: connection styles as a plain area (left) and with Dots and // Sample data only. The eight examples share the Line Chart data: connection styles as a plain area (left) and with Dots and
// value Labels (right), then Stacked (running total) and Expanded (each month normalized to 100%). // value Labels (right), then Stacked (running total) and Expanded (each month normalized to 100%).
@@ -26,7 +26,7 @@ export const Area: Story = {
<StoryPage> <StoryPage>
<StoryPageHeader title="Area Chart" /> <StoryPageHeader title="Area Chart" />
{/* Same grid and order as Line Chart, plus a Stacked | Expanded row. */} {/* Same grid and order as Line Chart, plus a Stacked | Expanded row. */}
<div className="grid grid-cols-1 gap-x-6 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1"> <StorySectionGrid className="grid-cols-1 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1">
<StorySection title="Curve"> <StorySection title="Curve">
<AreaCurveExample /> <AreaCurveExample />
</StorySection> </StorySection>
@@ -51,7 +51,7 @@ export const Area: Story = {
<StorySection title="Expanded"> <StorySection title="Expanded">
<AreaExpandedExample /> <AreaExpandedExample />
</StorySection> </StorySection>
</div> </StorySectionGrid>
</StoryPage> </StoryPage>
), ),
} }
+3 -3
View File
@@ -5,7 +5,7 @@ import {
SignedBarChartExample, SignedBarChartExample,
StackedBarChartExample, StackedBarChartExample,
} from "@hm-design/chart/examples" } from "@hm-design/chart/examples"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page" import { StoryPage, StoryPageHeader, StorySection, StorySectionGrid } from "@/components/story-page"
// shadcn/ui Chart (Recharts) with HM tokens, from the public @hm-design/chart entries. All values are sample data. // shadcn/ui Chart (Recharts) with HM tokens, from the public @hm-design/chart entries. All values are sample data.
// Narrow widths: use the toolbar Viewport presets (HM Mobile · 390). The chart is fluid. // Narrow widths: use the toolbar Viewport presets (HM Mobile · 390). The chart is fluid.
@@ -24,7 +24,7 @@ export const Bar: Story = {
{/* Two sections per row from 1024px (HM desktop boundary), one below. Source order is the mobile order; on desktop it reads {/* Two sections per row from 1024px (HM desktop boundary), one below. Source order is the mobile order; on desktop it reads
row by row: Grouped | Horizontal, then Stacked | Positive / Negative. On desktop every row has the same height and each row by row: Grouped | Horizontal, then Stacked | Positive / Negative. On desktop every row has the same height and each
card fills its section, so card tops, bottoms and chart areas line up. */} card fills its section, so card tops, bottoms and chart areas line up. */}
<div className="grid grid-cols-1 gap-x-6 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1"> <StorySectionGrid className="grid-cols-1 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1">
<StorySection title="Grouped"> <StorySection title="Grouped">
<BarChartExample /> <BarChartExample />
</StorySection> </StorySection>
@@ -37,7 +37,7 @@ export const Bar: Story = {
<StorySection title="Positive / Negative"> <StorySection title="Positive / Negative">
<SignedBarChartExample /> <SignedBarChartExample />
</StorySection> </StorySection>
</div> </StorySectionGrid>
</StoryPage> </StoryPage>
), ),
} }
+6 -6
View File
@@ -6,7 +6,7 @@ import {
DonutPlainExample, DonutPlainExample,
DonutSolidExample, DonutSolidExample,
} from "@hm-design/chart/examples" } from "@hm-design/chart/examples"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page" import { StoryPage, StoryPageHeader, StorySection, StorySectionGrid } from "@/components/story-page"
// Sample data only. The four examples share one data set, color mapping and item order. // Sample data only. The four examples share one data set, color mapping and item order.
const meta = { const meta = {
@@ -22,11 +22,11 @@ export const Donut: Story = {
<StoryPage> <StoryPage>
<StoryPageHeader title="Donut Chart" /> <StoryPageHeader title="Donut Chart" />
{/* Same grid as Bar and Line Chart: two per row from 1024px, one below; source order is the mobile order. */} {/* Same grid as Bar and Line Chart: two per row from 1024px, one below; source order is the mobile order. */}
<div className="grid grid-cols-1 gap-x-6 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1"> <StorySectionGrid className="grid-cols-1 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1">
<StorySection title="기본형"> <StorySection title="Basic">
<DonutPlainExample /> <DonutPlainExample />
</StorySection> </StorySection>
<StorySection title="기본형 + 텍스트"> <StorySection title="Basic · Center Text">
<DonutCenterExample /> <DonutCenterExample />
</StorySection> </StorySection>
<StorySection title="Active"> <StorySection title="Active">
@@ -35,10 +35,10 @@ export const Donut: Story = {
<StorySection title="Interactive"> <StorySection title="Interactive">
<DonutInteractiveExample /> <DonutInteractiveExample />
</StorySection> </StorySection>
<StorySection title="입체형"> <StorySection title="Solid">
<DonutSolidExample /> <DonutSolidExample />
</StorySection> </StorySection>
</div> </StorySectionGrid>
</StoryPage> </StoryPage>
), ),
} }
+3 -3
View File
@@ -7,7 +7,7 @@ import {
LineStraightColorLabelExample, LineStraightColorLabelExample,
LineStraightExample, LineStraightExample,
} from "@hm-design/chart/examples" } from "@hm-design/chart/examples"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page" import { StoryPage, StoryPageHeader, StorySection, StorySectionGrid } from "@/components/story-page"
// Sample data only. All six examples share one data set and axis range, so connection styles compare directly: curve, straight // Sample data only. All six examples share one data set and axis range, so connection styles compare directly: curve, straight
// and step, each as a plain line (left) and with Dots and value labels (right). // and step, each as a plain line (left) and with Dots and value labels (right).
@@ -25,7 +25,7 @@ export const Line: Story = {
<StoryPageHeader title="Line Chart" /> <StoryPageHeader title="Line Chart" />
{/* Same grid as Bar Chart: two per row from 1024px, one below. Source order is the mobile order; desktop reads row by row: {/* Same grid as Bar Chart: two per row from 1024px, one below. Source order is the mobile order; desktop reads row by row:
Curve | Curve + labels, Straight | Straight + colored labels, Step | Step + labels. */} Curve | Curve + labels, Straight | Straight + colored labels, Step | Step + labels. */}
<div className="grid grid-cols-1 gap-x-6 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1"> <StorySectionGrid className="grid-cols-1 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1">
<StorySection title="Curve"> <StorySection title="Curve">
<LineCurveExample /> <LineCurveExample />
</StorySection> </StorySection>
@@ -44,7 +44,7 @@ export const Line: Story = {
<StorySection title="Step · Dot + Label"> <StorySection title="Step · Dot + Label">
<LineStepDotLabelExample /> <LineStepDotLabelExample />
</StorySection> </StorySection>
</div> </StorySectionGrid>
</StoryPage> </StoryPage>
), ),
} }
+8 -8
View File
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react-vite" import type { Meta, StoryObj } from "@storybook/react-vite"
import { PieLabelExample, PieLabelListExample, PiePlainExample, PieSolidExample, PieStackedExample } from "@hm-design/chart/examples" import { PieLabelExample, PieLabelListExample, PiePlainExample, PieSolidExample, PieStackedExample } from "@hm-design/chart/examples"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page" import { StoryPage, StoryPageHeader, StorySection, StorySectionGrid } from "@/components/story-page"
// Sample data only. All pies share one item list and color mapping; the stacked pie draws two series as concentric rings. // Sample data only. All pies share one item list and color mapping; the stacked pie draws two series as concentric rings.
const meta = { const meta = {
@@ -16,23 +16,23 @@ export const Pie: Story = {
<StoryPage> <StoryPage>
<StoryPageHeader title="Pie Chart" /> <StoryPageHeader title="Pie Chart" />
{/* Same grid as Bar, Line and Donut Chart: two per row from 1024px, one below; source order is the mobile order. */} {/* Same grid as Bar, Line and Donut Chart: two per row from 1024px, one below; source order is the mobile order. */}
<div className="grid grid-cols-1 gap-x-6 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1"> <StorySectionGrid className="grid-cols-1 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1">
<StorySection title="기본 Pie"> <StorySection title="Basic">
<PiePlainExample /> <PiePlainExample />
</StorySection> </StorySection>
<StorySection title="Pie + Label"> <StorySection title="Label">
<PieLabelExample /> <PieLabelExample />
</StorySection> </StorySection>
<StorySection title="Pie + Label List"> <StorySection title="Label List">
<PieLabelListExample /> <PieLabelListExample />
</StorySection> </StorySection>
<StorySection title="Pie + Stacked"> <StorySection title="Stacked">
<PieStackedExample /> <PieStackedExample />
</StorySection> </StorySection>
<StorySection title="입체형"> <StorySection title="Solid">
<PieSolidExample /> <PieSolidExample />
</StorySection> </StorySection>
</div> </StorySectionGrid>
</StoryPage> </StoryPage>
), ),
} }
+7 -7
View File
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react-vite" import type { Meta, StoryObj } from "@storybook/react-vite"
import { RadarCustomLabelExample, RadarDotExample, RadarLinesOnlyExample, RadarPlainExample } from "@hm-design/chart/examples" import { RadarCustomLabelExample, RadarDotExample, RadarLinesOnlyExample, RadarPlainExample } from "@hm-design/chart/examples"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page" import { StoryPage, StoryPageHeader, StorySection, StorySectionGrid } from "@/components/story-page"
// Sample data only. All radars share one data set (six axes, two series) and the 0-100 axis range. // Sample data only. All radars share one data set (six axes, two series) and the 0-100 axis range.
const meta = { const meta = {
@@ -16,20 +16,20 @@ export const Radar: Story = {
<StoryPage> <StoryPage>
<StoryPageHeader title="Radar Chart" /> <StoryPageHeader title="Radar Chart" />
{/* Same grid as the other Chart pages: two per row from 1024px, one below. */} {/* Same grid as the other Chart pages: two per row from 1024px, one below. */}
<div className="grid grid-cols-1 gap-x-6 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1"> <StorySectionGrid className="grid-cols-1 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1">
<StorySection title="Radar"> <StorySection title="Basic">
<RadarPlainExample /> <RadarPlainExample />
</StorySection> </StorySection>
<StorySection title="Radar + Dot"> <StorySection title="Dot">
<RadarDotExample /> <RadarDotExample />
</StorySection> </StorySection>
<StorySection title="Radar + Lines Only"> <StorySection title="Lines Only">
<RadarLinesOnlyExample /> <RadarLinesOnlyExample />
</StorySection> </StorySection>
<StorySection title="Radar + Custom Label"> <StorySection title="Custom Label">
<RadarCustomLabelExample /> <RadarCustomLabelExample />
</StorySection> </StorySection>
</div> </StorySectionGrid>
</StoryPage> </StoryPage>
), ),
} }
+9 -9
View File
@@ -7,7 +7,7 @@ import {
RadialStackedExample, RadialStackedExample,
RadialTextExample, RadialTextExample,
} from "@hm-design/chart/examples" } from "@hm-design/chart/examples"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page" import { StoryPage, StoryPageHeader, StorySection, StorySectionGrid } from "@/components/story-page"
// Sample data only. One example per official shadcn/ui radial chart (radial-simple, -label, -grid, -text, -shape, -stacked). // Sample data only. One example per official shadcn/ui radial chart (radial-simple, -label, -grid, -text, -shape, -stacked).
const meta = { const meta = {
@@ -23,26 +23,26 @@ export const Radial: Story = {
<StoryPage> <StoryPage>
<StoryPageHeader title="Radial Chart" /> <StoryPageHeader title="Radial Chart" />
{/* Same grid as the other Chart pages: two per row from 1024px, one below; source order is the mobile order. */} {/* Same grid as the other Chart pages: two per row from 1024px, one below; source order is the mobile order. */}
<div className="grid grid-cols-1 gap-x-6 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1"> <StorySectionGrid className="grid-cols-1 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1">
<StorySection title="Radial Chart"> <StorySection title="Basic">
<RadialSimpleExample /> <RadialSimpleExample />
</StorySection> </StorySection>
<StorySection title="Radial Chart + Label"> <StorySection title="Label">
<RadialLabelExample /> <RadialLabelExample />
</StorySection> </StorySection>
<StorySection title="Radial Chart - Grid"> <StorySection title="Grid">
<RadialGridExample /> <RadialGridExample />
</StorySection> </StorySection>
<StorySection title="Radial Chart + Text"> <StorySection title="Text">
<RadialTextExample /> <RadialTextExample />
</StorySection> </StorySection>
<StorySection title="Radial Chart - Shape"> <StorySection title="Shape">
<RadialShapeExample /> <RadialShapeExample />
</StorySection> </StorySection>
<StorySection title="Radial Chart + Stacked"> <StorySection title="Stacked">
<RadialStackedExample /> <RadialStackedExample />
</StorySection> </StorySection>
</div> </StorySectionGrid>
</StoryPage> </StoryPage>
), ),
} }
+2 -13
View File
@@ -231,18 +231,7 @@ function DestructiveBoard() {
) )
} }
// Compact PC layout of the representative page (Storybook presentation only; Button itself is unchanged). // Page and section spacing come from the common compact Story page (story-page.css); this Story keeps no override.
// Page and section spacing are tightened for this Story only through `compactPage`; the shared story-page.css is not edited.
// The important modifier is required because the shared rules use the same specificity.
const compactPage = [
"py-4!",
"[&_.sb-page-header]:pb-3!",
"[&_.sb-page-title]:mt-1!",
"[&_.sb-page-title]:mb-2!",
"[&_.sb-page-section]:mt-5!",
"[&_.sb-page-section-header]:pb-2!",
"[&_.sb-page-section-header]:mb-3!",
].join(" ")
// 8px gap so the three Brand sizes stay on one line in the narrowest (Brand) column, same as the Variants rows. // 8px gap so the three Brand sizes stay on one line in the narrowest (Brand) column, same as the Variants rows.
const compactRow = "flex flex-wrap items-center gap-2" const compactRow = "flex flex-wrap items-center gap-2"
// Equal two columns over the whole content width. StateBoard's own auto-fill grid left an empty third column // Equal two columns over the whole content width. StateBoard's own auto-fill grid left an empty third column
@@ -285,7 +274,7 @@ export const All: Story = {
name: "Button", name: "Button",
parameters: { layout: "fullscreen" }, parameters: { layout: "fullscreen" },
render: () => ( render: () => (
<StoryPage className={compactPage}> <StoryPage>
<StoryPageHeader title="Button" /> <StoryPageHeader title="Button" />
<StorySection title="Sizes"> <StorySection title="Sizes">
{/* Same columns as Variants below (Brand / Standard Solid / Standard Outline): Brand and Standard Solid sizes line up with {/* Same columns as Variants below (Brand / Standard Solid / Standard Outline): Brand and Standard Solid sizes line up with
+4 -2
View File
@@ -10,12 +10,14 @@ export type BoardState = { id: string; label: string; note?: string; noteAfter?:
// `fixed` uses columns of exactly `min` px instead of stretching them (1fr). Specimens whose popup is anchored to a Radix // `fixed` uses columns of exactly `min` px instead of stretching them (1fr). Specimens whose popup is anchored to a Radix
// trigger need whole-pixel columns: a fractional column (e.g. 358.4px) puts the trigger edge between device pixels while the // trigger need whole-pixel columns: a fractional column (e.g. 358.4px) puts the trigger edge between device pixels while the
// popper rounds the popup to whole pixels, so the two outlines end up 1px apart. // popper rounds the popup to whole pixels, so the two outlines end up 1px apart.
// The labels are H3 (14/20/500, secondary) under the section's divider: they carry no divider of their own, and rows sit
// 24px apart (the section gap of story-page.css).
export function StateBoard({ states, min = 320, fixed = false }: { states: BoardState[]; min?: number; fixed?: boolean }) { export function StateBoard({ states, min = 320, fixed = false }: { states: BoardState[]; min?: number; fixed?: boolean }) {
// Inside a StorySection the labels sit one heading level below the section title. // Inside a StorySection the labels sit one heading level below the section title.
const Label = useContext(StorySectionContext) ? "h3" : "h2" const Label = useContext(StorySectionContext) ? "h3" : "h2"
return ( return (
<div <div
className={`grid items-start gap-x-6 gap-y-10 ${fixed ? "[grid-template-columns:repeat(auto-fill,min(100%,var(--sb-state-min,320px)))]" : "[grid-template-columns:repeat(auto-fill,minmax(min(100%,var(--sb-state-min,320px)),1fr))]"}`} className={`grid items-start gap-x-6 gap-y-6 ${fixed ? "[grid-template-columns:repeat(auto-fill,min(100%,var(--sb-state-min,320px)))]" : "[grid-template-columns:repeat(auto-fill,minmax(min(100%,var(--sb-state-min,320px)),1fr))]"}`}
style={{ "--sb-state-min": `${min}px` } as CSSProperties} style={{ "--sb-state-min": `${min}px` } as CSSProperties}
> >
{states.map(({ id, label, note, noteAfter, wide, children }) => ( {states.map(({ id, label, note, noteAfter, wide, children }) => (
@@ -25,7 +27,7 @@ export function StateBoard({ states, min = 320, fixed = false }: { states: Board
data-overview-state={id} data-overview-state={id}
aria-label={label} aria-label={label}
> >
<header className="grid gap-2 border-b border-black/10 pb-2"> <header className="grid gap-1">
<Label className="text-sm font-medium text-muted-foreground">{label}</Label> <Label className="text-sm font-medium text-muted-foreground">{label}</Label>
{note && !noteAfter ? <p className="text-sm text-muted-foreground">{note}</p> : null} {note && !noteAfter ? <p className="text-sm text-muted-foreground">{note}</p> : null}
</header> </header>
+33 -14
View File
@@ -1,8 +1,15 @@
/* Storybook presentation only. Common page frame for representative Stories; all selectors stay inside .sb-page. /* Storybook presentation only. Common page frame for representative Stories; all selectors stay inside .sb-page.
Inner (fluid; 16px each side up to 480px, growing to 60px at 768px; vertical gutter 16–40) and title (kicker 12 / title 28·36·700 / description, no divider below) reuse Foundation / Color. 2026-11-11 compact document rhythm, shared by Web / Chart / OrgChart (SW and CENTER pages use the same frame only):
Section reuses the Center Graphic / 교량 split: heading + description, a divider, then the specimen. */ H1 page title 28/36/700 (Page Header title) → description 14/20/400 secondary, 8px below (Page Header description)
→ 24px → H2 section title 16/24/700 (body-md) over a thin divider → 12px → specimen; sections 24px apart (Dashboard gap);
H3 specimen labels 14/20/500 secondary, no divider of their own (story-board.tsx). Top / bottom gutter 16–24px.
Side margins are unchanged (16px up to 480px, growing to 60px at 768px): the SW breakpoints 2040 / 1125 / 787 depend on them.
No design token is added; --sb-page-* only names existing component token values for this Storybook frame. */
.sb-page { .sb-page {
--sb-page-gutter: clamp(16px, 3vw, 40px); --sb-page-gutter: clamp(16px, 2vw, 24px);
--sb-page-section-gap: 24px;
--sb-page-section-title-font-size: var(--hm-dialog-body-font-size);
--sb-page-section-title-line-height: var(--hm-dialog-body-line-height);
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
min-width: 0; min-width: 0;
@@ -20,38 +27,46 @@
margin-inline: auto; margin-inline: auto;
} }
/* No bottom padding: the header's margin collapses with the next block's own top margin (a section's 24px), so the first
content sits 24px below the title whether it is a section, a section grid or a plain preview. */
.sb-page .sb-page-header { .sb-page .sb-page-header {
padding-bottom: 24px; margin-bottom: var(--sb-page-section-gap);
font-size: var(--hm-label-font-size); font-size: var(--hm-label-font-size);
line-height: var(--hm-label-line-height); line-height: var(--hm-label-line-height);
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
.sb-page .sb-page-header .sb-page-kicker { .sb-page .sb-page-header .sb-page-kicker {
margin: 0 0 12px; margin: 0 0 4px;
color: var(--hm-sem-fg-secondary); color: var(--hm-sem-fg-secondary);
font-size: var(--hm-button-font-size-sm); font-size: var(--hm-button-font-size-sm);
font-weight: var(--hm-font-weight-bold); font-weight: var(--hm-font-weight-bold);
} }
.sb-page .sb-page-header .sb-page-title { .sb-page .sb-page-header .sb-page-title {
margin: 8px 0 12px; margin: 0;
font-size: var(--hm-page-header-title-font-size); font-size: var(--hm-page-header-title-font-size);
line-height: var(--hm-page-header-title-line-height); line-height: var(--hm-page-header-title-line-height);
font-weight: var(--hm-page-header-title-font-weight); font-weight: var(--hm-page-header-title-font-weight);
} }
.sb-page .sb-page-header .sb-page-description { margin: 0 0 12px; font-weight: var(--hm-input-font-weight); } .sb-page .sb-page-header .sb-page-description {
margin: var(--hm-page-header-description-gap) 0 0;
color: var(--hm-page-header-description-color);
font-size: var(--hm-page-header-description-font-size);
line-height: var(--hm-page-header-description-line-height);
font-weight: var(--hm-input-font-weight);
}
.sb-page .sb-page-section { margin-top: 32px; scroll-margin-top: 16px; } .sb-page .sb-page-section { margin-top: var(--sb-page-section-gap); scroll-margin-top: 16px; }
.sb-page .sb-page-section-header { .sb-page .sb-page-section-header {
padding-bottom: 12px; padding-bottom: 8px;
margin-bottom: 16px; margin-bottom: 12px;
border-bottom: 1px solid var(--hm-sem-stroke-input-default); border-bottom: 1px solid var(--hm-sem-stroke-input-default);
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
.sb-page .sb-page-section-header .sb-page-section-title { .sb-page .sb-page-section-header .sb-page-section-title {
margin: 0; margin: 0;
font-size: var(--hm-card-title-font-size); font-size: var(--sb-page-section-title-font-size);
line-height: var(--hm-card-title-line-height); line-height: var(--sb-page-section-title-line-height);
font-weight: var(--hm-card-title-font-weight); font-weight: var(--hm-font-weight-bold);
} }
.sb-page .sb-page-section-header .sb-page-section-description { .sb-page .sb-page-section-header .sb-page-section-description {
margin: 4px 0 0; margin: 4px 0 0;
@@ -61,9 +76,13 @@
font-weight: var(--hm-input-font-weight); font-weight: var(--hm-input-font-weight);
} }
.sb-page .sb-page-section-body { min-width: 0; } .sb-page .sb-page-section-body { min-width: 0; }
/* Sections side by side (StorySectionGrid): grid items keep their margins, so the grid owns the row gap instead.
Columns and alignment stay with the Story (className): this unlayered rule would override Tailwind's layered grid-cols-*. */
.sb-page .sb-page-section-grid { display: grid; gap: var(--sb-page-section-gap) 24px; margin-top: var(--sb-page-section-gap); }
.sb-page .sb-page-section-grid > .sb-page-section { margin-top: 0; }
/* Notice above a page title (StoryNotice). Reuses .sb-page / .sb-page-inner (width and side margin are not redefined). /* Notice above a page title (StoryNotice). Reuses .sb-page / .sb-page-inner (width and side margin are not redefined).
The frame drops its bottom gutter and pulls the next page up so the notice sits 24px above the title block's own top gutter. */ The frame drops its bottom gutter and pulls the next page up so the notice sits 24px above the page title. */
.sb-page.sb-page-notice { .sb-page.sb-page-notice {
padding-bottom: 0; padding-bottom: 0;
margin-bottom: calc(24px - var(--sb-page-gutter)); margin-bottom: calc(24px - var(--sb-page-gutter));
+6
View File
@@ -70,6 +70,12 @@ export function StorySection({
) )
} }
// Sections side by side (e.g. two charts per row). The grid owns the row gap (the section gap), so rows line up the same
// way as stacked sections; columns and alignment come from `className`.
export function StorySectionGrid({ className, children }: { className?: string; children: ReactNode }) {
return <div className={className ? `sb-page-section-grid ${className}` : "sb-page-section-grid"}>{children}</div>
}
export const STORY_KICKER = { export const STORY_KICKER = {
foundation: "HM DESIGN SYSTEM / FOUNDATION", foundation: "HM DESIGN SYSTEM / FOUNDATION",
components: "HM DESIGN SYSTEM / COMPONENTS", components: "HM DESIGN SYSTEM / COMPONENTS",
+2 -3
View File
@@ -159,9 +159,8 @@ export const All: Story = {
name: "Tabs", name: "Tabs",
parameters: { layout: "fullscreen", controls: { disable: true } }, parameters: { layout: "fullscreen", controls: { disable: true } },
render: () => ( render: () => (
// `py-4!` trims this page's top and bottom padding (30-40px by viewport width) so the page fits a 900px-high PC screen without // The common compact Story page (16–24px gutter, 24px section gap) fits this page on a 900px-high PC screen without an override.
// shrinking any component; the important modifier is needed because the shared rule has the same specificity. Section gaps stay 32px. <StoryPage>
<StoryPage className="py-4!">
<StoryPageHeader title="Tabs" /> <StoryPageHeader title="Tabs" />
<StorySection title="Variants"> <StorySection title="Variants">
{/* The former States section (Overflow) is the third Variants item. */} {/* The former States section (Overflow) is the third Variants item. */}
+3 -3
View File
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"
import { BoldIcon, ItalicIcon, StarIcon } from "lucide-react" import { BoldIcon, ItalicIcon, StarIcon } from "lucide-react"
import { Toggle, ToggleGroup, ToggleGroupItem } from "@hm-design/ui" import { Toggle, ToggleGroup, ToggleGroupItem } from "@hm-design/ui"
import { StateBoard, type BoardState } from "@/components/story-board" import { StateBoard, type BoardState } from "@/components/story-board"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page" import { StoryPage, StoryPageHeader, StorySection, StorySectionGrid } from "@/components/story-page"
// Toggle is a persistent pressed-state control. The representative Story shows each state with real, clickable toggles; the same // Toggle is a persistent pressed-state control. The representative Story shows each state with real, clickable toggles; the same
// specimens also exist as Stories tagged `verification` (hidden from the sidebar). Icon-only toggles carry an `aria-label`. // specimens also exist as Stories tagged `verification` (hidden from the sidebar). Icon-only toggles carry an `aria-label`.
@@ -97,14 +97,14 @@ export const All: Story = {
render: () => ( render: () => (
<StoryPage> <StoryPage>
<StoryPageHeader title="Toggle" /> <StoryPageHeader title="Toggle" />
<div className="grid gap-x-6 min-[1000px]:grid-cols-3"> <StorySectionGrid className="grid-cols-1 min-[1000px]:grid-cols-3">
<StorySection title="Variants"> <StorySection title="Variants">
<StateBoard min={10000} states={[specimen("offon")]} /> <StateBoard min={10000} states={[specimen("offon")]} />
</StorySection> </StorySection>
<StorySection title="States"> <StorySection title="States">
<StateBoard min={10000} states={[specimen("disabled")]} /> <StateBoard min={10000} states={[specimen("disabled")]} />
</StorySection> </StorySection>
</div> </StorySectionGrid>
<StorySection title="단독 · 연결 그룹"> <StorySection title="단독 · 연결 그룹">
<SpecimenGrid <SpecimenGrid
states={[ states={[
+6 -7
View File
@@ -49,11 +49,11 @@
.hm-foundation .hm-company-grid > #company-jangheon-industry { grid-column-start: 1; } .hm-foundation .hm-company-grid > #company-jangheon-industry { grid-column-start: 1; }
.hm-foundation .hm-brand-color-row[data-company-id] { display: grid; gap: 8px; overflow: visible; padding-bottom: 0; grid-template-columns: repeat(auto-fill, minmax(max(100px, (100% - 16px) / 3), 1fr)); } .hm-foundation .hm-brand-color-row[data-company-id] { display: grid; gap: 8px; overflow: visible; padding-bottom: 0; grid-template-columns: repeat(auto-fill, minmax(max(100px, (100% - 16px) / 3), 1fr)); }
/* Typography: compact specification page. Font Family one line, Font Weight small cards, Specimens as comparison tables /* Typography: compact specification page. Font Family one line, Font Weight as three plain columns (no card box; the
(two columns on wide screens, stacked rows on narrow screens). */ section divider already frames them), Specimens as comparison tables (two columns on wide screens, stacked rows on narrow screens). */
.hm-foundation .hm-typo-family { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 16px; margin: 0; } .hm-foundation .hm-typo-family { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 16px; margin: 0; }
.hm-foundation .hm-typo-weights { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); gap: 12px; } .hm-foundation .hm-typo-weights { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); gap: 12px 24px; }
.hm-foundation .hm-typo-weight { padding: 12px 16px; } .hm-foundation .hm-typo-weight { min-width: 0; }
.hm-foundation .hm-typo-weight p { margin: 0; } .hm-foundation .hm-typo-weight p { margin: 0; }
.hm-foundation .hm-typo-weight-preview { margin: 4px 0 !important; font-size: var(--hm-card-title-font-size); line-height: var(--hm-card-title-line-height); } .hm-foundation .hm-typo-weight-preview { margin: 4px 0 !important; font-size: var(--hm-card-title-font-size); line-height: var(--hm-card-title-line-height); }
.hm-foundation .hm-typo-tables { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px 32px; align-items: start; } .hm-foundation .hm-typo-tables { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px 32px; align-items: start; }
@@ -73,10 +73,9 @@
} }
/* Layout: compact specification page (PC: three bands; narrow screens stack and scroll naturally). /* Layout: compact specification page (PC: three bands; narrow screens stack and scroll naturally).
Tightening applies only inside .hm-layout-ref; the common Story page and other Foundation pages are unchanged. No fixed heights or clipping. */ Section header spacing and the section gap come from the common Story page; the bands only own their grid. No fixed heights or clipping. */
.hm-foundation .hm-layout-ref { display: grid; gap: 32px; word-break: keep-all; } .hm-foundation .hm-layout-ref { display: grid; gap: var(--sb-page-section-gap); word-break: keep-all; }
.hm-foundation .hm-layout-ref .sb-page-section { margin-top: 0; } .hm-foundation .hm-layout-ref .sb-page-section { margin-top: 0; }
.hm-foundation .hm-layout-ref .sb-page-section-header { padding-bottom: 8px; margin-bottom: 12px; }
.hm-foundation .hm-layout-ref p, .hm-foundation .hm-layout-ref h3 { margin: 0; } .hm-foundation .hm-layout-ref p, .hm-foundation .hm-layout-ref h3 { margin: 0; }
.hm-foundation .hm-layout-ref .hm-foundation-card, .hm-foundation .hm-layout-ref .hm-foundation-panel { padding: 8px 16px; } .hm-foundation .hm-layout-ref .hm-foundation-card, .hm-foundation .hm-layout-ref .hm-foundation-panel { padding: 8px 16px; }
.hm-foundation .hm-layout-ref code { font-size: var(--hm-button-font-size-sm); color: var(--hm-sem-fg-secondary); } .hm-foundation .hm-layout-ref code { font-size: var(--hm-button-font-size-sm); color: var(--hm-sem-fg-secondary); }
+1 -1
View File
@@ -48,7 +48,7 @@ function TypographyReferencePage() {
<p className="hm-typo-family"><span>Noto Sans KR → 맑은 고딕 → 시스템 sans-serif</span><code className="hm-foundation-note">--hm-font-family-base: {values["--hm-font-family-base"]}</code></p> <p className="hm-typo-family"><span>Noto Sans KR → 맑은 고딕 → 시스템 sans-serif</span><code className="hm-foundation-note">--hm-font-family-base: {values["--hm-font-family-base"]}</code></p>
</ReferenceSection> </ReferenceSection>
<ReferenceSection title="Font Weight"> <ReferenceSection title="Font Weight">
<div className="hm-typo-weights">{weights.map(({ property, label }) => <article className="hm-foundation-card hm-typo-weight" key={property}> <div className="hm-typo-weights">{weights.map(({ property, label }) => <article className="hm-typo-weight" key={property}>
<p className="hm-typo-weight-meta"><strong>{label}</strong> <span className="hm-foundation-note">{values[property]}</span></p> <p className="hm-typo-weight-meta"><strong>{label}</strong> <span className="hm-foundation-note">{values[property]}</span></p>
<p className="hm-typo-weight-preview" style={{ fontWeight: `var(${property})` }}>한맥 Hanmac 0123</p> <p className="hm-typo-weight-preview" style={{ fontWeight: `var(${property})` }}>한맥 Hanmac 0123</p>
<code className="hm-foundation-note">{property}</code> <code className="hm-foundation-note">{property}</code>
+4 -1
View File
@@ -36,7 +36,10 @@
.sw-footer-story-row { display: flex; align-items: flex-start; gap: 16px; } .sw-footer-story-row { display: flex; align-items: flex-start; gap: 16px; }
.sw-footer-story-row--tight { gap: 0; } .sw-footer-story-row--tight { gap: 0; }
.sw-footer-story-inline { display: inline-flex; flex-direction: column; padding: 0; } .sw-footer-story-inline { display: inline-flex; flex-direction: column; padding: 0; }
.sw-footer-story-width + .sw-footer-story-width { margin-top: 16px; } /* Story page frame only (2026-11-11): the width rows sit in a container (.sw-story-responsive), where margins do not collapse,
so the rows own the common section gap (story-page.css) and their sections drop their own top margin. */
.sb-page .sw-footer-story-width > .sb-page-section { margin-top: 0; }
.sw-footer-story-width + .sw-footer-story-width { margin-top: var(--sb-page-section-gap, 24px); }
/* The hidden 1920px row (Responsive More, narrower than 1920px) takes its gap with it. */ /* The hidden 1920px row (Responsive More, narrower than 1920px) takes its gap with it. */
@container sw-story-page (max-width: 2039.98px) { .sw-story-responsive-hidden + .sw-footer-story-width { margin-top: 0; } } @container sw-story-page (max-width: 2039.98px) { .sw-story-responsive-hidden + .sw-footer-story-width { margin-top: 0; } }
.sw-footer-story-aux-compare { display: grid; grid-template-columns: repeat(auto-fill, minmax(560px, 1fr)); gap: 16px; } .sw-footer-story-aux-compare { display: grid; grid-template-columns: repeat(auto-fill, minmax(560px, 1fr)); gap: 16px; }
+1 -1
View File
@@ -944,5 +944,5 @@ function PropertyCompare() {
} }
/** Rendered by SW/Popup › Property Panel · 4개 비교 (sw-property-panel.stories.tsx, ID sw-header--property-panel-compare). */ /** Rendered by SW/Popup › Property Panel · 4개 비교 (sw-property-panel.stories.tsx, ID sw-header--property-panel-compare). */
export function PropertyPanelComparePage() { export function PropertyPanelComparePage() {
return <StoryPage className="sw-story-compare-page"><StoryPageHeader title="속성창 4개 상태 비교" /><PropertyCompare /></StoryPage> return <StoryPage className="sw-story-compare-page"><StoryPageHeader title="SW Popup — 속성창" /><PropertyCompare /></StoryPage>
} }
+4
View File
@@ -28,6 +28,10 @@
.sw-story-fixed-dimension .hm-sw-header__list-menu--dimension-style[data-shifted="true"]::before { top: calc(-1 * var(--hm-sw-header-field-height) - 1px); } .sw-story-fixed-dimension .hm-sw-header__list-menu--dimension-style[data-shifted="true"]::before { top: calc(-1 * var(--hm-sw-header-field-height) - 1px); }
.sw-story-log { margin: 0 0 8px; font-size: 13px; color: #3d4a45; } .sw-story-log { margin: 0 0 8px; font-size: 13px; color: #3d4a45; }
.sw-story-stack { display: flex; flex-direction: column; gap: 8px; } .sw-story-stack { display: flex; flex-direction: column; gap: 8px; }
/* Story page frame only (2026-11-11): a stack of sections (e.g. the 화면 너비 rows) takes the common section gap of
story-page.css, so the 8px stack gap and each section's own top margin do not add up. Headers inside are unchanged. */
.sb-page .sw-story-stack:has(> .sb-page-section, > div > .sb-page-section) { gap: var(--sb-page-section-gap); margin-top: var(--sb-page-section-gap); }
.sb-page .sw-story-stack > .sb-page-section, .sb-page .sw-story-stack > div > .sb-page-section { margin-top: 0; }
/* Window Move Motion: window controls alone on the window-area surface, magnified for comparison. */ /* Window Move Motion: window controls alone on the window-area surface, magnified for comparison. */
.sw-story-window { display: flex; width: 152px; height: 48px; background: var(--hm-sw-header-document-background); } .sw-story-window { display: flex; width: 152px; height: 48px; background: var(--hm-sw-header-document-background); }
.sw-story-zoom { zoom: 3; width: max-content; } .sw-story-zoom { zoom: 3; width: max-content; }
+1 -1
View File
@@ -275,7 +275,7 @@ export const ContentVariants: Story = {
const demo = useViewerDemo({ initialDocument: SAMPLES[1].name, initialPanels: ["layers", "properties", "options"] }) const demo = useViewerDemo({ initialDocument: SAMPLES[1].name, initialPanels: ["layers", "properties", "options"] })
return ( return (
<StoryPage> <StoryPage>
<StoryPageHeader title="SW Viewer — 설정창 (SWViewerPanel)" /> <StoryPageHeader title="SW Viewer — 설정창" />
<Log lines={demo.log} /> <Log lines={demo.log} />
{/* 2026-10-22: 620px fits the tallest popup (옵션, 520px) now that 축척 지정 is gone. */} {/* 2026-10-22: 620px fits the tallest popup (옵션, 520px) now that 축척 지정 is gone. */}
<ViewerFrame height={620} header={<SWViewerHeader {...VIEWER_STORY_LOGO} documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={demo.trailing} onCommand={demo.onCommand} />}> <ViewerFrame height={620} header={<SWViewerHeader {...VIEWER_STORY_LOGO} documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={demo.trailing} onCommand={demo.onCommand} />}>