Files
HMDesign/docs/components/command.md
T
2026-10-28 14:03:46 +09:00

107 lines
7.3 KiB
Markdown

# Command
현재 HM 구현의 기준 문서. 공통 원칙은 `docs/guidelines/프로젝트-지침.md`(§6, §7), Token 구조 원칙은 `테마-토큰-가이드.md`를 따른다. 이 문서는 Command 고유의 API·Token·상태·결정만 기록한다.
## 1. 상태와 성격
| 항목 | 내용 |
|---|---|
| 성격 | **Primitive component** (검색 가능한 command/list). Combobox 자체가 아니다 |
| 구현 상태 | 구현 완료 (현재 HM 규격) |
| 기준일 | 2026-10-02 |
| 기반 | shadcn/ui Command = **cmdk** |
| 대표 Story | `Components/Search & Date/Command` → `All` (이름 "Command") |
| 보류 | 아래 §11 |
## 2. 적용 근거
- **참고 근거**: 외부 ui_viz Reference (ZIP 미포함)의 기존 테스트 규격에는 Command가 없다(Search Field의 후보 목록은 다른 컴포넌트). 참고할 수치가 없었다.
- **현재 HM 규격**: 아래 §3~§4. 현재 HM Semantic/Primitive로 새로 정했다. Input·Popover·Dropdown Menu Token은 참조하지 않는다(Command Input은 Command 전용 Token).
- **구현 방식**: cmdk가 검색 필터링, 방향키/Home/End 이동, Enter 선택, disabled 건너뜀, 그룹, Empty, listbox/option ARIA를 소유한다. HM은 cmdk의 속성(`data-selected`, `data-disabled`, `[cmdk-group-heading]`)에 시각을 연결한다.
- **범위**: 전역 Command Palette(`CommandDialog`)는 만들지 않았다(별도 고급 패턴).
- **보류**: §11.
## 3. 기본 규격 (현재 HM 규격)
| 항목 | 값 |
|---|---|
| Command | 흰 면(`--hm-sem-bg-surface`), 글자 `--hm-sem-fg-primary`, radius 4px. **border·shadow 없음**(Popover 등 컨테이너 안에서 쓰는 것을 전제) |
| Input | 높이 40px, padding-x 12px, 14 / 20, 아래 1px `--hm-sem-stroke-input-default` 구분선, 검색 아이콘 16px `--hm-sem-fg-secondary`, placeholder `--hm-sem-fg-placeholder` |
| List | 최대 높이 280px(넘치면 세로 scroll), padding 4px |
| Item | 최소 높이 36px, padding-x 12px, gap 8px, radius 2px, 14 / 20, 아이콘 16px |
| Group heading | 12 / 18 / 500, `--hm-sem-fg-secondary`, padding `6px 12px` |
| Separator | 1px `--hm-sem-stroke-input-default`, List 폭 전체 |
| Shortcut | 12px, `--hm-sem-fg-secondary`, letter-spacing .05em |
| Empty | padding 24px 12px, 가운데, `--hm-sem-fg-secondary` |
## 4. 상태 규격
| 상태 | 값 |
|---|---|
| Highlighted (`data-selected="true"`) | 배경 Accent 45%(`--hm-sem-surface-highlight`), 글자 `--accent-foreground`. 포인터 hover와 키보드 이동이 공유 |
| Disabled (`data-disabled="true"`) | opacity 0.6(이 컴포넌트 값, 전역 Disabled 규칙으로 승격 안 함), cursor not-allowed, 방향키 이동에서 제외 |
| Empty | 필터 결과가 없을 때 `CommandEmpty` 표시 |
| Focus | Input에 있음(`outline-hidden`). 이동은 Highlighted 면으로 표시 |
## 5. API
cmdk props 그대로. export: `Command`, `CommandInput`, `CommandList`, `CommandEmpty`, `CommandGroup`, `CommandItem`, `CommandSeparator`, `CommandShortcut`.
- `Command`: `value`/`onValueChange`(highlight), `filter`(사용자 정의 필터), `shouldFilter`, `loop`, `label`.
- `CommandItem`: `value`, `keywords`, `disabled`, `onSelect`.
- `CommandInput`: Input + 검색 아이콘 wrapper(`data-slot="command-input-wrapper"`)를 포함한다.
- **필터링은 cmdk 기본(fuzzy) 그대로**이며 HM이 검색 알고리즘을 만들지 않았다.
- 접근성: `CommandInput`은 `role="combobox"`, `aria-expanded`, `aria-controls`(listbox), `aria-autocomplete="list"`; `CommandList`는 `role="listbox"`; `CommandItem`은 `role="option"`.
## 6. Dependency
- `cmdk` ^1.1.1 (`packages/ui`에 추가, shadcn Command의 기반). CENTER 패키지에는 추가하지 않았다.
## 7. Token
Component Mapping 위치: `packages/ui/src/theme/command.css` (`--hm-command-*`).
`background`, `text`, `radius`, `input-*`(height, padding-x, gap, font, placeholder, icon), `divider-color`, `list-max-height|padding`, `item-*`(height, padding-x, gap, radius, font), `highlight-background|text`, `group-label-*`, `shortcut-*`, `empty-*`, `disabled-opacity`. Semantic과 Primitive(`--hm-warm-neutral-pale`)만 참조한다. 신규 Primitive와 Semantic 없음. 이 CSS는 cmdk 내부 속성에 맞춘 일반 CSS이며 `@layer`를 쓰지 않는다(`className`으로 Command의 높이 등을 바꾸는 것은 TSX class 영역).
## 8. cmdk에서 유지한 항목
필터링과 정렬, 키보드(ArrowUp/Down, Home/End, Enter, disabled 건너뜀), 그룹 숨김(결과 없는 그룹), Empty, `data-selected`/`data-disabled`, listbox/option ARIA, 마우스 hover가 highlight를 옮기는 동작.
## 9. Storybook
- 대표 Story `All`("Command"): Search + disabled item(선택 로그는 숨김 `Search`에만 표시), Groups·icons·shortcut(`CalendarIcon`, `UserIcon`, `SettingsIcon`), Long list(30개, List가 scroll)를 한 페이지에 둔다. 테두리 박스는 Story 조합이다.
- verification: `Search`, `Grouped`.
- 2026-10-28: Search 예제는 Next.js(`next`), SvelteKit(`sveltekit`), Remix(`remix`, 비활성), Astro(`astro`) 순서로 표시한다. Combobox 예제와 표시명·내부 값을 맞추며, 표시명 검색도 유지한다. §13의 과일 항목 검증은 변경 전 기록으로 보존한다.
## 10. 사용자 확정 결정
- 2026-10-02: ui-viz는 참고 자료이며 최종 기준은 현재 HM 구현과 이 문서다(작업 지시). 개별 수치에 대한 별도 결정은 없다.
## 11. 미확정 / 보류
- 수치(40px Input, 36px Item, 280px List 최대 높이)는 현재 HM 규격이며 디자인 확정 시 재검토.
- Command Palette(`CommandDialog`, 전역 단축키 호출)는 별도 패턴으로 남겼다.
- 로딩 상태(`Command.Loading`), 중첩 페이지는 스타일링하지 않았다.
- 선택됨(value) 표시는 Command에 없다(highlight와 다르다). Combobox가 check 아이콘으로 따로 표시한다.
- Command의 Escape는 Command 자체에서는 하는 일이 없다(Popover/Dialog 안에서는 컨테이너가 닫는다).
## 12. 관련 파일
```
packages/ui/src/components/ui/command.tsx
packages/ui/src/theme/command.css
packages/ui/package.json (dependency: cmdk)
packages/ui/src/theme.css (import)
packages/ui/src/index.ts (export)
src/components/command.stories.tsx
```
## 13. 검증 상태
- `npm run typecheck`, `npm run build`: 통과(2026-10-02).
- ARIA(Browser pane): Input `role=combobox`·`aria-expanded=true`·`aria-controls`·`aria-autocomplete=list`, List `role=listbox`, Item `role=option`, disabled Item에 `data-disabled="true"`.
- keyboard: 첫 항목이 처음부터 highlight, ArrowDown으로 이동(비활성 항목 건너뜀: 바나나 → 포도), Enter로 선택(`onSelect` 로그 갱신), 그룹 간 이동(ArrowUp 환경설정 → 프로필), End로 마지막 활성 항목(비활성 `결제`는 제외).
- 검색: 한글 "바나" 입력 시 바나나만 표시, 결과 없는 "zzz"는 Empty 문구, 입력을 지우면 전체 4개로 복귀하고 첫 항목이 highlight. Escape는 Input focus를 유지(Command 단독에서는 동작 없음).
- computed style: Input 래퍼 40px, List 최대 280px·padding 4px, Item 36px·padding `0 12px`·14/20, Highlighted `rgb(189,223,212)`, Group heading 12px/500 `rgb(110,103,89)`, Shortcut 12px.
- 미확인: 긴 List의 키보드 scroll 시각, 모바일 폭(Combobox 안에서만 확인), pointer hover 시각, `CommandItem`의 `keywords` 외 사용자 정의 `filter`.