style(storybook): refine manager sidebar, LNB inset and shared story frame
- Manager sidebar labels and sort order for the grouped pages; LNB 1-depth text start 12px (token, baseline and LNB doc updated) - Shared story frame: drop the divider under section titles, side margin max 60px -> 30px; Foundation color/typography spacing - Pseudo-state mirroring also pins :focus-visible via data-sb-focus while keeping the previous clones for compound rules (Slider Focus + Drag)
This commit is contained in:
1 parent
7ff852952a
commit
fe9c115cf9
22 files changed
+100
-83
No files matched your search
+35
-39
@@ -16,7 +16,8 @@
|
||||
--overlay-black-30: rgba(0, 0, 0, .3);
|
||||
}
|
||||
/* Storybook 10.6 manager only. Source: docs/patterns/dashboard-composition.md §6.
|
||||
The 18px join / inset geometry follows the current LNB specimen, not a new token. */
|
||||
2026-11-13 compact 180px LNB: 14px side gutters, 40px / 36px rows, 6px selected radius and a 12px join
|
||||
(was 18px, the LNB specimen value) so the curves stay in proportion to the narrower column. Not a new token. */
|
||||
@font-face {
|
||||
font-family: 'Hanmac Manager Noto Sans KR';
|
||||
src: url('/fonts/NotoSansKR-variable.ttf') format('truetype');
|
||||
@@ -36,7 +37,7 @@
|
||||
}
|
||||
|
||||
#storybook-sidebar-region {
|
||||
--hm-manager-join: 18px;
|
||||
--hm-manager-join: 12px;
|
||||
color: var(--color-fg-on-green-primary);
|
||||
background: linear-gradient(90deg,
|
||||
var(--hm-green-default) 0%, var(--hm-green-light-1) 30%,
|
||||
@@ -46,7 +47,7 @@
|
||||
font-family: var(--hm-manager-font);
|
||||
}
|
||||
#storybook-sidebar-region [data-radix-scroll-area-content] > div {
|
||||
padding: 28px 0 28px 20px;
|
||||
padding: 20px 0 20px 14px;
|
||||
}
|
||||
/* Keep the native draggable scrollbar in the unused left gutter. The selected
|
||||
item must reach the content edge without a scroll thumb crossing its curves.
|
||||
@@ -61,13 +62,17 @@
|
||||
background: var(--hm-fg-on-green-secondary);
|
||||
}
|
||||
#storybook-sidebar-region .sidebar-header {
|
||||
margin: 0 20px 0px 0;
|
||||
margin: 0 14px 0px 0;
|
||||
padding: 0;
|
||||
color: var(--color-fg-on-green-primary);
|
||||
}
|
||||
/* The Settings button is hidden, so the title uses the full header width. */
|
||||
#storybook-sidebar-region .sidebar-header > div { margin-right: 0; }
|
||||
#storybook-sidebar-region .sidebar-header > div a {
|
||||
/* 2026-11-13: centred on the 180px LNB (the header box is already symmetric, 14px each side);
|
||||
Storybook's -4px link inline margin is dropped so the link spans exactly that box. */
|
||||
justify-content: center;
|
||||
margin-inline: 0;
|
||||
color: var(--color-fg-on-green-primary);
|
||||
font: 700 16px/30px var(--hm-manager-font);
|
||||
white-space: normal;
|
||||
@@ -113,27 +118,18 @@
|
||||
#storybook-explorer-tree .sidebar-subheading {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0 20px 0 0;
|
||||
min-height: 48px;
|
||||
padding: 0 14px 0 0;
|
||||
min-height: 40px;
|
||||
color: var(--hm-fg-on-green-secondary);
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-subheading[data-hm-section]:not([data-hm-section='0']) {
|
||||
margin-top: 24px;
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-subheading[data-hm-section]:not([data-hm-section='0'])::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: 0;
|
||||
right: 20px;
|
||||
height: 2px;
|
||||
background: linear-gradient(var(--overlay-black-30) 0 50%, rgba(255,255,255,.4) 50% 100%);
|
||||
mask-image: linear-gradient(90deg, transparent, #000 20%, #000 80%, transparent);
|
||||
margin-top: 0;
|
||||
}
|
||||
/* 2026-11-13: the divider above each group was removed. */
|
||||
#storybook-explorer-tree .sidebar-subheading > button {
|
||||
min-height: 48px;
|
||||
min-height: 40px;
|
||||
color: inherit;
|
||||
font: 700 14px/28px var(--hm-manager-font);
|
||||
font: 700 14px/24px var(--hm-manager-font);
|
||||
letter-spacing: 0;
|
||||
text-transform: none;
|
||||
}
|
||||
@@ -141,7 +137,7 @@
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 48px;
|
||||
min-height: 40px;
|
||||
overflow: visible;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
@@ -155,51 +151,51 @@
|
||||
box-shadow: inset 1px 0 var(--hm-green-light-2);
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-item[data-hm-group-start='true'] {
|
||||
border-radius: 8px 0 0 0;
|
||||
border-radius: 6px 0 0 0;
|
||||
box-shadow: inset 1px 1px var(--hm-green-light-2);
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-item[data-hm-group-end='true'] {
|
||||
border-radius: 0 0 0 8px;
|
||||
padding-bottom: 8px;
|
||||
border-radius: 0 0 0 6px;
|
||||
padding-bottom: 6px;
|
||||
box-shadow: inset 1px -1px var(--hm-green-light-2);
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-item > :is(a[id], button[id]:not([data-testid])) {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-height: 48px;
|
||||
gap: 8px;
|
||||
min-height: 40px;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 10px 32px 10px 18px;
|
||||
padding: 8px 14px 8px 10px;
|
||||
border: 0;
|
||||
border-radius: 4px 0 0 4px;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: 700 14px/28px var(--hm-manager-font);
|
||||
font: 700 14px/24px var(--hm-manager-font);
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
text-overflow: clip;
|
||||
}
|
||||
/* Sub rows (depth 2+): 42px (7px + 28px line + 7px), shorter than the 48px top-level rows. */
|
||||
/* Sub rows (depth 2+): 36px (6px + 24px line + 6px), shorter than the 40px top-level rows (2026-11-13 compact 180px LNB). */
|
||||
#storybook-explorer-tree .sidebar-item:is([data-hm-depth='2'], [data-hm-depth='3'], [data-hm-depth='4']) {
|
||||
min-height: 42px;
|
||||
min-height: 36px;
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-item:is([data-hm-depth='2'], [data-hm-depth='3'], [data-hm-depth='4']) > :is(a[id], button[id]:not([data-testid])) {
|
||||
min-height: 42px;
|
||||
padding-block: 7px;
|
||||
min-height: 36px;
|
||||
padding-block: 6px;
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-item[data-hm-depth='2'] > :is(a[id], button[id]:not([data-testid])) {
|
||||
margin-left: 32px;
|
||||
width: calc(100% - 32px);
|
||||
margin-left: 26px;
|
||||
width: calc(100% - 26px);
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-item[data-hm-depth='3'] > :is(a[id], button[id]:not([data-testid])),
|
||||
#storybook-explorer-tree .sidebar-item[data-hm-depth='4'] > :is(a[id], button[id]:not([data-testid])) {
|
||||
margin-left: 44px;
|
||||
width: calc(100% - 44px);
|
||||
margin-left: 36px;
|
||||
width: calc(100% - 36px);
|
||||
}
|
||||
/* Menu item labels and section heading labels (.sidebar-subheading) are both 14px / 700. */
|
||||
#storybook-explorer-tree .sidebar-item .hm-manager-label {
|
||||
@@ -246,9 +242,9 @@
|
||||
display: none;
|
||||
}
|
||||
/* First-depth leaves still sit inside their root label after losing the icon
|
||||
slot. Deeper rows retain their existing 32px / 44px depth margins. */
|
||||
slot. Deeper rows retain their 26px / 36px depth margins. */
|
||||
#storybook-explorer-tree .sidebar-item[data-hm-depth='1'] > :is(a[id], button[id]:not([data-testid])):has(> div:first-child > svg[type]):not(:has(> div:first-child > div > svg)) {
|
||||
padding-left: 32px;
|
||||
padding-left: 26px;
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-item[data-hm-current-parent='true'],
|
||||
#storybook-explorer-tree .sidebar-subheading[data-hm-current-parent='true'] {
|
||||
@@ -261,7 +257,7 @@
|
||||
z-index: 1;
|
||||
background: var(--color-bg-content);
|
||||
color: var(--color-action-primary);
|
||||
border-radius: 8px 0 0 8px;
|
||||
border-radius: 6px 0 0 6px;
|
||||
}
|
||||
/* Keep the keyboard skip link without reserving a 1px flex column.
|
||||
That column exposed the green branch background at the selected item's end. */
|
||||
@@ -300,7 +296,7 @@
|
||||
background: radial-gradient(circle at bottom left, transparent calc(var(--hm-manager-join) - .5px), var(--color-bg-content) var(--hm-manager-join));
|
||||
}
|
||||
#storybook-explorer-tree .sidebar-item[data-selected='true'][data-hm-group-end='true']::after {
|
||||
top: calc(100% - 8px);
|
||||
top: calc(100% - 6px);
|
||||
}
|
||||
/* Auxiliary status/create actions are separate from selected row geometry.
|
||||
Hide only these controls; the Story link and native group toggles stay intact. */
|
||||
|
||||
+10
-6
@@ -29,6 +29,9 @@ toolbarStyle.textContent = `
|
||||
document.head.appendChild(toolbarStyle)
|
||||
|
||||
// Sidebar display names only. Story titles, Story IDs and storySort stay English; items not listed keep item.name.
|
||||
// 2026-11-13 grouped pages: 버튼 (Button + Toggle), 선택 컨트롤 (Checkbox / Radio Group / Switch), 시트·드로어 (Sheet + Drawer),
|
||||
// 툴팁·호버 카드 (Tooltip + Hover Card), 접기·펼치기 (Accordion + Collapsible), 날짜 선택 (Date Picker + Calendar). The absorbed
|
||||
// components keep their own labels for the hidden (verification) Stories shown by the sidebar tag filter.
|
||||
const koreanLabels = {
|
||||
foundation: '기본 요소',
|
||||
components: '컴포넌트',
|
||||
@@ -43,6 +46,7 @@ const koreanLabels = {
|
||||
'components-form-input': '입력창',
|
||||
'components-form-textarea': '텍스트 영역',
|
||||
'components-form-select': '선택 목록',
|
||||
'components-form-selection-controls': '선택 컨트롤',
|
||||
'components-form-checkbox': '체크박스',
|
||||
'components-form-radio-group': '라디오 버튼',
|
||||
'components-form-switch': '스위치',
|
||||
@@ -55,12 +59,12 @@ const koreanLabels = {
|
||||
'components-navigation-menubar': '메뉴바',
|
||||
'components-overlay-dialog': '대화상자',
|
||||
'components-overlay-alert-dialog': '확인·경고 대화상자',
|
||||
'components-overlay-sheet': '시트 패널',
|
||||
'components-overlay-sheet': '시트·드로어',
|
||||
'components-overlay-drawer': '드로어',
|
||||
'components-overlay-dropdown-menu': '드롭다운 메뉴',
|
||||
'components-overlay-dropdown-menu': '팝업 메뉴',
|
||||
'components-overlay-context-menu': '컨텍스트 메뉴',
|
||||
'components-overlay-popover': '팝오버',
|
||||
'components-overlay-tooltip': '툴팁',
|
||||
'components-overlay-tooltip': '툴팁·호버 카드',
|
||||
'components-overlay-hover-card': '호버 카드',
|
||||
'components-data-display-card': '카드',
|
||||
'components-data-display-table': '테이블',
|
||||
@@ -72,7 +76,7 @@ const koreanLabels = {
|
||||
'components-data-display-aspect-ratio': '화면 비율',
|
||||
'components-data-display-carousel': '캐러셀',
|
||||
'components-data-display-scroll-area': '스크롤 영역',
|
||||
'components-disclosure-accordion': '아코디언',
|
||||
'components-disclosure-accordion': '접기·펼치기',
|
||||
'components-disclosure-collapsible': '접기·펼치기',
|
||||
'components-disclosure-resizable': '크기 조절',
|
||||
'components-search-date-command': '명령어 검색',
|
||||
@@ -86,9 +90,9 @@ const koreanLabels = {
|
||||
'patterns-data-data-table': '데이터 테이블',
|
||||
'patterns-data-filter-bar': '필터 바',
|
||||
'patterns-layout': '화면 구성',
|
||||
'patterns-layout-gnb': '전체 메뉴 (GNB)',
|
||||
'patterns-layout-gnb': '상단 메뉴',
|
||||
'patterns-layout-dashboard': '대시보드',
|
||||
'patterns-layout-lnb': '좌측 메뉴 (LNB)',
|
||||
'patterns-layout-lnb': '좌측 메뉴',
|
||||
'patterns-layout-dashboard-layout': '대시보드',
|
||||
'patterns-layout-page-header': '페이지 헤더',
|
||||
chart: '차트',
|
||||
|
||||
@@ -29,7 +29,8 @@ const preview: Preview = {
|
||||
"Components",
|
||||
[
|
||||
"Form",
|
||||
["Button", "Input", "Textarea", "Select", "Checkbox", "Radio Group", "Switch", "Slider", "Toggle", "Toggle Group", "Label"],
|
||||
// "Selection Controls" (2026-11-13) takes the place of Checkbox / Radio Group / Switch, now hidden from the sidebar.
|
||||
["Button", "Input", "Textarea", "Select", "Selection Controls", "Checkbox", "Radio Group", "Switch", "Slider", "Toggle", "Toggle Group", "Label"],
|
||||
"Navigation",
|
||||
["Tabs", "Breadcrumb", "Pagination", "Navigation Menu", "Menubar"],
|
||||
"Overlay",
|
||||
@@ -48,9 +49,9 @@ const preview: Preview = {
|
||||
"Chart",
|
||||
["Bar Chart", "Line Chart", "Donut Chart", "Pie Chart", "Radar Chart", "Area Chart", "Radial Chart"],
|
||||
"SW",
|
||||
["Header", ["SW Header", "*"], "Viewer", "Step Header", "Popup", "하단 명령어바", ["SW 하단 명령어바", "*"], "Theme"],
|
||||
["Header", ["SW Header", "*"], "Viewer", "Step Header", "Popup", "하단 명령어바", ["SW 명령어바", "*"], "Theme"],
|
||||
"조직도",
|
||||
["개요", "부서별 색상", "전체 구성", "확장 조직도"],
|
||||
["가족사 현황", "부서별 색상", "전체 구성", "확장 조직도"],
|
||||
"노선도",
|
||||
"*",
|
||||
// With includeNames, keeps the Loader group after the Runtime Contract Stories (its place without names).
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
// Pins :hover and :active visuals for state Stories (Storybook only, never loaded by the app).
|
||||
// Every rule that uses :hover or :active is cloned right after itself with the pseudo-class swapped for a
|
||||
// Pins :hover, :active and :focus-visible visuals for state Stories (Storybook only, never loaded by the app).
|
||||
// Every rule that uses :hover, :active or :focus-visible is cloned right after itself with the pseudo-class swapped for a
|
||||
// data attribute, so cascade order and specificity stay the same. No declaration or value is authored here.
|
||||
// 2026-11-13: :focus-visible → data-sb-focus, so a Focus specimen can show its ring without holding the page focus (only one
|
||||
// element can be focused; e.g. Selection Controls focuses the Checkbox and pins the Radio Group / Switch rings).
|
||||
const HOVER = "data-sb-hover"
|
||||
const ACTIVE = "data-sb-active"
|
||||
const FOCUS = "data-sb-focus"
|
||||
const PSEUDO = /:hover\b|:active\b|:focus-visible\b/
|
||||
// A negated :focus-visible (:not(:focus-visible)) would match every element without the attribute, so such a rule keeps its
|
||||
// :focus-visible and only its :hover / :active (if any) are swapped.
|
||||
const NEGATED_FOCUS = /:not\([^)]*:focus-visible/
|
||||
const mirrored = new WeakSet<CSSStyleSheet>()
|
||||
|
||||
function mirror(list: CSSStyleSheet | CSSGroupingRule) {
|
||||
for (let i = list.cssRules.length - 1; i >= 0; i--) {
|
||||
const rule = list.cssRules[i]
|
||||
if (rule instanceof CSSStyleRule) {
|
||||
if (/:hover\b|:active\b/.test(rule.selectorText)) {
|
||||
list.insertRule(
|
||||
rule.cssText.replace(/:hover\b/g, `[${HOVER}]`).replace(/:active\b/g, `[${ACTIVE}]`),
|
||||
i + 1,
|
||||
)
|
||||
if (PSEUDO.test(rule.selectorText)) {
|
||||
// `base` keeps :focus-visible (the pre-2026-11-13 clone), so real focus + data-sb-hover / data-sb-active still matches
|
||||
// a compound rule such as :focus-visible:active (Slider Focus + Drag). `full` also swaps :focus-visible.
|
||||
const base = rule.cssText.replace(/:hover\b/g, `[${HOVER}]`).replace(/:active\b/g, `[${ACTIVE}]`)
|
||||
const full = NEGATED_FOCUS.test(rule.selectorText) ? base : base.replace(/:focus-visible\b/g, `[${FOCUS}]`)
|
||||
if (full !== base) list.insertRule(full, i + 1)
|
||||
if (base !== rule.cssText) list.insertRule(base, i + 1)
|
||||
}
|
||||
} else if ("cssRules" in rule) {
|
||||
mirror(rule as CSSGroupingRule)
|
||||
|
||||
@@ -157,7 +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`가 공통으로 소유한다. 2026-11-11 사용자 요청으로 `StoryPageHeader`는 H1 페이지 제목·kicker·설명·SW 공통 안내 문구를 표시하지 않으며(호출과 props는 유지), 페이지는 위 여백(16–24px) 바로 아래 첫 섹션부터 시작한다. 단, Preview 문서 제목이 Story ID라 스크린 리더가 페이지를 식별할 수 있도록 `title`만 시각적으로 숨긴 접근성 전용 H1(`.sb-page-a11y-title`, 1px clip·문서 흐름 밖)로 렌더링한다. 검증 스크립트는 이 H1의 시각 스타일에 의존하지 않고 첫 콘텐츠 블록(`.sb-page-inner > :not(h1)`)으로 렌더링을 기다리며, 페이지 식별은 H1 텍스트로 한다. 제품 `PageHeader` 컴포넌트와는 무관하다. Web·Chart·OrgChart(Admin)의 문서 위계는 H2 섹션 제목 16/24/700 → 12px → 얇은 구분선 → 16px → 콘텐츠이며, 섹션 사이는 28px(`--sb-page-section-gap`; `StorySectionGrid`의 column gap 24px은 별도 유지), `StateBoard`의 H3 라벨은 14/20/500 보조색이고 자체 구분선을 두지 않으며 행 사이 24px이다. 구분선 아래 문서 텍스트·사양 표 같은 정보 블록만 `.sb-page-indent`(16px, 640px 이하 12px)로 묶음 단위로 들여쓰고, 실제 컴포넌트·Pattern·Chart·OrgChart·SW·CENTER 미리보기와 테두리 있는 카드/칩 Grid는 섹션 왼쪽 기준선에 둔다. 예외로 그룹 보드(H3 그룹 라벨 + 해당 예제)는 같은 들여쓰기(`--sb-page-indent`)를 받아 섹션 제목(L1) → 그룹 라벨(L2) → 예제(L3) 위계를 만든다. `StateBoard`(`StorySection` 안)와 Card의 `CardBoard`는 `data-sb-board`로 표시만 하고, `story-page.css`가 섹션 본문의 직계 자식 중 보드이거나 보드를 품은 블록 하나에만 들여쓰기를 적용한다. 그래서 Story 전용 Grid의 열마다 단일 그룹 보드를 둔 구성도 Grid 전체가 한 번만 들여써지고 열 간격 24px이 유지된다(열별 중복 들여쓰기 금지). 보드가 없는 Sizes Grid(Input·Select·Toggle)는 Grid에 `.sb-page-indent`를 붙이며, Input·Select의 `Small / Medium / Large`는 실제 필드 Label(Primary)로 유지한다. 전체 폭 Pattern 미리보기(GNB·LNB·Loading Screen·Page Header)는 `indent={false}`로 섹션 기준선을 유지하고, 그룹 라벨 없이 예제만 있는 Toggle Group(내부 검증용)과 단일 미리보기(Menubar·Dropdown Menu·Context Menu 등)는 들여쓰지 않는다. 위·아래 여백은 16–24px, 좌우 여백은 기존 값(480px까지 16px, 768px부터 60px)을 유지한다. 값은 기존 Component Token 또는 Storybook 전용 px 값을 `--sb-page-*`로 이름 붙인 것이며 새 Design Token이 아니다. 섹션을 나란히 놓을 때는 `StorySectionGrid`를 쓰고, Story별 `!important`·`[&_.sb-page-*]` 덮어쓰기로 간격을 바꾸지 않는다. SW와 노선도(CENTER)는 이 페이지 프레임과 섹션 간격만 공유하고 컴포넌트 크기·글꼴·캔버스·카드 규격은 각자 유지한다. 페이지 제목은 사이드바 이름을 따르며, 한 그룹에 Story가 여럿이면 `그룹 — Story 이름`(예: `SW Header — 화면 너비`)으로 쓴다. 섹션 제목은 페이지 제목과 예제 카드 제목을 반복하지 않는 짧은 변형명(예: Chart `Basic`, `Label`, `Solid`)으로 쓴다.
|
||||
- Storybook 페이지 프레임(2026-11-11 확정)은 `src/components/story-page.tsx`·`story-page.css`의 `StoryPage`·`StoryPageHeader`·`StorySection`·`StorySectionGrid`가 공통으로 소유한다. 2026-11-11 사용자 요청으로 `StoryPageHeader`는 H1 페이지 제목·kicker·설명·SW 공통 안내 문구를 표시하지 않으며(호출과 props는 유지), 페이지는 위 여백(16–24px) 바로 아래 첫 섹션부터 시작한다. 단, Preview 문서 제목이 Story ID라 스크린 리더가 페이지를 식별할 수 있도록 `title`만 시각적으로 숨긴 접근성 전용 H1(`.sb-page-a11y-title`, 1px clip·문서 흐름 밖)로 렌더링한다. 검증 스크립트는 이 H1의 시각 스타일에 의존하지 않고 첫 콘텐츠 블록(`.sb-page-inner > :not(h1)`)으로 렌더링을 기다리며, 페이지 식별은 H1 텍스트로 한다. 제품 `PageHeader` 컴포넌트와는 무관하다. Web·Chart·OrgChart(Admin)의 문서 위계는 H2 섹션 제목 16/24/700 → 12px → 콘텐츠이며(2026-11-13 제목 아래 얇은 구분선과 16px 간격 제거), 섹션 사이는 28px(`--sb-page-section-gap`; `StorySectionGrid`의 column gap 24px은 별도 유지), `StateBoard`의 H3 라벨은 14/20/500 보조색이고 자체 구분선을 두지 않으며 행 사이 24px이다. 섹션 본문의 문서 텍스트·사양 표 같은 정보 블록만 `.sb-page-indent`(16px, 640px 이하 12px)로 묶음 단위로 들여쓰고, 실제 컴포넌트·Pattern·Chart·OrgChart·SW·CENTER 미리보기와 테두리 있는 카드/칩 Grid는 섹션 왼쪽 기준선에 둔다. 예외로 그룹 보드(H3 그룹 라벨 + 해당 예제)는 같은 들여쓰기(`--sb-page-indent`)를 받아 섹션 제목(L1) → 그룹 라벨(L2) → 예제(L3) 위계를 만든다. `StateBoard`(`StorySection` 안)와 Card의 `CardBoard`는 `data-sb-board`로 표시만 하고, `story-page.css`가 섹션 본문의 직계 자식 중 보드이거나 보드를 품은 블록 하나에만 들여쓰기를 적용한다. 그래서 Story 전용 Grid의 열마다 단일 그룹 보드를 둔 구성도 Grid 전체가 한 번만 들여써지고 열 간격 24px이 유지된다(열별 중복 들여쓰기 금지). 보드가 없는 Sizes Grid(Input·Select·Toggle)는 Grid에 `.sb-page-indent`를 붙이며, Input·Select의 `Small / Medium / Large`는 실제 필드 Label(Primary)로 유지한다. 전체 폭 Pattern 미리보기(GNB·LNB·Loading Screen·Page Header)는 `indent={false}`로 섹션 기준선을 유지하고, 그룹 라벨 없이 예제만 있는 Toggle Group(내부 검증용)과 단일 미리보기(Menubar·Dropdown Menu·Context Menu 등)는 들여쓰지 않는다. 위·아래 여백은 16–24px, 좌우 여백은 480px까지 16px, 768px부터 30px이다(2026-11-13 최대 60px → 30px, 180px LNB와 함께). 값은 기존 Component Token 또는 Storybook 전용 px 값을 `--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로 분리한다.
|
||||
|
||||
@@ -19,7 +19,7 @@ Implementation: current @hm-design/ui primitives / patterns (HM Collapsible + pl
|
||||
|---|---|
|
||||
| 구현 상태 | 구현 완료 (2026-10-03), 3뎁스 추가 (2026-10-06) |
|
||||
| 기반 | HM Collapsible + 일반 요소 (새 dependency 없음) |
|
||||
| 대표 Story | `Patterns/Layout/LNB` → `LNB` (States: Selected·Expanded·3뎁스 Selected, Variants: With Logo·Long Labels. Scroll·Standalone 반응형은 2026-11-09 대표 페이지에서 분리되어 아래 verification Story로 확인; verification: Third Level, Third Level Long Labels, Default, Selected, Expanded, Selected Child, Collapsed With Selection, With Logo, Long Labels, Scroll, Third Level Scroll, VERIFICATION ONLY · GNB + LNB, LNB Standalone Desktop, LNB Standalone Mobile, LNB Standalone Mobile Menu Open) |
|
||||
| 대표 Story | `Patterns/Layout/LNB` → `LNB` (States 한 구역: Selected·Expanded·3뎁스 Selected. With Logo·Long Labels·Scroll·Standalone 반응형은 2026-11-09 대표 페이지에서 분리되어 아래 verification Story로 확인; verification: Third Level, Third Level Long Labels, Default, Selected, Expanded, Selected Child, Collapsed With Selection, With Logo, Long Labels, Scroll, Third Level Scroll, VERIFICATION ONLY · GNB + LNB, LNB Standalone Desktop, LNB Standalone Mobile, LNB Standalone Mobile Menu Open, LNB Profile Menu Open) |
|
||||
| 보류 | §11 |
|
||||
|
||||
## 2. 목적 / 역할
|
||||
@@ -178,7 +178,7 @@ LNB Mode가 Desktop Brand/Profile과 Mobile Header/Sheet Profile을 담당한다
|
||||
| `--hm-lnb-width` | 240px (1023px 이하에서 0px) |
|
||||
| `--hm-lnb-join` | 18px. 콘텐츠 둥근 모서리·bleed |
|
||||
| `--hm-lnb-background` / `-background-end` | 5-stop gradient / `#266651` |
|
||||
| item | 1뎁스 min-height 48(`--hm-lnb-item-min-height`) + padding-block 10(`--hm-lnb-item-padding-block`), 14/28/700(`--hm-lnb-item-font-size`), gap 12, 아이콘 20, 글자 테두리 1.5px |
|
||||
| item | 1뎁스 min-height 48(`--hm-lnb-item-min-height`) + padding-block 10(`--hm-lnb-item-padding-block`), padding-start 12(`--hm-lnb-item-padding-start`, 2026-11-12 이전 18. Storybook 사이드바 `10 32 10 12`와 같음), 오른쪽 32(inset-end 20 + 12), 14/28/700(`--hm-lnb-item-font-size`), gap 12, 아이콘 20, 글자 테두리 1.5px |
|
||||
| `--hm-lnb-subitem-*` | 2뎁스(그룹 안 직속 링크·중첩 그룹 trigger) min-height 42(`-min-height`), padding-block 7(`-padding-block`), padding-start 68 |
|
||||
| `--hm-lnb-profile-min-height` | 48px. Profile 높이는 3뎁스 행과 독립이다 |
|
||||
| current | `--hm-sem-bg-content` + `--hm-sem-action-primary`, radius 8 0 0 8 |
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
--hm-lnb-item-min-height: 48px;
|
||||
/* 1-depth row: 28px line + 2 x 10px = 48px. */
|
||||
--hm-lnb-item-padding-block: 10px;
|
||||
--hm-lnb-item-padding-start: 18px;
|
||||
/* 1-depth text start 12px (2026-11-12, was 18px), same as the Storybook Manager sidebar rows. */
|
||||
--hm-lnb-item-padding-start: 12px;
|
||||
--hm-lnb-item-gap: 12px;
|
||||
--hm-lnb-item-font-size: 14px;
|
||||
--hm-lnb-item-line-height: 28px;
|
||||
|
||||
@@ -58,7 +58,7 @@ export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
// Representative-page layout (Storybook presentation only): equal columns on PC (iframe width 1000px and up), fewer below that
|
||||
// (200px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle and divider
|
||||
// (200px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle
|
||||
// has the width of its column; a section with fewer specimens than columns leaves the remaining columns empty.
|
||||
// The Card example spans two columns (about its previous width); the ratio boxes fill their column by design.
|
||||
const specimenColumns = {
|
||||
|
||||
@@ -77,7 +77,7 @@ export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
// Representative-page layout (Storybook presentation only): equal columns on PC (iframe width 1000px and up), fewer below that
|
||||
// (240px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle and divider
|
||||
// (240px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle
|
||||
// has the width of its column; a section with fewer specimens than columns leaves the remaining columns empty.
|
||||
const specimenColumns = {
|
||||
2: "min-[1000px]:grid-cols-2",
|
||||
|
||||
@@ -24,7 +24,7 @@ export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
// Representative-page layout (Storybook presentation only): equal columns on PC (iframe width 1000px and up), fewer below that
|
||||
// (280px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle and divider
|
||||
// (280px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle
|
||||
// has the width of its column; a section with fewer specimens than columns leaves the remaining columns empty.
|
||||
const specimenColumns = {
|
||||
2: "min-[1000px]:grid-cols-2",
|
||||
|
||||
@@ -143,7 +143,7 @@ export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
// Representative-page layout (Storybook presentation only): equal columns on PC (iframe width 1000px and up), fewer below that
|
||||
// (200px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle and divider
|
||||
// (200px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle
|
||||
// has the width of its column; a section with fewer specimens than columns leaves the remaining columns empty.
|
||||
// The Media example spans two columns; carousels fill their column by design.
|
||||
const specimenColumns = {
|
||||
@@ -167,7 +167,6 @@ function SpecimenGrid({ ids, columns, spans = {} }: { ids: string[]; columns: 2
|
||||
)
|
||||
}
|
||||
|
||||
// Representative Story: the common Story page; the sections group the same specimens.
|
||||
// Representative Story: Overview (Basic) is merged into Variants (Basic, Multiple items, Loop, Vertical), 4 columns on PC; Media stays under Usage.
|
||||
export const All: Story = {
|
||||
name: "Carousel",
|
||||
|
||||
@@ -81,7 +81,7 @@ type Story = StoryObj<typeof meta>
|
||||
|
||||
// Representative-page layout (Storybook presentation only): one Variants section, 2 equal columns x 2 rows on PC (iframe width
|
||||
// 1030px and up), 1 column below that. Each specimen sits in its own single-column StateBoard (min larger than any
|
||||
// column), so every title and divider has the width of its column. The Pagination root is `mx-auto flex w-full justify-center`;
|
||||
// column), so every title has the width of its column. The Pagination root is `mx-auto flex w-full justify-center`;
|
||||
// `justify-start` on the cell left-aligns the button group in its column without stretching it.
|
||||
const cellAlignLeft = "w-full [&_[data-slot=pagination]]:justify-start"
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
// Representative-page layout (Storybook presentation only): equal columns on PC (iframe width 1000px and up), fewer below that
|
||||
// (240px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle and divider
|
||||
// (240px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle
|
||||
// has the width of its column; a section with fewer specimens than columns leaves the remaining columns empty.
|
||||
const specimenColumns = {
|
||||
2: "min-[1000px]:grid-cols-2",
|
||||
|
||||
@@ -100,7 +100,7 @@ export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
// Representative-page layout (Storybook presentation only): equal columns on PC (iframe width 1000px and up), one column
|
||||
// below that. Each specimen sits in its own single-column StateBoard (min larger than any column), so the title, divider and
|
||||
// below that. Each specimen sits in its own single-column StateBoard (min larger than any column), so the title and
|
||||
// specimen start line up and unused columns stay empty. The frames keep their own size (max 560px wide).
|
||||
function StateGrid({ states, cols }: { states: BoardState[]; cols: 2 | 3 }) {
|
||||
return (
|
||||
|
||||
@@ -78,7 +78,7 @@ export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
// Representative-page layout (Storybook presentation only): equal columns on PC (iframe width 1000px and up), fewer below that
|
||||
// (300px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle and divider
|
||||
// (300px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle
|
||||
// has the width of its column; a section with fewer specimens than columns leaves the remaining columns empty.
|
||||
const specimenColumns = {
|
||||
2: "min-[1000px]:grid-cols-2",
|
||||
|
||||
@@ -21,7 +21,7 @@ export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
// Representative-page layout (Storybook presentation only): equal columns on PC (iframe width 1000px and up), fewer below that
|
||||
// (240px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle and divider
|
||||
// (240px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle
|
||||
// has the width of its column; a section with fewer specimens than columns leaves the remaining columns empty.
|
||||
const specimenColumns = {
|
||||
2: "min-[1000px]:grid-cols-2",
|
||||
|
||||
@@ -54,7 +54,7 @@ export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
// Representative-page layout (Storybook presentation only): equal columns on PC (iframe width 1000px and up), fewer below that
|
||||
// (240px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle and divider
|
||||
// (240px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle
|
||||
// has the width of its column; a section with fewer specimens than columns leaves the remaining columns empty.
|
||||
const specimenColumns = {
|
||||
2: "min-[1000px]:grid-cols-2",
|
||||
|
||||
@@ -10,7 +10,7 @@ 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
|
||||
// The labels are H3 (14/20/500, secondary) under the section title: they carry no divider of their own, and rows sit
|
||||
// 24px apart (one step tighter than the 28px section gap of story-page.css).
|
||||
// 2026-11-11: inside a StorySection the board is marked `data-sb-board`; story-page.css then indents, once, the block directly
|
||||
// under the section body that is or holds the board (16px / 12px at 640px and below, --sb-page-indent). Stories that put one
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/* Storybook presentation only. Common page frame for representative Stories; all selectors stay inside .sb-page.
|
||||
2026-11-11 compact document rhythm, shared by Web / Chart / OrgChart (SW and CENTER pages use the same frame only):
|
||||
2026-11-11: the H1 page title / description header is no longer shown (the H1 stays for screen readers only); pages start with the
|
||||
H2 section title 16/24/700 (body-md) → 12px → thin divider → 16px → content; sections 28px apart (2026-11-11 rhythm, first applied
|
||||
H2 section title 16/24/700 (body-md) → 12px (no divider or 16px margin since 2026-11-13) → content; sections 28px apart (2026-11-11 rhythm, first applied
|
||||
on Foundation / 폰트); 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.
|
||||
Side margins: 16px up to 480px, growing to 30px at 768px (60px before 2026-11-13): the SW breakpoints 1980 / 1065 / 727 depend on them.
|
||||
Only information blocks (text, spec tables) opt in to the content indent (.sb-page-indent); previews stay on the section edge,
|
||||
except group boards (H3 label + its specimens, marked data-sb-board by StateBoard / CardBoard), indented once per section block below.
|
||||
No design token is added; --sb-page-* only names this Storybook frame's values (component token values or plain px). */
|
||||
@@ -23,9 +23,10 @@
|
||||
.sb-page > .sb-page-inner {
|
||||
box-sizing: border-box;
|
||||
/* 2026-11-05 (user request): no max width; the Inner follows the page width. Side margin (each side) = 16px up to a
|
||||
480px viewport (358px Inner at 390px, so a 314px specimen fits), then linear to 60px at 768px (44px over 288px,
|
||||
11 / 72 of the viewport growth), 60px from 768px. The Inner keeps growing (about 0.69px per 1px), with no jump. */
|
||||
width: calc(100% - 2 * clamp(16px, calc(16px + (100vw - 480px) * 11 / 72), 60px));
|
||||
480px viewport (358px Inner at 390px, so a 314px specimen fits), then linear to the maximum at 768px, the maximum from 768px.
|
||||
2026-11-13 (user request, with the 180px LNB): maximum 60px → 30px (14px over 288px, 7 / 144 of the viewport growth);
|
||||
the 16px mobile margin is unchanged. The Inner keeps growing (about 0.9px per 1px), with no jump. */
|
||||
width: calc(100% - 2 * clamp(16px, calc(16px + (100vw - 480px) * 7 / 144), 30px));
|
||||
min-width: 0;
|
||||
margin-inline: auto;
|
||||
}
|
||||
@@ -48,10 +49,11 @@
|
||||
}
|
||||
|
||||
.sb-page .sb-page-section { margin-top: var(--sb-page-section-gap); scroll-margin-top: 16px; }
|
||||
/* 2026-11-13 (user request, all Story pages): the thin divider under the section title is removed, and so is the 16px margin
|
||||
that sat under it, so the title-to-content spacing is the header's own 12px padding. Every other border stays. */
|
||||
.sb-page .sb-page-section-header {
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid var(--hm-sem-stroke-input-default);
|
||||
margin-bottom: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.sb-page .sb-page-section-header .sb-page-section-title {
|
||||
|
||||
@@ -145,7 +145,7 @@ export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
// 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
|
||||
// (400px minimum). Each specimen sits in its own single-column StateBoard (min larger than any column), so every subtitle
|
||||
// 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
|
||||
|
||||
Vendored
+8
-3
@@ -56,6 +56,10 @@
|
||||
.hm-foundation .hm-brand-color-row[data-palette] .hm-brand-color-chip:first-child .hm-foundation-swatch { border-radius: var(--hm-radius-control-md) 0 0 var(--hm-radius-control-md); }
|
||||
.hm-foundation .hm-brand-color-row[data-palette] .hm-brand-color-chip:last-child .hm-foundation-swatch { border-radius: 0 var(--hm-radius-control-md) var(--hm-radius-control-md) 0; }
|
||||
}
|
||||
/* 2026-11-13: Default stands apart from the Light / Dark steps with a 2px page-background seam on each side. The seam is an
|
||||
outer shadow drawn over the neighbours, so chip widths, values and order do not change; at a line start it falls on the page. */
|
||||
.hm-foundation .hm-brand-color-row[data-palette] .hm-brand-color-chip[data-color-stage="default"] { position: relative; z-index: 1; }
|
||||
.hm-foundation .hm-brand-color-row[data-palette] .hm-brand-color-chip[data-color-stage="default"] .hm-foundation-swatch { box-shadow: -2px 0 0 var(--hm-sem-bg-content), 2px 0 0 var(--hm-sem-bg-content); }
|
||||
/* Name 14 Bold; role line 12 Regular (Default "기본색" 12 Medium); HEX 12. Dark text on a swatch reads thinner than white,
|
||||
so its role line and HEX use Medium. */
|
||||
.hm-foundation .hm-brand-color-row:is([data-palette], [data-company-id], [data-text-colors]) h3 { margin: 0; font-size: var(--hm-label-font-size); line-height: var(--hm-label-line-height); font-weight: var(--hm-font-weight-bold); color: inherit; }
|
||||
@@ -84,7 +88,7 @@
|
||||
.hm-foundation .hm-brand-color-row:is([data-company-id], [data-text-colors]) .hm-foundation-swatch { border-radius: var(--hm-radius-control-md); }
|
||||
|
||||
/* Typography: minimal type specimen page (presentation only, existing tokens; no card box, outer border or shadow).
|
||||
Section titles, rhythm (title → 12px → divider → 16px → content, sections 28px apart) and the content indent (.sb-page-indent on
|
||||
Section titles, rhythm (title → 12px → content, sections 28px apart) and the content indent (.sb-page-indent on
|
||||
Font Family, Font Weight and Specimens; Text Colors chips stay on the edge) come from the common Story page (story-page.css).
|
||||
Text levels: label / token 12/18/400 secondary, family and weight names 14/700 primary, specimen name and metrics 12/18 secondary;
|
||||
previews and samples carry the largest type. */
|
||||
@@ -97,6 +101,8 @@
|
||||
the row is one column with the preview first. */
|
||||
.hm-foundation :is(.hm-typo-rows, .hm-typo-group) { container-type: inline-size; }
|
||||
.hm-foundation .hm-typo-row { display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px 24px; align-items: center; }
|
||||
/* 2026-11-13: row content sits 12px inside on both sides; the row rules (border-top on the row) keep their start and width. */
|
||||
.hm-foundation .hm-typo-row { padding-inline: 12px; }
|
||||
.hm-foundation .hm-typo-row :is(p, code) { margin: 0; }
|
||||
.hm-foundation :is(.hm-typo-row-meta, .hm-typo-row-preview) { display: grid; gap: 2px; min-width: 0; }
|
||||
.hm-foundation .hm-typo-row-preview { order: -1; }
|
||||
@@ -109,8 +115,7 @@
|
||||
.hm-foundation .hm-typo-kicker { color: var(--hm-sem-fg-secondary); }
|
||||
.hm-foundation .hm-typo-family-name { font-weight: var(--hm-font-weight-bold); }
|
||||
.hm-foundation .hm-typo-row .hm-typo-family-preview { margin-bottom: 4px; font-size: var(--hm-page-header-title-font-size); line-height: var(--hm-page-header-title-line-height); }
|
||||
/* Font Weight: one row per weight (name + value · token | preview), 12px above and below with a thin rule between rows only
|
||||
(the section divider sits above the first row). */
|
||||
/* Font Weight: one row per weight (name + value · token | preview), 12px above and below with a thin rule between rows only. */
|
||||
.hm-foundation .hm-typo-weight { padding-block: 12px; }
|
||||
.hm-foundation .hm-typo-weight:first-child { padding-top: 0; }
|
||||
.hm-foundation .hm-typo-weight:last-child { padding-bottom: 0; }
|
||||
|
||||
@@ -610,7 +610,7 @@
|
||||
"--hm-lnb-brand-text-gap": "5px",
|
||||
"--hm-lnb-item-min-height": "48px",
|
||||
"--hm-lnb-item-padding-block": "10px",
|
||||
"--hm-lnb-item-padding-start": "18px",
|
||||
"--hm-lnb-item-padding-start": "12px",
|
||||
"--hm-lnb-item-gap": "12px",
|
||||
"--hm-lnb-item-font-size": "14px",
|
||||
"--hm-lnb-item-line-height": "28px",
|
||||
|
||||
Reference in new issue
Block a user