Files

110 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Navigation Menu
현재 HM 구현의 기준 문서. 공통 원칙은 `docs/guidelines/프로젝트-지침.md`(§6, §7), Token 구조 원칙은 `테마-토큰-가이드.md`를 따른다. 이 문서는 Navigation Menu 고유의 API·Token·상태·결정만 기록한다. Dropdown Menu(`dropdown-menu.md`), Menubar(`menubar.md`)와 값을 공유하지 않는다.
## 1. 상태
| 항목 | 내용 |
|---|---|
| 구현 상태 | 구현 완료 (현재 HM 규격) |
| 기준일 | 2026-10-02 |
| 기반 | shadcn/ui Navigation Menu (Radix `NavigationMenu`) |
| 대표 Story | `Components/Navigation/Navigation Menu` → `All` (이름 "Navigation Menu") |
| 보류 | 아래 §11 |
## 2. 적용 근거
- **참고 근거**: 외부 ui_viz Reference (ZIP 미포함)의 기존 테스트 규격에는 Navigation Menu 규격이 없다. 현재 위치(Current) 의미와 Hover/Focus는 독립이라는 ui-viz의 navigation 안내만 참고했다.
- **현재 HM 규격**: 아래 §3~§4. 현재 HM Semantic 체계로 새로 정했다. Dropdown Menu의 운영 원칙(Radix 상태 속성, Escape, focus 복귀, Portal/viewport)만 참고하고 token은 참조하지 않았다.
- **구현 방식**: shadcn / Radix NavigationMenu 구조(Root, List, Item, Trigger, Content, Link, Viewport, Indicator) 유지, theme CSS로 HM 스타일 연결.
- **역할 구분**: 목적지 이동(링크) 중심이다. Dropdown Menu(명령)·Menubar(메뉴 바)와 접근성 모델이 다르다(`role=menu`가 아니라 disclosure + link).
- **보류**: §11.
## 3. 기본 규격 (현재 HM 규격)
| 항목 | 값 |
|---|---|
| 상위 Trigger / Link | 높이 34px, padding `0 12px`, radius 4px, 14 / 20 / 500, `--hm-sem-fg-primary`, 항목 gap 4px |
| Trigger indicator | shadcn `ChevronDownIcon` 12px, open 시 180° 회전 |
| Content / Viewport 면 | `--hm-sem-bg-surface`, 1px `--hm-sem-floating-border-color`, radius 4px, shadow `0 0 12px rgb(0 0 0 / .2)` |
| Content | padding 8px, min-width 240px, 폭은 안의 내용이 정함 |
| Content 안 Link(목적지) | 세로 배치, padding `8px 12px`, gap 2px, radius 2px. `data-description` 자식은 12 / 18 / 400 secondary |
| Indicator(화살표) | shadcn 회전 사각형, `--hm-sem-floating-border-color` |
| 열기 | hover(Radix 기본 delay)·click·keyboard. 이동 animation은 shadcn 기본 |
## 4. 상태 규격
| 상태 | 값 |
|---|---|
| Hover / Focus / Open (Trigger) | 배경 `--accent`, 글자 `--accent-foreground`. Focus는 ring 모양 `0 0 0 3px ring 50%` 추가 |
| Link Hover / Focus | Trigger와 같은 면 |
| Active Link (`active`) | Radix `data-active` + `aria-current="page"`. 글자 `--hm-sem-action-primary`, weight 700, 배경 `--accent` 45% (hover면 일반 hover 면) |
| Disabled Trigger | `opacity: 0.6`(이 컴포넌트 값, 전역 Disabled 규칙으로 승격 안 함), `cursor: not-allowed`, native `disabled` |
| Open / Closed | Radix `data-state`, 이동 방향은 `data-motion` (shadcn animation) |
## 5. API
Radix NavigationMenu props 그대로. export: `NavigationMenu`, `NavigationMenuList`, `NavigationMenuItem`, `NavigationMenuTrigger`, `NavigationMenuContent`, `NavigationMenuLink`, `NavigationMenuIndicator`, `NavigationMenuViewport`.
- `NavigationMenu`: `viewport`(기본 true, shadcn). `false`면 각 Content가 자기 Trigger 아래에 열린다. 출력 `data-viewport`.
- `NavigationMenuLink`: `active`(현재 위치), `asChild`로 router Link 조합(router에 의존하지 않음). disabled link는 지원하지 않는다.
- `NavigationMenuTrigger`: chevron 내장. `disabled`는 native 속성.
- `NavigationMenuIndicator`: 선택 사용(Story의 viewport 예시에서 사용).
- 2026-10-06 Visual QA: viewport 모드의 Item은 static으로 두어 Radix List wrapper가 Trigger의 `offsetParent`가 되도록 한다. `viewport={false}`에서만 Item의 relative anchor를 유지한다. Indicator에 임의 위치 보정값을 넣지 않는다. `IndicatorAlignment` verification Story는 서로 다른 label width의 첫·중간·마지막 Trigger와 두 viewport 모드를 검증한다.
- 대표 Story의 고정 360/300px 내용은 좁은 화면에서 자연 폭을 사용한다. Desktop 폭도 Story frame container에 제한하며, 모바일 inline Content의 absolute 배치는 Story 범위에서만 적용한다(Production CSS 변경 없음).
- 새 prop은 만들지 않았다. Content 안 title/description은 소비 조합이며 `data-description` 속성으로 보조 문구를 표시한다.
## 6. Token
Component Mapping 위치: `packages/ui/src/theme/navigation-menu.css` (`--hm-navigation-menu-*`).
`height`, `padding-x`, `gap`, `radius`, `font-size|line-height|font-weight`, `text`, `icon-size`, `hover-background|text`, `active-background|text|font-weight`, `background`, `border-width|color`, `surface-radius`, `shadow`, `content-padding`, `content-min-width`, `item-padding|gap|radius`, `description-*`, `disabled-opacity`. Semantic과 Theme palette(`--hm-sem-floating-border-color`)만 참조한다. `--hm-dropdown-menu-*`, `--hm-tab-*`, `--hm-button-*`는 참조하지 않는다. 신규 Primitive 없음.
## 7. shadcn / Radix에서 유지한 항목
Radix 구조·동작(hover open delay, Arrow 키와 Tab 이동, Escape 닫힘, focus 복귀, Viewport 크기 측정, Indicator 위치), ARIA(`aria-expanded`, `aria-controls`, `aria-current`), 이동 animation(`data-motion`), viewport/non-viewport 구조, chevron, `data-slot`, `cn(..., className)`.
## 8. HM에서 변경한 항목
- shadcn `navigationMenuTriggerStyle`(rounded-md, h-9, bg-background, hover/focus/open `bg-accent/50`)과 Content·Viewport의 `bg-popover text-popover-foreground rounded-md border shadow` 등을 제거하고 theme CSS로 이전.
- Content 안 Link는 shadcn에서 별도 class로 꾸미던 것을 선택자(`content` 하위 `link`)로 처리.
- Active Link 스타일(색·weight·배경)을 정의(shadcn은 `data-[active=true]:bg-accent/50`).
## 9. Storybook
- 대표 Story `All`("Navigation Menu"): `Viewport(기본)`과 `viewport={false}` 두 구성을 한 페이지에 둔다. 제품(설명 있는 3 링크), 자료(2열 4 링크), 회사(현재 위치 링크), 문의(일반 링크), 준비 중(disabled Trigger).
- verification: `Default`, `No Viewport`.
- Story의 Content 폭은 `style` 인라인으로 지정한다(새 Story 파일의 Tailwind 임의값 클래스가 개발 서버에서 생성되지 않는 일이 있었다).
## 10. 사용자 확정 결정
- 2026-10-02: ui-viz는 참고 자료이며 최종 기준은 현재 HM 구현과 이 문서다(작업 지시). 개별 수치에 대한 별도 결정은 없다.
## 11. 미확정 / 보류
- 수치(34px, 12px padding, Content 240px 최소 폭, 그림자, Active 표현)는 현재 HM 규격이며 디자인 확정 시 재검토.
- **모바일**: Radix NavigationMenu는 데스크톱 GNB 구조다. 390px에서 Viewport의 폭은 Content 폭(Story에서 360px)을 따라가며 `max-width` 제한이 없어 우측 끝에 닿았다(가로 overflow는 없음). 모바일 navigation(햄버거, Sheet/Drawer 조합 등)은 GNB/Layout 단계에서 별도 패턴으로 검토한다.
- disabled link 지원 없음. 필요하면 `aria-disabled` 규칙을 Pagination과 같은 방식으로 후속 검토.
- Indicator 사용 여부와 색은 현재 선택 사항이며 별도 규격이 없다.
- 열림/닫힘 Motion은 shadcn 기본.
## 12. 관련 파일
```
packages/ui/src/components/ui/navigation-menu.tsx
packages/ui/src/theme/navigation-menu.css
packages/ui/src/theme.css (import)
packages/ui/src/index.ts (export)
src/components/navigation-menu.stories.tsx
```
## 13. 검증 상태
- 2026-10-06 Visual QA: 1280/1024/768/390px에서 제품 Trigger/Arrow center 48.8828/49px, 자료 118.6484/119px. 다른 label width(제품/기술 자료/지원 센터)도 Arrow 오차 절댓값 0.45px 이내. inline 모드의 각 Content가 자기 Trigger 아래에 열림 확인. 대표 Story 두 모드의 열린 패널이 viewport 안에 있고 페이지 가로 overflow 없음. `typecheck`, `build`, `build-storybook` 통과.
- `npm run typecheck`, `npm run build`: 통과(2026-10-02).
- 실제 interaction(Browser pane): 키보드로 Enter 열림(`aria-expanded=true`), Tab으로 Content 안 첫 링크, 다음 링크로 이동, Escape로 닫히고 Trigger로 focus 복귀, ArrowRight로 다음 상위 항목으로 이동, 마우스 hover로 해당 Content가 열림, 현재 위치 링크가 `aria-current="page"`·`data-active`·weight 700.
- computed style: Viewport 흰 면, border `rgb(222,219,210)`, radius 4px, shadow `0 0 12px .2`. Content 폭이 안의 목록(360px) 폭에 맞춰 376px.
- 390×844: 열린 Viewport 폭 378px, 좌측 16px에서 시작해 오른쪽 끝에 닿음(가로 overflow 없음). 한계는 §11.
- 미확인: 닫힘 animation 시각, `viewport={false}` 구성의 열림(코드 경로는 Radix/shadcn 기본), 두 Content 사이 이동 animation 시각, ArrowDown으로 Content 진입.