9.8 KiB
Menubar
2026-10-10 추가 검증: 파일 메뉴 항목에서 ArrowRight를 누르면 편집 Trigger만 Focus되고 다음 popup이 닫히는 문제를 재현했다. 이전 popup의 exit animation 동안 남은 DismissableLayer가 다음 popup Focus를 외부로 처리했다. Main Content의 닫힘 애니메이션만 제거해 즉시 unmount하며 열림 애니메이션·Radix 키보드/API·Sub menu는 유지한다. 현재 결과는 추가 수정 보고서를 따른다.
현재 HM 구현의 기준 문서. 공통 원칙은 docs/guidelines/프로젝트-지침.md(§6, §7), Token 구조 원칙은 테마-토큰-가이드.md를 따른다. 이 문서는 Menubar 고유의 API·Token·상태·결정만 기록한다. Dropdown Menu(dropdown-menu.md), Navigation Menu(navigation-menu.md)와 값을 공유하지 않는다.
1. 상태
| 항목 | 내용 |
|---|---|
| 구현 상태 | 구현 완료 (현재 HM 규격) |
| 기준일 | 2026-10-06 |
| 기반 | shadcn/ui Menubar (Radix Menubar) |
| 대표 Story | Components/Navigation/Menubar → All (이름 "Menubar") |
| 보류 | 아래 §11 |
2. 적용 근거
- 참고 근거: 외부 ui_viz Reference (ZIP 미포함)의 기존 테스트 규격에는 Menubar 규격이 없다. 참고할 수치가 없었다.
- 현재 HM 규격: 아래 §3~§4. 현재 HM Semantic 체계로 새로 정했다. Dropdown Menu에서 확인한 Radix menu 운영 원칙(Radix 상태 속성, highlighted/disabled, submenu, Escape, focus 복귀, Portal/collision, shadcn 아이콘 유지)만 참고하고 token은 참조하지 않았다.
- 구현 방식: shadcn / Radix Menubar 구조 유지, theme CSS로 HM 스타일 연결. 상태는 Radix 속성(
data-highlighted,data-disabled,data-state)만 쓴다. - 역할 구분: 데스크톱 menu bar(좌우 키로 상위 메뉴 이동)다. Navigation Menu(목적지 링크)와 접근성 모델이 다르고(
role=menubar/menu), Dropdown Menu(단일 trigger)와도 다르다. - 보류: §11.
3. 기본 규격 (현재 HM 규격)
| 항목 | 값 |
|---|---|
| Bar | 폭 내용 기준(fit-content), 높이 38px, padding 2px, gap 2px, 1px --hm-sem-stroke-input-default border, radius 4px, 흰 면 |
| 상위 Trigger | padding 0 12px, radius 2px, 14 / 20 / 500 |
| Content / SubContent | 흰 면, 1px --hm-sem-stroke-input-default, radius 4px, shadow 0 0 12px rgb(0 0 0 / .2), padding 4px |
| Item | min-height 36px, padding-x 12px, gap 8px, radius 2px, 14 / 20 / 400 |
| Label | 12 / 18 / 500, --hm-sem-fg-secondary |
| Shortcut | 12px, --hm-sem-fg-secondary |
| Separator | 1px --hm-sem-stroke-input-default, Content 폭 전체 |
| Check / Radio | 16px indicator 열, shadcn CheckIcon, 8px 채운 원 |
| Sub menu chevron | shadcn ChevronRightIcon |
| 위치 | Content sideOffset 8, alignOffset -4, align start (shadcn 기본) |
4. 상태 규격
| 상태 | 값 |
|---|---|
| 상위 Trigger Highlighted / Open | 배경 Accent 100%(--hm-menubar-trigger-highlight-background → --accent), 글자 --accent-foreground |
상위 Trigger Focus (:focus-visible) |
기존 강조 면 위에 독립 3px --ring 50% box-shadow ring (--ring → --hm-sem-stroke-input-focus) |
| 상위 Trigger Open + Focus | Accent 100% 면 + Focus ring 동시 표시. Focus가 Open 면을 대체하지 않음 |
Item Highlighted (data-highlighted) |
배경 Accent 45%(--hm-sem-surface-highlight), 글자 --accent-foreground (포인터 hover와 키보드 이동 공통) |
Disabled (data-disabled) |
Item은 shadcn opacity-50, 상위 Trigger는 opacity: 0.6, 키보드 이동에서 제외 |
| Checked / Selected | ItemIndicator가 check 또는 원을 표시 (Radix data-state) |
| Sub menu open | SubTrigger data-state="open"이 Highlighted와 같은 면 유지 |
Destructive (variant="destructive") |
글자 --hm-sem-action-destructive, Highlighted 면 --hm-color-action-destructive-hover |
5. API
Radix Menubar props 그대로. export: Menubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem, MenubarCheckboxItem, MenubarRadioGroup, MenubarRadioItem, MenubarLabel, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubTrigger, MenubarSubContent, MenubarGroup, MenubarPortal.
MenubarItem: shadcnvariant="default" | "destructive"(출력data-variant),inset. Label·CheckboxItem·RadioItem·SubTrigger도inset.- 새 prop은 만들지 않았다.
6. Token
Component Mapping 위치: packages/ui/src/theme/menubar.css (--hm-menubar-*).
Bar: background, text, border-width|color, radius, height, padding, gap. Trigger: trigger-*. Content: content-background|border-color|radius|shadow|padding. Item: item-height|padding-x|gap|radius, font-size, line-height, highlight-*, destructive-*, label-*, shortcut-*, separator-color, indicator-size, radio-dot-size. Semantic과 Theme palette(--hm-sem-stroke-input-default)만 참조한다. --hm-dropdown-menu-*는 참조하지 않는다(값이 같아 보이는 부분은 후속 token 정리 후보). 신규 Primitive 없음.
7. shadcn / Radix에서 유지한 항목
Radix 구조·ARIA(role=menubar, menu, menuitem*)·키보드 모델(Down/Enter/Space로 열기, Up/Down, Home/End, Left/Right, sub menu, typeahead, Escape)·focus 이동/복귀, Portal·positioning·collision, data-slot, cn(..., className), open/close animation, lucide 아이콘.
8. HM에서 변경한 항목
- shadcn의
rounded-lg border bg-background h-9 p-1 shadow-xs, Triggerrounded-sm px-2 py-1 text-sm font-medium focus:bg-accent, Content·Item의 색/radius/padding class를 제거하고 theme CSS +data-highlighted로 대체. - Radio 표시를 별도 아이콘 import 대신 CSS 크기 token을 쓰는 원으로 구현(Dropdown Menu와 같은 방식).
- Check / Radio indicator span에
data-slot="menubar-item-indicator"추가. - Bar 폭을
fit-content로 지정(shadcn의flex는 가로 전체로 늘어난다).
9. Storybook
- 대표 Story
All("Menubar"): 파일(item+shortcut, disabled, separator, sub menu 내보내기, destructive 종료), 편집, 보기(Label, Checkbox, Radio Group)를 가진 실제 bar. - 숨김 verification:
TriggerFocus,OpenFocus.OpenFocus는 검수 목적으로 Story의play에서 실제 열린 menu의 Trigger에 focus를 준다. 일반 Story/Production은 Radix 기본대로 메뉴 항목에 focus를 이동한다. - Focus 검수는 일반 Story의 computed style 기준으로 한다. Designer의
.hm-designer button:focus-visible장식 outline을 컴포넌트 ring의 근거로 사용하지 않는다.
10. 사용자 확정 결정
- 2026-10-02: ui-viz는 참고 자료이며 최종 기준은 현재 HM 구현과 이 문서다(작업 지시). 개별 수치에 대한 별도 결정은 없다.
11. 미확정 / 보류
- 수치(38px bar, 12px padding, 36px item, 그림자)는 현재 HM 규격이며 디자인 확정 시 재검토. Dropdown Menu와 값이 같은 부분은 공통 Semantic 후보이며 이번에 승격·리팩터링하지 않았다.
- 모바일: 데스크톱 중심 컴포넌트다. 390px에서의 구성(상위 메뉴가 넘칠 때)은 검증하지 않았고, 모바일 menu 패턴은 별도로 정한다.
- Disabled opacity: Item은 shadcn 50, 상위 Trigger는 0.6으로 서로 다르다(전역 Disabled 규칙 없음).
- Motion은 shadcn 기본.
- Menu 안 항목을 링크로 쓰는 예는 정하지 않았다(링크 목적지는 Navigation Menu).
12. 관련 파일
packages/ui/src/components/ui/menubar.tsx
packages/ui/src/theme/menubar.css
packages/ui/src/theme.css (import)
packages/ui/src/index.ts (export)
src/components/menubar.stories.tsx
13. 검증 상태
- 2026-10-06 Focus 정합:
typecheck,build,build-storybook,git diff --check통과. 기존 문서 예시 CSS(bg-[var(...)])와 Storybook chunk 크기 경고 유지. - Theme Designer에서 Primary
#6837a8, Active#007f73, Focus#c75b00, Accent#ffd36d를 분리 적용한 뒤 일반 Story에서 확인: Open + Focus Trigger 면rgb(255,211,109)+ Focus 50% / 3px ring. 실제:focus-visible,data-state="open"동시 성립, Designer wrapper 없음, computed outlinenone으로 장식 CSS 영향 배제. - 단독 override: Focus는 Trigger ring만, Accent는 Top Trigger 면과 Item Highlighted(45%)만 변경. Primary/Active는 Menubar 면·ring에 영향 없음. Disabled Trigger shadow 없음·opacity 0.6, Disabled Item opacity 0.5 유지.
- 일반 키보드 Enter는 실제 첫 Item에 focus를 이동하며 Trigger는 Accent 100% Open 면 / ring 없음, Item은 Accent 45% Highlighted를 유지한다. Escape는 닫힌 Trigger로 focus를 복귀시키며 독립 ring을 표시한다. Navigation Menu 일반 Story에서도 Accent 면 + 3px Focus ring·좌우 키 이동을 확인했고 소스는 변경하지 않았다.
npm run typecheck,npm run build: 통과(2026-10-02).- 실제 키보드(Browser pane): 상위 Trigger에서 Enter로 열기(첫 항목에 focus), ArrowDown/Up으로 이동(disabled 항목 건너뜀), Home/End, 오른쪽 방향키로 Sub menu 열림과 왼쪽 방향키로 Sub menu 닫힘(상위 menu 유지), Sub menu 안에서 오른쪽 방향키로 다음 상위 메뉴 Trigger로 이동, 상위 Trigger에서 오른쪽 방향키로 다음 Trigger 이동, ArrowDown으로 보기 menu 열기, Enter로 Checkbox 토글(menu가 닫히고 Trigger로 focus 복귀, 재오픈 시
눈금자checked), Escape로 닫힘과 Trigger focus 유지. - computed style: Bar 38px / radius 4px / border
rgb(222,219,210)/ 흰 면, Trigger 14/20/500, Content radius 4px, shadow0 0 12px .2, padding 4px, Item 36px, Highlightedrgb(189,223,212). - 미확인: Radio 선택 키보드 동작(Radix 동일 구조, Checkbox만 확인), 실제 pointer hover, typeahead, 좁은 viewport.