Merge pull request 'feat(ui): standardize LNB sizing and active depth effect' (#25) from feat/storybook-ui-refinement into main
Deploy Storybook / deploy (push) Successful in 1m15s
Deploy Storybook / deploy (push) Successful in 1m15s
Reviewed-on: #25
This commit was merged in pull request #25.
This commit is contained in:
commit
fae221b192
9 files changed
+95
-75
No files matched your search
@@ -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 |
|
||||
|
||||
@@ -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
@@ -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. 관련 파일
|
||||
|
||||
@@ -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 골격
|
||||
|
||||
@@ -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. 상태
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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)",
|
||||
|
||||
Reference in new issue
Block a user