diff --git a/WORKING_CONTEXT.md b/WORKING_CONTEXT.md index 94b3d79..6b16a35 100644 --- a/WORKING_CONTEXT.md +++ b/WORKING_CONTEXT.md @@ -1,5 +1,13 @@ # 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 프로세스 영역 양 끝 직각 - 사용자 결정: `SWProcessHeader appearance="multi"`만 프로세스 영역(파일명 옆·창 영역 옆) 위 모서리를 직각으로(`default`는 Strana 6px·덮개 5px 유지 — 처음 두 타입 모두로 들었다가 사용자 정정). `sw-process-header.css` multi 블록에 바탕 `::before`·양 끝 덮개 `border-radius: 0` 1줄. 단계 탭 캡슐·화살표·사다리꼴 면·색·높이 변경 없음. Header 최외곽은 원래 직각. diff --git a/docs/guidelines/프로젝트-지침.md b/docs/guidelines/프로젝트-지침.md index 947d6b8..ee2f38c 100644 --- a/docs/guidelines/프로젝트-지침.md +++ b/docs/guidelines/프로젝트-지침.md @@ -157,6 +157,7 @@ HM 지정값이 있는 색상, radius, control height, typography 등은 Theme T - 컴포넌트가 실제 지원하는 default, variant, size, disabled 및 주요 상태를 확인한다. invalid, open 등 해당 컴포넌트에 실제로 있는 상태만 다루고, 없는 상태를 문서화를 위해 만들지 않는다. - 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는 제공된 경우에만 충돌하지 않는 참고로 사용한다. +- 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`를 쓴다. - 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로 분리한다. diff --git a/src/chart/area-chart.stories.tsx b/src/chart/area-chart.stories.tsx index 7cca540..afe16a8 100644 --- a/src/chart/area-chart.stories.tsx +++ b/src/chart/area-chart.stories.tsx @@ -9,7 +9,7 @@ import { AreaStraightColorLabelExample, AreaStraightExample, } 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 // value Labels (right), then Stacked (running total) and Expanded (each month normalized to 100%). @@ -26,7 +26,7 @@ export const Area: Story = { {/* Same grid and order as Line Chart, plus a Stacked | Expanded row. */} -
+ @@ -51,7 +51,7 @@ export const Area: Story = { -
+
), } diff --git a/src/chart/bar-chart.stories.tsx b/src/chart/bar-chart.stories.tsx index 331e45e..6bd84ed 100644 --- a/src/chart/bar-chart.stories.tsx +++ b/src/chart/bar-chart.stories.tsx @@ -5,7 +5,7 @@ import { SignedBarChartExample, StackedBarChartExample, } 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. // 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 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. */} -
+ @@ -37,7 +37,7 @@ export const Bar: Story = { -
+ ), } diff --git a/src/chart/donut-chart.stories.tsx b/src/chart/donut-chart.stories.tsx index e061828..9f0abea 100644 --- a/src/chart/donut-chart.stories.tsx +++ b/src/chart/donut-chart.stories.tsx @@ -6,7 +6,7 @@ import { DonutPlainExample, DonutSolidExample, } 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. const meta = { @@ -22,11 +22,11 @@ export const Donut: Story = { {/* Same grid as Bar and Line Chart: two per row from 1024px, one below; source order is the mobile order. */} -
- + + - + @@ -35,10 +35,10 @@ export const Donut: Story = { - + -
+
), } diff --git a/src/chart/line-chart.stories.tsx b/src/chart/line-chart.stories.tsx index 1f6e0ff..ef8cfc3 100644 --- a/src/chart/line-chart.stories.tsx +++ b/src/chart/line-chart.stories.tsx @@ -7,7 +7,7 @@ import { LineStraightColorLabelExample, LineStraightExample, } 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 // and step, each as a plain line (left) and with Dots and value labels (right). @@ -25,7 +25,7 @@ export const Line: Story = { {/* 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. */} -
+ @@ -44,7 +44,7 @@ export const Line: Story = { -
+ ), } diff --git a/src/chart/pie-chart.stories.tsx b/src/chart/pie-chart.stories.tsx index f991896..b115cf1 100644 --- a/src/chart/pie-chart.stories.tsx +++ b/src/chart/pie-chart.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from "@storybook/react-vite" 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. const meta = { @@ -16,23 +16,23 @@ export const Pie: Story = { {/* Same grid as Bar, Line and Donut Chart: two per row from 1024px, one below; source order is the mobile order. */} -
- + + - + - + - + - + -
+
), } diff --git a/src/chart/radar-chart.stories.tsx b/src/chart/radar-chart.stories.tsx index f727cc0..0a41151 100644 --- a/src/chart/radar-chart.stories.tsx +++ b/src/chart/radar-chart.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from "@storybook/react-vite" 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. const meta = { @@ -16,20 +16,20 @@ export const Radar: Story = { {/* Same grid as the other Chart pages: two per row from 1024px, one below. */} -
- + + - + - + - + -
+
), } diff --git a/src/chart/radial-chart.stories.tsx b/src/chart/radial-chart.stories.tsx index f88e466..d545062 100644 --- a/src/chart/radial-chart.stories.tsx +++ b/src/chart/radial-chart.stories.tsx @@ -7,7 +7,7 @@ import { RadialStackedExample, RadialTextExample, } 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). const meta = { @@ -23,26 +23,26 @@ export const Radial: Story = { {/* Same grid as the other Chart pages: two per row from 1024px, one below; source order is the mobile order. */} -
- + + - + - + - + - + - + -
+
), } diff --git a/src/components/button.stories.tsx b/src/components/button.stories.tsx index eca8dbf..3279791 100644 --- a/src/components/button.stories.tsx +++ b/src/components/button.stories.tsx @@ -231,18 +231,7 @@ function DestructiveBoard() { ) } -// Compact PC layout of the representative page (Storybook presentation only; Button itself is unchanged). -// 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(" ") +// Page and section spacing come from the common compact Story page (story-page.css); this Story keeps no override. // 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" // 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", parameters: { layout: "fullscreen" }, render: () => ( - + {/* Same columns as Variants below (Brand / Standard Solid / Standard Outline): Brand and Standard Solid sizes line up with diff --git a/src/components/story-board.tsx b/src/components/story-board.tsx index 2d5457c..6311443 100644 --- a/src/components/story-board.tsx +++ b/src/components/story-board.tsx @@ -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 // 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. +// 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 }) { // Inside a StorySection the labels sit one heading level below the section title. const Label = useContext(StorySectionContext) ? "h3" : "h2" return (
{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} aria-label={label} > -
+
{note && !noteAfter ?

{note}

: null}
diff --git a/src/components/story-page.css b/src/components/story-page.css index 6c879db..8835ba7 100644 --- a/src/components/story-page.css +++ b/src/components/story-page.css @@ -1,8 +1,15 @@ /* 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. - Section reuses the Center Graphic / 교량 split: heading + description, a divider, then the specimen. */ + 2026-11-11 compact document rhythm, shared by Web / Chart / OrgChart (SW and CENTER pages use the same frame only): + 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-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; width: 100%; min-width: 0; @@ -20,38 +27,46 @@ 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 { - padding-bottom: 24px; + margin-bottom: var(--sb-page-section-gap); font-size: var(--hm-label-font-size); line-height: var(--hm-label-line-height); overflow-wrap: anywhere; } .sb-page .sb-page-header .sb-page-kicker { - margin: 0 0 12px; + margin: 0 0 4px; color: var(--hm-sem-fg-secondary); font-size: var(--hm-button-font-size-sm); font-weight: var(--hm-font-weight-bold); } .sb-page .sb-page-header .sb-page-title { - margin: 8px 0 12px; + margin: 0; font-size: var(--hm-page-header-title-font-size); line-height: var(--hm-page-header-title-line-height); 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 { - padding-bottom: 12px; - margin-bottom: 16px; + padding-bottom: 8px; + margin-bottom: 12px; border-bottom: 1px solid var(--hm-sem-stroke-input-default); overflow-wrap: anywhere; } .sb-page .sb-page-section-header .sb-page-section-title { margin: 0; - font-size: var(--hm-card-title-font-size); - line-height: var(--hm-card-title-line-height); - font-weight: var(--hm-card-title-font-weight); + font-size: var(--sb-page-section-title-font-size); + line-height: var(--sb-page-section-title-line-height); + font-weight: var(--hm-font-weight-bold); } .sb-page .sb-page-section-header .sb-page-section-description { margin: 4px 0 0; @@ -61,9 +76,13 @@ font-weight: var(--hm-input-font-weight); } .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). - 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 { padding-bottom: 0; margin-bottom: calc(24px - var(--sb-page-gutter)); diff --git a/src/components/story-page.tsx b/src/components/story-page.tsx index a52b61c..bf46ac4 100644 --- a/src/components/story-page.tsx +++ b/src/components/story-page.tsx @@ -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
{children}
+} + export const STORY_KICKER = { foundation: "HM DESIGN SYSTEM / FOUNDATION", components: "HM DESIGN SYSTEM / COMPONENTS", diff --git a/src/components/tabs.stories.tsx b/src/components/tabs.stories.tsx index cd1ec30..0ec7b3f 100644 --- a/src/components/tabs.stories.tsx +++ b/src/components/tabs.stories.tsx @@ -159,9 +159,8 @@ export const All: Story = { name: "Tabs", parameters: { layout: "fullscreen", controls: { disable: true } }, 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 - // shrinking any component; the important modifier is needed because the shared rule has the same specificity. Section gaps stay 32px. - + // The common compact Story page (16–24px gutter, 24px section gap) fits this page on a 900px-high PC screen without an override. + {/* The former States section (Overflow) is the third Variants item. */} diff --git a/src/components/toggle.stories.tsx b/src/components/toggle.stories.tsx index 847104e..70b0584 100644 --- a/src/components/toggle.stories.tsx +++ b/src/components/toggle.stories.tsx @@ -2,7 +2,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite" import { BoldIcon, ItalicIcon, StarIcon } from "lucide-react" import { Toggle, ToggleGroup, ToggleGroupItem } from "@hm-design/ui" 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 // 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: () => ( -
+ -
+ #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)); } -/* Typography: compact specification page. Font Family one line, Font Weight small cards, Specimens as comparison tables - (two columns on wide screens, stacked rows on narrow screens). */ +/* Typography: compact specification page. Font Family one line, Font Weight as three plain columns (no card box; the + 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-weights { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); gap: 12px; } -.hm-foundation .hm-typo-weight { padding: 12px 16px; } +.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 { min-width: 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-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). - Tightening applies only inside .hm-layout-ref; the common Story page and other Foundation pages are unchanged. No fixed heights or clipping. */ -.hm-foundation .hm-layout-ref { display: grid; gap: 32px; word-break: keep-all; } + 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: 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-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 .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); } diff --git a/src/foundation/typography.stories.tsx b/src/foundation/typography.stories.tsx index fab208d..0dad925 100644 --- a/src/foundation/typography.stories.tsx +++ b/src/foundation/typography.stories.tsx @@ -48,7 +48,7 @@ function TypographyReferencePage() {

Noto Sans KR → 맑은 고딕 → 시스템 sans-serif--hm-font-family-base: {values["--hm-font-family-base"]}

-
{weights.map(({ property, label }) =>
+
{weights.map(({ property, label }) =>

{label} {values[property]}

한맥 Hanmac 0123

{property} diff --git a/src/sw/sw-footer-stories.css b/src/sw/sw-footer-stories.css index 1bb1c5d..84f1d9b 100644 --- a/src/sw/sw-footer-stories.css +++ b/src/sw/sw-footer-stories.css @@ -36,7 +36,10 @@ .sw-footer-story-row { display: flex; align-items: flex-start; gap: 16px; } .sw-footer-story-row--tight { gap: 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. */ @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; } diff --git a/src/sw/sw-header.stories.tsx b/src/sw/sw-header.stories.tsx index 80b146b..cb346ce 100644 --- a/src/sw/sw-header.stories.tsx +++ b/src/sw/sw-header.stories.tsx @@ -944,5 +944,5 @@ function PropertyCompare() { } /** Rendered by SW/Popup › Property Panel · 4개 비교 (sw-property-panel.stories.tsx, ID sw-header--property-panel-compare). */ export function PropertyPanelComparePage() { - return + return } diff --git a/src/sw/sw-stories.css b/src/sw/sw-stories.css index 36bc2b8..0a71dd4 100644 --- a/src/sw/sw-stories.css +++ b/src/sw/sw-stories.css @@ -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-log { margin: 0 0 8px; font-size: 13px; color: #3d4a45; } .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. */ .sw-story-window { display: flex; width: 152px; height: 48px; background: var(--hm-sw-header-document-background); } .sw-story-zoom { zoom: 3; width: max-content; } diff --git a/src/sw/sw-viewer.stories.tsx b/src/sw/sw-viewer.stories.tsx index c220369..d48f5bd 100644 --- a/src/sw/sw-viewer.stories.tsx +++ b/src/sw/sw-viewer.stories.tsx @@ -275,7 +275,7 @@ export const ContentVariants: Story = { const demo = useViewerDemo({ initialDocument: SAMPLES[1].name, initialPanels: ["layers", "properties", "options"] }) return ( - + {/* 2026-10-22: 620px fits the tallest popup (옵션, 520px) now that 축척 지정 is gone. */} }>