Files

94 lines
6.5 KiB
Markdown

# Context Menu
현재 HM 구현의 기준 문서. 공통 원칙은 `docs/guidelines/프로젝트-지침.md`(§6, §7), Token 구조 원칙은 `테마-토큰-가이드.md`를 따른다. 이 문서는 Context Menu 고유의 API·Token·상태·결정만 기록한다. Dropdown Menu와 같은 메뉴 계열이므로 상세 값은 `dropdown-menu.md`와 대응한다.
## 1. 상태
| 항목 | 내용 |
|---|---|
| 구현 상태 | 구현 완료 (현재 HM 규격) |
| 기준일 | 2026-10-03 |
| 기반 | shadcn/ui Context Menu (Radix `ContextMenu`, `radix-ui` 1.6.7 / `@radix-ui/react-context-menu` 2.3.7) |
| 대표 Story | `Components/Overlay/Context Menu` → `All` (이름 "Context Menu") |
| 보류 | 아래 §11 |
## 2. 적용 근거
- **참고 근거**: ui-viz에는 Context Menu 규격이 없다. 최종 기준은 현재 `@hm-design/ui` 구현과 Dropdown Menu 문서다.
- **현재 HM 규격**: 면·윤곽·radius·그림자는 Floating Surface Semantic(`--hm-sem-floating-*`)을 source로 쓰고, item·text 값은 Dropdown Menu와 같은 현재 HM 값이다.
- **구현 방식**: shadcn / Radix Context Menu 구조 유지. 상태는 Radix 속성(`data-highlighted`, `data-disabled`, `data-state`)만 쓴다.
## 3. Dropdown Menu와 역할 차이
| | Dropdown Menu | Context Menu |
|---|---|---|
| 여는 방법 | 명시적 버튼(Trigger) 클릭 / Enter / Space / 방향키 | 대상 영역 우클릭, context-menu key / Shift+F10, 터치 long press |
| 위치 | Trigger 기준 anchored (`sideOffset` 4) | 포인터 위치 기준 (`sideOffset` 없음) |
| 용도 | 버튼이 가진 Action 목록 | 대상 객체에 대한 보조 Action |
| 닫힐 때 focus | Trigger로 복귀 | Trigger가 포커스 가능한 요소일 때만 복귀(§7) |
브라우저 native context menu 대신 HM Context Menu가 열린다(Radix가 `contextmenu` 이벤트를 막음).
## 4. 기본 규격 / 상태 규격
`dropdown-menu.md` §3·§4와 값이 같다(Surface White, 1px pale border, radius 4px, 12px glow 그림자, item 36px·14/20, Highlighted Accent 45%(`--hm-sem-surface-highlight`), Disabled opacity 50, Destructive `--hm-sem-action-destructive`, Check 16px / Radio 8px). 값이 같아도 `--hm-dropdown-menu-*`는 참조하지 않는다.
## 5. API
Radix Context Menu props 그대로. export: `ContextMenu`, `ContextMenuTrigger`, `ContextMenuContent`, `ContextMenuGroup`, `ContextMenuLabel`, `ContextMenuItem`, `ContextMenuCheckboxItem`, `ContextMenuRadioGroup`, `ContextMenuRadioItem`, `ContextMenuSeparator`, `ContextMenuShortcut`, `ContextMenuSub`, `ContextMenuSubTrigger`, `ContextMenuSubContent`, `ContextMenuPortal`.
- `ContextMenuItem`: shadcn `variant="default" | "destructive"`, `inset`. Label·CheckboxItem·RadioItem·SubTrigger: `inset`.
- 출력 속성: `data-slot="context-menu-*"`, Radix `data-state`·`data-highlighted`·`data-disabled`·`data-side`.
- HM 전용 prop 없음.
## 6. Token
Component Mapping 위치: `packages/ui/src/theme/context-menu.css` (`--hm-context-menu-*`).
- Surface 5개(`background`, `border-width`, `border-color`, `radius`, `shadow`)는 **Floating Surface Semantic을 source로 참조**한다: `--hm-sem-floating-background|border-width|border-color|radius|shadow`.
- 나머지(`text`, `padding`, `item-*`, `font-*`, `highlight-*`, `destructive-*`, `label-*`, `shortcut-*`, `separator-color`, `indicator-size`, `radio-dot-size`)는 Dropdown Menu와 같은 현재 HM 값을 자체 정의한다.
- 다른 컴포넌트의 Component Token을 쓰지 않는다. 신규 Primitive·Semantic 없음. 기존 Floating Semantic을 유지한다.
## 7. shadcn / Radix에서 유지한 항목
Radix 구조·ARIA(`role=menu`, `menuitem*`)·키보드(방향키, Home/End, Enter/Space, 오른쪽 방향키 sub menu, Escape, typeahead)·Portal·collision·`data-slot`·`cn(..., className)`·open/close animation class·`z-50`·lucide 아이콘.
**접근성 주의**: Radix `ContextMenuTrigger`는 기본적으로 포커스할 수 없는 `span`이다. 키보드 사용자가 여는 경로(Shift+F10 / context-menu key)를 제공하려면 소비자가 Trigger 영역에 `tabIndex={0}`을 주거나 `asChild`로 포커스 가능한 요소(버튼, 행 등)를 감싸야 한다. 우클릭만으로 접근 가능한 기능을 만들지 않는다. 대표 Story의 영역은 `tabIndex={0}`을 준다.
## 8. HM에서 변경한 항목
Dropdown Menu와 동일: shadcn 시각 class(`rounded`, `shadow`, `p-1`, `focus:bg-accent` 등)를 제거하고 theme CSS + `data-highlighted`로 대체. Radio는 CSS 크기 token을 쓰는 원(`context-menu-radio-dot`), Check / Radio indicator span에 `data-slot="context-menu-item-indicator"` 추가. Context Menu에는 `sideOffset`이 없다.
## 9. Storybook
- 대표 Story `All`("Context Menu"): 점선 영역을 우클릭하면 전체 메뉴가 열린다(Label, Item + Shortcut, disabled, Separator, Checkbox, Radio Group, Sub menu, destructive).
- verification: `Open`(로드 시 합성 `contextmenu` 이벤트로 열린 상태, fullscreen).
## 10. 사용자 확정 결정
- 2026-10-03: Context Menu는 Dropdown Menu와 같은 floating menu 계열이므로 Floating Surface Semantic을 Component Token의 source로 쓴다. Component Token은 유지하고 `--hm-dropdown-menu-*`는 참조하지 않는다(작업 지시).
## 11. 미확정 / 보류
- 수치는 Dropdown Menu와 같은 현재 HM 규격이며 디자인 확정 시 함께 재검토.
- Home/End·typeahead·long press(터치)는 확인하지 못했다.
- Shift+F10 / context-menu key는 자동화 입력이 브라우저의 native `contextmenu` 이벤트를 만들지 못해 확인하지 못했다(Radix는 이 이벤트로 여는 구조이며 우클릭과 같은 경로).
- 메뉴가 닫힌 뒤 Trigger가 포커스 불가면 focus는 body에 남는다(Radix 동작).
## 12. 관련 파일
```
packages/ui/src/components/ui/context-menu.tsx
packages/ui/src/theme/context-menu.css
packages/ui/src/theme.css (import)
packages/ui/src/index.ts (export)
src/components/context-menu.stories.tsx
```
## 13. 검증 상태
- `npm run typecheck`, `npm run build`: 통과(2026-10-03).
- 실제 interaction(Browser pane): 우클릭 시 native 메뉴 대신 HM 메뉴가 열림, 방향키 이동(disabled 항목 건너뜀), Enter로 Checkbox 토글(메뉴 유지), 방향키 아래 + 오른쪽으로 Sub menu 열림과 첫 항목 focus, Escape 두 번으로 sub → menu 순서 닫힘.
- computed style: Content·Sub Content 모두 white, `1px solid rgb(222,219,210)`, radius 4px, `0 0 12px .2` 그림자, item 36px, Dropdown Menu(`Open` Story) 값과 동일. role `menu`.
- Dropdown Menu의 같은 값이 변하지 않음을 확인(회귀 없음).