feat(ui): standardize LNB sizing and active depth effect

- Adopt the official LNB desktop spec as package defaults: 240px column,
  14px menu text, 48px 1-depth rows and 42px 2/3-depth rows, with new
  item/subitem padding-block and profile min-height tokens.
- Match --hm-lnb-current-depth to the Storybook Manager selected item:
  sampled soft inset falloff (left/top/bottom) and a fade over the last
  quarter, keeping the concave corners and content join.
- Drop the size overrides from the LNB Story CSS and update the LNB, GNB
  and Dashboard docs, AI guides and the theme verification baseline.
This commit is contained in:
shkim4 committed 2026-11-09 21:34:28 +09:00
1 parent 89f265ae2f
commit e42b67c53a
9 files changed
+95 -75

No files matched your search

+3 -3
View File
@@ -162,9 +162,9 @@ ui-viz에서 확인한 구조(실제 파일: `compositions/dashboard-variant-spe
| 영역 | ui-viz 구조 | 현재 HM 대응 | 이번 구현 |
|---|---|---|---|
| 조립 | Variant A(LNB 272 + 콘텐츠), Variant B(GNB 70 + 콘텐츠), Variant C(Compact 48). **GNB+LNB 동시 사용 예 없음** | `DashboardLayout` | slot 방식. 둘 다 / 하나만 |
| 조립 | Variant A(LNB 240 + 콘텐츠. 2026-11-09 이전 272), Variant B(GNB 70 + 콘텐츠), Variant C(Compact 48). **GNB+LNB 동시 사용 예 없음** | `DashboardLayout` | slot 방식. 둘 다 / 하나만 |
| GNB | 70px green, 콘텐츠와 12px 접점 | `GNB` | slot `header` |
| LNB | 272px green, 콘텐츠와 18px 접점 | `LNB` | slot `sidebar` |
| LNB | 240px green(2026-11-09 이전 272), 콘텐츠와 18px 접점 | `LNB` | slot `sidebar` |
| Content surface | `#F5F0E5`, padding 34, 접점 쪽 radius(LNB 18 / GNB 12), 그림자 `-8px 0 18px`(LNB) · `0 -8px 18px`(GNB) rgba(7,30,24,.18), 별도 border 없음 | 신규 | `DashboardMain` |
| 스크롤 | "메뉴/헤더는 고정, 콘텐츠 영역에서만 스크롤"(Confirmed). `overflow-y:auto`, `overscroll-behavior: contain` | 신규 | `DashboardMain`이 스크롤 소유 |
| Page heading | breadcrumb 12px(정적 `p`, 컴포넌트 아님) → 제목 28 → 설명, space-between·align end·gap 24, **하단 20px** | Breadcrumb + 신규 Pattern | `PageHeader` |
@@ -260,7 +260,7 @@ Compact / Sidebar mobile hide: NONE
| 항목 | ui-viz | 현재 HM | 분류 |
|---|---|---|---|
| GNB 70 / LNB 272 | 같음 | `--hm-gnb-height` / `--hm-lnb-width` | MATCHED |
| GNB 70 / LNB 240 | 같음(LNB 폭은 2026-11-09 공식 규격 채택, ui-viz 272와 다름) | `--hm-gnb-height` / `--hm-lnb-width` | MATCHED |
| 콘텐츠 면 색·padding 34 | `#F5F0E5`, 34 | 같음 | MATCHED |
| 접점 radius (LNB 18 / GNB 12), shadow | 같음 | 같음. 소유를 Dashboard로 이동 | MATCHED |
| 헤더·메뉴 고정, 콘텐츠만 스크롤 | 같음 | `DashboardMain` 스크롤 | MATCHED |
+2 -2
View File
@@ -183,7 +183,7 @@ src/patterns/gnb.stories.tsx
공용 `@hm-design/ui/theme.css`의 opt-in HTML attribute 계약:
- 프레임: `data-hm-gnb-layout="sidebar"`. 소비자가 높이를 지정하며 Grid가 GNB의 자연 높이와 남은 본문 높이를 배분한다.
- 본문: 프레임의 직계 `data-hm-gnb-body`. Desktop은 기존 `--hm-lnb-width` 열 + 가용 콘텐츠 열, Mobile은 콘텐츠 한 열이다.
- 본문: 프레임의 직계 `data-hm-gnb-body`. Desktop은 기존 `--hm-lnb-width` 열 + 가용 콘텐츠 열, Mobile은 콘텐츠 한 열이다. 열 폭과 항목 크기는 LNB 공식 규격(2026-11-09: 240px, 14px, 1뎁스 48px·2/3뎁스 42px)을 그대로 따르며 GNB 전용 override는 두지 않는다.
- Sidebar: 본문의 직계 `<LNB data-hm-gnb-sidebar>`. 기존 `data-slot="lnb"`는 유지한다. 이 Reference 범위에서 본문과 같은 배경, 세로 divider 한 개를 사용한다. Green은 Primary, `data-hm-color="brown"`은 기존 Brown 팔레트로 선택·Hover를 표현한다. 펼침 화살표는 유지하며 아이콘은 선택적으로 전달한다. 신규 AI GNB_ONLY에는 Sidebar를 추가하지 않는다.
- Main: 본문의 직계 `<main data-hm-gnb-main>`. 공용 CSS가 면·접점·스크롤을 제공하며 콘텐츠 padding은 소비자가 정한다. GNB의 상단 접점과 LNB의 측면 접점은 기존 값을 재사용한다.
@@ -284,4 +284,4 @@ export function Example() {
사용자 메뉴와 콘텐츠 padding은 기존 GNB 사용 예제에 따라 소비자가 추가한다. Theme Source, LNB 원본, Runtime 계약, 공개 API는 변경하지 않는다. 공통 요약은 [AGENTS.md](../../AGENTS.md), 탐색 경로는 [Reference Index](../guidelines/component-pattern-reference.md)를 따른다.
검증(2026-10-08, 기존 Storybook 6024): 1440px Sidebar 272px / Green Light 2, 2·3Depth 선택·그룹 Enter/Space·Hover·선택 경로·메뉴 스크롤 확인. 768px·375px에서 GNB 56px / Sidebar 숨김 / Main 전체 폭 / 흰 Sheet 1개 / 링크 44px·14px / 선택 시 닫기·Escape·포커스 복귀 확인. 열린 Sheet에서 Desktop 복귀 시 닫힘과 선택 경로 보존 확인. 기존 GNB Overview·LNB 대표 메뉴·Dashboard Header computed style 비교 동일, 검사한 화면의 Console error 없음. 앱 TypeScript 검사 통과. 전체 프로젝트 회귀 테스트와 실기기 터치 검증은 수행하지 않았다.
검증(2026-10-08, 기존 Storybook 6024): 1440px Sidebar 272px(당시 값. 2026-11-09부터 LNB 규격 240px) / Green Light 2, 2·3Depth 선택·그룹 Enter/Space·Hover·선택 경로·메뉴 스크롤 확인. 768px·375px에서 GNB 56px / Sidebar 숨김 / Main 전체 폭 / 흰 Sheet 1개 / 링크 44px·14px / 선택 시 닫기·Escape·포커스 복귀 확인. 열린 Sheet에서 Desktop 복귀 시 닫힘과 선택 경로 보존 확인. 기존 GNB Overview·LNB 대표 메뉴·Dashboard Header computed style 비교 동일, 검사한 화면의 Console error 없음. 앱 TypeScript 검사 통과. 전체 프로젝트 회귀 테스트와 실기기 터치 검증은 수행하지 않았다.
+16 -12
View File
@@ -81,10 +81,10 @@ Collapsible의 trigger/content는 `asChild`로 자체 `button`/`div`를 써서 C
| 항목 | 명세 | 구현 | 분류 |
|---|---|---|---|
| 글자 | 1·2뎁스 18/28/700, 3뎁스 18/24/500, 선택 700 | 같음 | MATCHED |
| 3뎁스 행 | 최소 48, 상하 12, 링크 padding-left 88(2뎁스 68 + 20) | 같음 (1·2뎁스 56 유지) | MATCHED |
| 글자 | 1·2뎁스 18/28/700, 3뎁스 18/24/500, 선택 700 | 크기만 14로 변경: 1·2뎁스 14/28/700, 3뎁스 14/24/500, 선택 700 유지 | INTENTIONAL_CHANGE (2026-11-09) |
| 3뎁스 행 | 최소 48, 상하 12, 링크 padding-left 88(2뎁스 68 + 20) | 최소 42, 상하 9, padding-left 88 유지 (1뎁스 48, 2뎁스 42) | INTENTIONAL_CHANGE (2026-11-09) |
| 3뎁스 글자 장식 | 외곽선·그림자 없음 | 같음 | MATCHED |
| 긴 이름 | 줄바꿈, 행 높이 증가 | 48 / 72 / 96 (1·2·3줄), ellipsis 없음, 가로 overflow 0 | MATCHED |
| 긴 이름 | 줄바꿈, 행 높이 증가 | 3뎁스 기준 42 / 66 / 90 (1·2·3줄, 24px 줄 + 상하 9), ellipsis 없음, 가로 overflow 0. 2026-11-09 이전 값은 48 / 72 / 96 | INTENTIONAL_CHANGE (2026-11-09) |
| 선택 | Beige + `--hm-sem-action-primary`, 오른쪽 곡선 | 기존 current 규칙 그대로(폭 +1, 위·아래 오목 곡선) | MATCHED |
| 상위 경로 | 1·2뎁스 노랑, 접혀도 유지 | `current` → `--hm-yellow-accent` | MATCHED |
| 연결선 | 3뎁스만, 1px, 세로선 x 68, 가로 12, 글자까지 8, 행 세로 중앙, 마지막 행 중앙에서 끝 | 행마다 background 2겹(세로 `1px × 100%`, 마지막 행 `1px × 50%`, 가로 `12 × 1` at `calc(50% - 0.5px)`). DOM·tab stop·aria 없음 | MATCHED / IMPLEMENTATION_CHANGE(명세의 "배경 레이어" 방식) |
@@ -153,11 +153,11 @@ LNB Mode가 Desktop Brand/Profile과 Mobile Header/Sheet Profile을 담당한다
| 항목 | ui-viz | 최종 HM | 분류 |
|---|---|---|---|
| 폭 | track 272, visual 290(오른쪽 18 bleed) | 열 272(`--hm-lnb-width`), 뒤로 18(`--hm-lnb-join`) 같은 색 연장 | MATCHED (연장은 `::after`) |
| 폭 | track 272, visual 290(오른쪽 18 bleed) | 열 240(`--hm-lnb-width`, 2026-11-09 공식 규격 채택), 뒤로 18(`--hm-lnb-join`) 같은 색 연장 | INTENTIONAL_CHANGE (폭만 ui-viz 272와 다름, 연장은 `::after`) |
| 배경 | 90° 5-stop `#123328/#205241/#266651/#249473/#266651` 0/30/50/90/100%, 트랙 폭, 트랙 밖 `#266651` | 같음 (`--hm-green-*`) | MATCHED |
| 상단 | 위 28, 브랜드 padding 0 10 22, 메뉴 위 20 | 위 28, 브랜드 inset 30(=20+10), 아래 22, 메뉴 위 20 | MATCHED |
| 브랜드 | 로고 조건부, 위 로고·아래 서비스명, eyebrow 11/700, 이름 17 | 같음 | MATCHED |
| 항목 | 18/28/700, 최소 56, padding 14 12 14 18, White, 아이콘 20 opacity .5, 글자 테두리 1.5px + 그림자 `1px 2px 1px` | 같음 | MATCHED |
| 항목 | 18/28/700, 최소 56, padding 14 12 14 18, White, 아이콘 20 opacity .5, 글자 테두리 1.5px + 그림자 `1px 2px 1px` | 14/28/700, 1뎁스 최소 48(padding 상하 10), 2뎁스 최소 42(상하 7). 색·아이콘·글자 테두리는 같음 | INTENTIONAL_CHANGE (2026-11-09) |
| hover | Black 10%, 콘텐츠 경계까지, 시작 쪽 radius 4 | 같음 | MATCHED |
| current | Beige + `#123328`, 왼쪽 radius 8, 오른쪽 20+1 연장, 위·아래 18 오목 곡선, 안쪽 깊이 그림자(마지막 25% fade), 글자 효과 없음 | 같음. 깊이는 span 대신 background gradient 3겹으로 근사 | MATCHED / IMPLEMENTATION_CHANGE |
| nested(2뎁스) | 상위 = disclosure 버튼, 화살표 접힘 → 펼침 90°, 펼친 그룹 Black 8% + 1px `#266651` 선 + 왼쪽 radius 8, 하위 padding-left 68, 하단 8, 하위 선택 시 상위 노랑 `#FFC600` | 같음. HM Collapsible(Radix) 사용 | MATCHED / IMPLEMENTATION_CHANGE |
@@ -175,13 +175,15 @@ LNB Mode가 Desktop Brand/Profile과 Mobile Header/Sheet Profile을 담당한다
| Token | 값 |
|---|---|
| `--hm-lnb-width` | 272px (1023px 이하에서 0px) |
| `--hm-lnb-width` | 240px (1023px 이하에서 0px) |
| `--hm-lnb-join` | 18px. 콘텐츠 둥근 모서리·bleed |
| `--hm-lnb-background` / `-background-end` | 5-stop gradient / `#266651` |
| item | min-height 56, 18/28/700, gap 12, 아이콘 20, 글자 테두리 1.5px |
| 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 |
| `--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 |
| group | open face Black 8%, 선 1px `--hm-sem-palette-green-light-2`, 하위 padding-start 68, current 글자 `--hm-sem-palette-yellow-accent` |
| `--hm-lnb-third-*` | min-height 48, padding-block 12, text padding-start 88(메뉴 행 기준), line-height 24, weight 500 / current 700, connector x `= --hm-lnb-subitem-padding-start`(68)·length 12·width 1, 모든 상태에서 `--hm-sem-palette-green-tint` 40%. 실제 link는 connector 끝부터 시작 |
| `--hm-lnb-third-*` | min-height 42, padding-block 9, text padding-start 88(메뉴 행 기준), line-height 24, weight 500 / current 700, connector x `= --hm-lnb-subitem-padding-start`(68)·length 12·width 1, 모든 상태에서 `--hm-sem-palette-green-tint` 40%. 실제 link는 connector 끝부터 시작 |
새 Primitive: `--hm-yellow-accent: #ffc600`(`primitives.css`). ui-viz `color.action.accent-on-green`이며 HM 팔레트에 없어 추가했다. 그 외 새 Semantic은 없다.
@@ -212,11 +214,11 @@ viewport 높이 shell에서 모바일 Header와 main을 세로 flex로 배분할
> HISTORICAL_RECORD: 아래 날짜별 geometry·상태 측정은 당시 기록이다. 현재 링크 interaction 범위·connector 색은 §3a/§5, Profile interaction은 §7을 따른다.
- 1280: current 항목 x 20→273, 높이 56, 18/28/700, Beige `#F5F0E5`/`#123328`, radius `8 0 0 8`, 그룹·하위 x 20→272. 하위 선택 시 상위 `rgb(255,198,0)`, 접힌 채여도 유지. 긴 이름은 줄바꿈(112px), 스크롤 영역은 메뉴만 스크롤하고 profile은 아래 22px 고정. 그룹 trigger Enter로 펼침.
- 1280(당시 값. 2026-11-09부터 공식 규격은 열 240, 1뎁스 48·2/3뎁스 42, 14px): current 항목 x 20→273, 높이 56, 18/28/700, Beige `#F5F0E5`/`#123328`, radius `8 0 0 8`, 그룹·하위 x 20→272. 하위 선택 시 상위 `rgb(255,198,0)`, 접힌 채여도 유지. 긴 이름은 줄바꿈(112px), 스크롤 영역은 메뉴만 스크롤하고 profile은 아래 22px 고정. 그룹 trigger Enter로 펼침.
- 1000px: 열 `display:none`, 가로 overflow 없음. 390px은 GNB 모바일 메뉴로 대체.
- `npm run typecheck`, `npm run build` 통과. 기존 primitive Story 변화 없음.
- 3뎁스(2026-10-06, Storybook): 위 §3a 수치를 computed style로 확인(1280 열 272, 3뎁스 x 20 → 272/선택 273, 그룹 2개 모두 x 20·폭 252, 안쪽 그룹 배경 투명·`::before` 없음). 키보드: Enter로 바깥 그룹 접힘(노랑 유지, 3뎁스·선 사라짐), Space로 다시 펼침(안쪽 그룹 열림 유지), Tab → 안쪽 그룹 Enter로 접힘. trigger는 `button` + `aria-expanded`/`aria-controls`, 선택 링크 `aria-current="page"`, focus-visible 흰 outline. 기존 Selected Child 수치 변화 없음(2뎁스 68/56/700, 그룹 하단 8, 선 1px).
- 3뎁스(2026-10-06, Storybook): 위 §3a 수치를 computed style로 확인(당시 1280 열 272, 3뎁스 x 20 → 272/선택 273, 그룹 2개 모두 x 20·폭 252, 안쪽 그룹 배경 투명·`::before` 없음). 키보드: Enter로 바깥 그룹 접힘(노랑 유지, 3뎁스·선 사라짐), Space로 다시 펼침(안쪽 그룹 열림 유지), Tab → 안쪽 그룹 Enter로 접힘. trigger는 `button` + `aria-expanded`/`aria-controls`, 선택 링크 `aria-current="page"`, focus-visible 흰 outline. 기존 Selected Child 수치 변화 없음(2뎁스 68/56/700, 그룹 하단 8, 선 1px).
- 스크롤 소유(2026-10-06, Storybook): 열 = flex column, brand·profile `flex: none`, `LNBNav`만 `flex: 1 1 auto; min-height: 0; overflow-y: auto`(열 overflow 없음, 높이는 Story shell / Dashboard Layout이 결정). 메뉴 위 20을 nav margin 2 + padding 18에서 nav padding 20(`--hm-lnb-nav-padding-top`)으로 옮겨 스크롤 영역이 brand 바로 아래에서 시작(첫 항목 y 그대로). 측정: Scroll(460) nav 93.7→370 = brand bottom → profile top, client 276 / scroll 830, 최하단 마지막 항목 bottom 351.7(= nav bottom − 18); Third Level Scroll(460) client 276 / scroll 990, 3뎁스 선택 행·곡선·연결선 함께 스크롤; Dashboard Third Level 1280·1024×600 nav 163.7→510, client 346 / scroll 526, 최하단 491.7. 1023px 열 `display:none`, 가로 overflow 없음.
@@ -230,7 +232,9 @@ viewport 높이 shell에서 모바일 Header와 main을 세로 flex로 배분할
Collapsible, Navigation Menu 등 기존 primitive 코드·theme CSS는 수정하지 않았다. `primitives.css`에 `--hm-yellow-accent` 한 줄만 추가했다.
**공용 패키지 기본값 vs Storybook 전용 오버라이드:** 이 문서의 공용 패키지 기본 규격(열 272px, 메뉴 18px, 행 56px 등)과 `packages/ui`·Theme Token은 변경하지 않았다. `Patterns/Layout/LNB` 대표 Story(`src/patterns/lnb.stories.tsx`)만 `src/patterns/lnb.stories.css`로 PC 열 너비 240px(1024px 이상, 최소 너비 212px), 메뉴 폰트 14px, 행 높이 depth1 48px·depth2·3 42px를 Storybook 표시용으로 덮어쓴다(`.hm-lnb-story` 범위 안에서만 적용하며 소비 프로젝트에는 영향이 없다).
**공식 기본 규격 (2026-11-09):** PC 열 240px, 메뉴 14px, 행 높이 1뎁스 48px·2/3뎁스 42px를 `packages/ui/src/theme/lnb.css`의 공식 기본값으로 채택했다. 이전 값(272px, 18px, 56px / 3뎁스 48px)은 위 날짜별 검증 기록에만 남는다. 모바일 규격(`--hm-lnb-mobile-*`)과 `DashboardSidebar`(자체 `--hm-dashboard-sidebar-width` 272px)는 변경하지 않았다. GNB ONLY 내부 Sidebar는 LNB 부품과 `--hm-lnb-width`를 재사용하므로 같은 규격(240px / 14px / 48px / 42px)을 따른다. 소비 프로젝트는 크기를 Storybook 화면이 아니라 `--hm-lnb-*` 토큰에서 읽어야 하며, 새 CSS 변수는 `--hm-lnb-item-padding-block`, `--hm-lnb-subitem-min-height`, `--hm-lnb-subitem-padding-block`, `--hm-lnb-profile-min-height` 4개다.
**Storybook 전용 표시(`src/patterns/lnb.stories.css`):** `Patterns/Layout/LNB` 대표 Story만 `.hm-lnb-story` 범위 안에서 최소 너비 212px, 브랜드 아래 padding 8px, Profile 글자(14/12/700), 구분선을 덮어쓴다. 크기 규격은 더 이상 덮어쓰지 않는다.
## 10. 남은 차이
@@ -243,7 +247,7 @@ Collapsible, Navigation Menu 등 기존 primitive 코드·theme CSS는 수정하
| 실기기, 1024~1280 사이 시각 | 미확인 |
| 4뎁스 이상 | 명세 없음. 3뎁스 규칙만 정의(더 깊이 중첩하면 3뎁스 모양이 반복된다) |
| 선택 경로 전체 연결선을 선택색으로 칠할지 | 현재 모든 connector는 Green Tint 40%를 유지한다(§3a/§5). 선택은 Beige 면 + Primary 글자 + 700 weight로 표현하며, 경로 전체 선의 선택색은 별도 결정 전까지 적용하지 않는다 |
| LNB 모바일 Sheet 그룹 접기·상위 current 표시 | 현재 링크 목록 방식. Desktop 18px / Mobile 14px 유지 |
| LNB 모바일 Sheet 그룹 접기·상위 current 표시 | 현재 링크 목록 방식. Desktop 14px(2026-11-09, 이전 18px) / Mobile 14px 유지 |
| LNB Mobile Header 공개 API | 구현 완료. GNB/Dashboard 의존성 없음 |
## 11. 관련 파일
+1
View File
@@ -31,6 +31,7 @@
- 요청이 모드를 말하지 않으면 화면 목적(서비스 상단 메뉴 = GNB, 계층 업무 메뉴 = LNB, 운영 현황 = DASHBOARD)으로 고르고 **선택 근거를 보고**합니다. 임의로 Sidebar를 추가하지 않습니다.
- 혼합 요청(GNB + LNB 등)은 정책 충돌로 안내하고 하나를 고르게 합니다. Drawer·Hamburger 등 대체 메뉴를 추측해 만들지 않습니다.
- Breadcrumb·FilterBar·사용자 Dropdown은 Navigation 모드가 아닙니다.
- **LNB 크기의 기준은 패키지 토큰입니다.** `@hm-design/ui`의 `--hm-lnb-*`가 정의한 값(Desktop 열 240px, 메뉴 14/28/700, 1뎁스 행 48px, 2·3뎁스 행 42px, Profile 48px, 모바일 Header 56px·링크 44px)을 그대로 씁니다. Storybook 화면에서 눈으로 잰 값이나 과거 예제의 272px·18px·56px를 새로 정하지 않고, `--hm-lnb-width` 등을 소비 CSS에서 다시 정의하지 않습니다. 레이아웃 열은 `var(--hm-lnb-width)`로 읽습니다.
```tsx
// LNB ONLY 골격
+1
View File
@@ -68,6 +68,7 @@ Brown·Orange 계열 팔레트(`--hm-sem-palette-brown-*`, `--hm-sem-palette-ora
| Select Trigger 높이 | medium 34 · large 44(기본) | — |
| Control radius | 4px | `--hm-radius-control-md` → `--hm-radius-button`, `--hm-radius-input`, `--hm-radius-select`, `--hm-radius-container` |
| Card | border 1px, padding 24px, radius 4px, 그림자 없음 | `--hm-card-*` |
| LNB(Desktop) | 열 240px, 메뉴 14/28/700, 1뎁스 행 48 · 2·3뎁스 행 42, Profile 48, 콘텐츠 접점 18. 모바일 Header 56 · 링크 44 | `--hm-lnb-width`, `--hm-lnb-item-*`, `--hm-lnb-subitem-*`, `--hm-lnb-third-*`, `--hm-lnb-profile-min-height`, `--hm-lnb-join` |
| Dialog | radius 8px, padding 24px, Header 아래 16px, Footer 위 16px, 버튼 간격 8px, 그림자 `0 0 12px` 검정 20% | `--hm-dialog-*` |
## 5. 상태
+53 -13
View File
@@ -4,7 +4,7 @@
Black 10% to the same edge; an open group is a Black 8% face with a 1px green line. Spacing values are pattern-local. */
:root, [data-hm-theme] {
/* Exposed for the layout: grid column width and the surface that continues behind the content's rounded corners. */
--hm-lnb-width: 272px;
--hm-lnb-width: 240px;
--hm-lnb-join: 18px;
--hm-lnb-background: linear-gradient(90deg, var(--hm-sem-action-primary) 0%, var(--hm-sem-palette-green-light-1) 30%, var(--hm-sem-palette-green-light-2) 50%, var(--hm-sem-palette-green-light-3) 90%, var(--hm-sem-palette-green-light-2) 100%);
--hm-lnb-background-end: var(--hm-sem-palette-green-light-2);
@@ -29,10 +29,12 @@
--hm-lnb-brand-name-size: 17px;
--hm-lnb-brand-text-gap: 5px;
--hm-lnb-item-min-height: 56px;
--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;
--hm-lnb-item-gap: 12px;
--hm-lnb-item-font-size: 18px;
--hm-lnb-item-font-size: 14px;
--hm-lnb-item-line-height: 28px;
--hm-lnb-item-font-weight: 700;
--hm-lnb-item-radius: 4px 0 0 4px;
@@ -46,11 +48,38 @@
--hm-lnb-current-background: var(--hm-sem-bg-content);
--hm-lnb-current-text: var(--hm-sem-action-primary);
--hm-lnb-current-radius: 8px 0 0 8px;
/* Inset depth of the current face: left edge, top, bottom; the last quarter fades so the content side stays open. */
/* Inset depth of the current face (left, top, bottom), matched to the Storybook Manager's selected item: inset box-shadows
6px 0 8px -5px Black 30%, 0 3px 5px -3px Black 20% and 0 -2px 4px -3px Black 20%, faded out over the last quarter. A background
cannot hold a box-shadow and ::before / ::after already draw the concave corners, so the blurred falloff is sampled as gradient
stops (left 10px, top 5px, bottom 4px), and the first layer repaints the face color from 75% to 100% so the depth fades toward
the content side. */
--hm-lnb-current-depth:
linear-gradient(to right, color-mix(in srgb, var(--hm-sem-palette-black) 30%, transparent), transparent 6px) 0 0 / 75% 100% no-repeat,
linear-gradient(to bottom, color-mix(in srgb, var(--hm-sem-palette-black) 20%, transparent), transparent 3px) 0 0 / 75% 100% no-repeat,
linear-gradient(to top, color-mix(in srgb, var(--hm-sem-palette-black) 20%, transparent), transparent 3px) 0 0 / 75% 100% no-repeat;
linear-gradient(to right, transparent, var(--hm-lnb-current-background)) 100% 0 / 25% 100% no-repeat,
linear-gradient(to right,
color-mix(in srgb, var(--hm-sem-palette-black) 20%, transparent) 0,
color-mix(in srgb, var(--hm-sem-palette-black) 15.7%, transparent) 1px,
color-mix(in srgb, var(--hm-sem-palette-black) 12.2%, transparent) 2px,
color-mix(in srgb, var(--hm-sem-palette-black) 9.5%, transparent) 3px,
color-mix(in srgb, var(--hm-sem-palette-black) 7.1%, transparent) 4px,
color-mix(in srgb, var(--hm-sem-palette-black) 5%, transparent) 5px,
color-mix(in srgb, var(--hm-sem-palette-black) 3.3%, transparent) 6px,
color-mix(in srgb, var(--hm-sem-palette-black) 2.2%, transparent) 7px,
color-mix(in srgb, var(--hm-sem-palette-black) 1.4%, transparent) 8px,
color-mix(in srgb, var(--hm-sem-palette-black) 0.8%, transparent) 9px,
transparent 10px),
linear-gradient(to bottom,
color-mix(in srgb, var(--hm-sem-palette-black) 12%, transparent) 0,
color-mix(in srgb, var(--hm-sem-palette-black) 8.5%, transparent) 1px,
color-mix(in srgb, var(--hm-sem-palette-black) 5%, transparent) 2px,
color-mix(in srgb, var(--hm-sem-palette-black) 2.3%, transparent) 3px,
color-mix(in srgb, var(--hm-sem-palette-black) 0.8%, transparent) 4px,
transparent 5px),
linear-gradient(to top,
color-mix(in srgb, var(--hm-sem-palette-black) 9%, transparent) 0,
color-mix(in srgb, var(--hm-sem-palette-black) 5.1%, transparent) 1px,
color-mix(in srgb, var(--hm-sem-palette-black) 2.4%, transparent) 2px,
color-mix(in srgb, var(--hm-sem-palette-black) 0.7%, transparent) 3px,
transparent 4px);
--hm-lnb-group-open-background: color-mix(in srgb, var(--hm-sem-palette-black) 8%, transparent);
--hm-lnb-group-open-line: 1px solid var(--hm-sem-palette-green-light-2);
@@ -58,14 +87,17 @@
--hm-lnb-group-current-text: var(--hm-sem-palette-yellow-accent);
--hm-lnb-group-arrow-size: 18px;
--hm-lnb-subitem-padding-start: 68px;
/* 2-depth row (links directly in a group, nested group triggers): 28px line + 2 x 7px = 42px. */
--hm-lnb-subitem-min-height: 42px;
--hm-lnb-subitem-padding-block: 7px;
/* Align the 2-depth link surface with the 1-depth group text; retain its existing text inset. */
--hm-lnb-subitem-surface-inset: calc(var(--hm-lnb-item-padding-start) + var(--hm-lnb-group-arrow-size) + var(--hm-lnb-item-gap));
--hm-lnb-group-padding-bottom: 8px;
/* 3-depth: the items of an `LNBGroup` nested in a group. Same 18px; the hierarchy is the extra 20px indent and the connector
(a vertical line at the 2-depth text start, a 12px branch to each row's middle, 8px to the text; it ends at the last row). */
--hm-lnb-third-min-height: 48px;
--hm-lnb-third-padding-block: 12px;
/* 3-depth: the items of an `LNBGroup` nested in a group. Same font size as the other depths; the hierarchy is the extra 20px indent and the connector
(a vertical line at the 2-depth text start, a 12px branch to each row's middle, 8px to the text; it ends at the last row). 42px = 24px line + 9px padding x2. */
--hm-lnb-third-min-height: 42px;
--hm-lnb-third-padding-block: 9px;
--hm-lnb-third-padding-start: 88px;
--hm-lnb-third-line-height: 24px;
--hm-lnb-third-font-weight: 500;
@@ -79,6 +111,7 @@
--hm-lnb-profile-padding-x: 30px;
--hm-lnb-profile-padding-top: 22px;
--hm-lnb-profile-gap: 12px;
--hm-lnb-profile-min-height: 48px;
--hm-lnb-profile-name-size: 16px;
--hm-lnb-profile-description-size: 12px;
}
@@ -150,7 +183,7 @@
max-width: calc(100% - 2 * (var(--hm-lnb-profile-padding-x) - 8px));
margin-top: auto;
margin-inline: calc(var(--hm-lnb-profile-padding-x) - 8px);
min-height: var(--hm-lnb-third-min-height);
min-height: var(--hm-lnb-profile-min-height);
padding: 0 8px;
border: 0;
background: transparent;
@@ -211,7 +244,7 @@
gap: var(--hm-lnb-item-gap);
width: calc(100% - 1px);
min-height: var(--hm-lnb-item-min-height);
padding: 14px calc(var(--hm-lnb-inset-end) + 12px) 14px var(--hm-lnb-item-padding-start);
padding: var(--hm-lnb-item-padding-block) calc(var(--hm-lnb-inset-end) + 12px) var(--hm-lnb-item-padding-block) var(--hm-lnb-item-padding-start);
border: 0;
border-radius: var(--hm-lnb-item-radius);
background: transparent;
@@ -329,6 +362,13 @@
[data-slot="lnb-group-content"] [data-slot="lnb-item"]:not([aria-current="page"]):hover {
border-radius: var(--hm-lnb-item-radius);
}
/* 2-depth rows (links directly in a group, nested group triggers): 42px = 28px line + 2 x 7px. */
[data-slot="lnb-group-content"] > [data-slot="lnb-item"],
[data-slot="lnb-group-content"] [data-slot="lnb-group-trigger"] {
min-height: var(--hm-lnb-subitem-min-height);
padding-top: var(--hm-lnb-subitem-padding-block);
padding-bottom: var(--hm-lnb-subitem-padding-block);
}
[data-slot="lnb-group-content"] [data-slot="lnb-item"][aria-current="page"] {
width: calc(100% + 1px);
margin-right: -1px;
+5 -36
View File
@@ -1,27 +1,16 @@
/* Storybook presentation only: Patterns/Layout/LNB Stories. Scoped by the decorator wrapper in lnb.stories.tsx, so the shared
@hm-design/ui LNB Pattern, its tokens and every other Story keep their values.
- min-width 212px: a floor, not a fixed width. The column follows --hm-lnb-width (240px from 1024px, see below) and may be wider.
- menu font 14px: --hm-lnb-item-font-size is redefined for this subtree; weight (700) and line height (28px) stay.
- row height: depth1 48px, depth2 and deeper 42px. Heights come from min-height + vertical padding (not a fixed height), so a
label that wraps grows the row instead of being clipped. Current and hover only change color/background, not the height. */
/* Storybook presentation only: Patterns/Layout/LNB Stories. Scoped by the decorator wrapper in lnb.stories.tsx.
Column width (240px), menu font (14px) and row heights (depth1 48px, depth2/3 42px) are now the @hm-design/ui package defaults
(packages/ui/src/theme/lnb.css); only the Story-specific presentation below stays here.
- min-width 212px: a floor, not a fixed width. The column follows --hm-lnb-width and may be wider.
- brand bottom padding 8px, profile text, and divider lines. */
.hm-lnb-story {
display: contents;
--hm-lnb-item-font-size: 14px;
/* Existing variable: min-height of every item and group trigger (used by nothing else). */
--hm-lnb-item-min-height: 48px;
/* Existing variable: bottom padding of [data-slot="lnb-brand"] (22px by default; top 0 and the 30px sides are unchanged). */
--hm-lnb-brand-padding-bottom: 8px;
}
.hm-lnb-story [data-slot="lnb"] {
min-width: 212px;
}
/* PC column width 240px through the existing --hm-lnb-width (the column, the Story grid track and the menu row offset all read it).
Only from 1024px: the package sets --hm-lnb-width to 0px below that and collapses the column, which must keep working. */
@media (min-width: 1024px) {
.hm-lnb-story {
--hm-lnb-width: 240px;
}
}
/* Profile text (PC column): name (strong) 14px, description (p) 12px with no top margin, both 700. The package's existing 1px text
stroke on strong/p stays as is; the color and the 1.5 line-height ratio are unchanged (21px + 18px lines in the 48px profile).
@@ -91,23 +80,3 @@
paint-order: normal;
}
/* depth1: nav items and top-level group triggers. The package hard-codes 14px vertical padding (28px line + 28px = 56px). */
.hm-lnb-story [data-slot="lnb-item"],
.hm-lnb-story [data-slot="lnb-group-trigger"] {
padding-top: 10px;
padding-bottom: 10px;
}
/* depth2: links directly inside a group and nested group triggers (28px line + 14px = 42px). */
.hm-lnb-story [data-slot="lnb-group-content"] > [data-slot="lnb-item"],
.hm-lnb-story [data-slot="lnb-group-content"] [data-slot="lnb-group-trigger"] {
min-height: 42px;
padding-top: 7px;
padding-bottom: 7px;
}
/* depth3: links in a nested group (24px line + 18px = 42px). --hm-lnb-third-min-height is also the Profile's height, so it is
overridden on the row's link instead of through the variable. */
.hm-lnb-story [data-slot="lnb-group-content"] [data-slot="lnb-third-row"] > [data-slot="lnb-item"] {
min-height: 42px;
padding-top: 9px;
padding-bottom: 9px;
}
+5 -4
View File
@@ -34,9 +34,10 @@ import { StateBoard } from "@/components/story-board"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
import "./lnb.stories.css"
// LNB is a composition pattern. Layout visual reference is the ui-viz LNB (the shared package default: 272px green column, 18/700 items,
// Beige current item joined to the content, Black 10% hover, Black 8% open group). This Story overrides only the display values (240px column,
// 212px min width, 14px items, 48/42px rows) in lnb.stories.css; the package defaults are unchanged. The menu data and the current location are local sample data for this Story
// LNB is a composition pattern. Layout visual reference is the ui-viz LNB (the shared package default: 240px green column, 14/700 items,
// 48px 1-depth and 42px 2/3-depth rows, Beige current item joined to the content, Black 10% hover, Black 8% open group). The sizes come from the package
// (`--hm-lnb-*` in packages/ui/src/theme/lnb.css); lnb.stories.css only keeps Story presentation (212px min width, brand bottom padding, profile text,
// divider lines). The menu data and the current location are local sample data for this Story
// (no router). The content next to the column is a positioned surface with `--hm-lnb-join` rounded left corners: the LNB Mode page
// owns it, here it only shows the connection. The column is desktop only (hidden below 1024px).
@@ -619,7 +620,7 @@ const mobileViewport = {
globals: { viewport: { value: "hmMobile", isRotated: false } },
}
// `hm-lnb-story` (display: contents, see lnb.stories.css) scopes the 212px min width and 14px menu font to this Story only.
// `hm-lnb-story` (display: contents, see lnb.stories.css) scopes the 212px min width, brand bottom padding, profile text and divider lines to this Story only.
const meta = { title: "Patterns/Layout/LNB", component: LNB, decorators: [(Story) => <div className="hm-lnb-story"><Story /></div>], parameters: { layout: "fullscreen", controls: { disable: true } } } satisfies Meta<typeof LNB>
export default meta
type Story = StoryObj<typeof meta>
+9 -5
View File
@@ -588,7 +588,7 @@
"--hm-label-line-height": "20px",
"--hm-label-font-weight": "500",
"--hm-label-text": "var(--hm-sem-fg-secondary)",
"--hm-lnb-width": "272px",
"--hm-lnb-width": "240px",
"--hm-lnb-join": "18px",
"--hm-lnb-background": "linear-gradient(90deg, var(--hm-green-default) 0%, var(--hm-green-light-1) 30%, var(--hm-green-light-2) 50%, var(--hm-green-light-3) 90%, var(--hm-green-light-2) 100%)",
"--hm-lnb-background-end": "var(--hm-green-light-2)",
@@ -608,10 +608,11 @@
"--hm-lnb-brand-eyebrow-spacing": "0.12em",
"--hm-lnb-brand-name-size": "17px",
"--hm-lnb-brand-text-gap": "5px",
"--hm-lnb-item-min-height": "56px",
"--hm-lnb-item-min-height": "48px",
"--hm-lnb-item-padding-block": "10px",
"--hm-lnb-item-padding-start": "18px",
"--hm-lnb-item-gap": "12px",
"--hm-lnb-item-font-size": "18px",
"--hm-lnb-item-font-size": "14px",
"--hm-lnb-item-line-height": "28px",
"--hm-lnb-item-font-weight": "700",
"--hm-lnb-item-radius": "4px 0 0 4px",
@@ -631,9 +632,11 @@
"--hm-lnb-group-current-text": "var(--hm-yellow-accent)",
"--hm-lnb-group-arrow-size": "18px",
"--hm-lnb-subitem-padding-start": "68px",
"--hm-lnb-subitem-min-height": "42px",
"--hm-lnb-subitem-padding-block": "7px",
"--hm-lnb-group-padding-bottom": "8px",
"--hm-lnb-third-min-height": "48px",
"--hm-lnb-third-padding-block": "12px",
"--hm-lnb-third-min-height": "42px",
"--hm-lnb-third-padding-block": "9px",
"--hm-lnb-third-padding-start": "88px",
"--hm-lnb-third-line-height": "24px",
"--hm-lnb-third-font-weight": "500",
@@ -646,6 +649,7 @@
"--hm-lnb-profile-padding-x": "30px",
"--hm-lnb-profile-padding-top": "22px",
"--hm-lnb-profile-gap": "12px",
"--hm-lnb-profile-min-height": "48px",
"--hm-lnb-profile-name-size": "16px",
"--hm-lnb-profile-description-size": "12px",
"--hm-loading-screen-background": "var(--hm-green-default)",