Files
HMDesign/docs/components/drawer.md
T

114 lines
6.8 KiB
Markdown
Raw 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.
# Drawer
현재 HM 구현의 기준 문서. 공통 원칙은 `docs/guidelines/프로젝트-지침.md`(§6, §7), Token 구조 원칙은 `테마-토큰-가이드.md`를 따른다. 이 문서는 Drawer 고유의 API·Token·상태·결정만 기록한다. Dialog·Sheet와 값을 공유하지 않는다.
## 1. 상태
| 항목 | 내용 |
|---|---|
| 구현 상태 | 구현 완료 (현재 HM 규격) |
| 기준일 | 2026-10-02 |
| 기반 | shadcn/ui Drawer = **vaul** (`vaul@1.1.2`, 이번에 `@hm-design/ui` dependency로 추가) + Radix Dialog |
| 대표 Story | `Components/Overlay/Drawer` → `All` (이름 "Drawer") |
| 보류 | 아래 §11 |
## 2. 적용 근거
- **참고 근거**: 외부 ui_viz Reference (ZIP 미포함)의 기존 테스트 규격에는 Drawer가 없다. 참고할 수치가 없었다.
- **현재 HM 규격**: 아래 §3~§4. Dialog의 접근성 원칙과 `asChild` 처리 규칙은 재사용하고 수치는 현재 HM Semantic으로 새로 정했다.
- **구현 방식**: shadcn Drawer 구조(vaul)를 유지하고 theme CSS로 HM 스타일 연결. drag·snap·dismiss는 vaul 기본이며 재구현하지 않았다.
- **vaul 추가 이유**: shadcn 공식 Drawer가 vaul 기반이다(Radix에 Drawer가 없음). 프로젝트에 없던 dependency라서 추가했다(`npm install vaul --workspace @hm-design/ui`).
- **Sheet와의 역할 구분**: Drawer = 손가락/포인터로 끌어 닫는 하단 중심 임시 panel(handle 포함), Sheet = 버튼으로 닫는 edge panel.
## 3. 기본 규격 (현재 HM 규격)
| 항목 | 값 |
|---|---|
| Surface / 전경 | `--hm-sem-bg-surface` / `--hm-sem-fg-primary` |
| Border | 1px `--hm-sem-stroke-input-default`, 화면을 향한 한 변 |
| Radius | 8px, 화면 안쪽 두 모서리(bottom이면 위 두 모서리) |
| Shadow | `0 0 12px rgb(0 0 0 / .2)` |
| bottom / top | 폭 100%, 높이 내용 기준, 최대 `85dvh` |
| left / right | 폭 `min(400px, 100vw − 48px)`, 높이 100% |
| Handle(bottom만) | 48 × 4px 알약, `--hm-sem-stroke-default`, 위 12px |
| Padding | 24px |
| Header | padding `24 24 16`, gap 8px |
| Title | 18 / 28 / 700 |
| Description | 14 / 20 / 400, `--hm-sem-fg-secondary` |
| Body | 14 / 20 / 400, 좌우 padding 24px, 유일한 scroll 영역 |
| Footer | padding `16 24 24`, gap 8px, 세로 쌓임(주 action이 위, shadcn 기본) |
| Close(X) | 없음(handle drag, Overlay 클릭, Escape, `DrawerClose`) |
| Overlay | shadcn 기본(`bg-black/10` + blur) |
## 4. 상태 규격
| 상태 | 값 |
|---|---|
| Closed / Open | vaul `data-state`, 열림 시 modal, 배경 aria-hidden, scroll lock |
| Drag | handle 또는 panel을 아래로 끌어 닫음(vaul 기본). 내부 scroll 영역이 있으면 vaul이 scroll과 drag를 구분 |
| Focus | 열릴 때 첫 tabbable로 이동, Tab 순환, 닫힐 때 trigger로 복귀 |
| Long Content | Header·Footer 고정, Body만 scroll |
## 5. API
vaul `Drawer.Root` props 그대로(`direction`, `snapPoints`, `dismissible`, `shouldScaleBackground`, `modal`, `open`/`onOpenChange` …). export: `Drawer`, `DrawerTrigger`, `DrawerPortal`, `DrawerOverlay`, `DrawerClose`, `DrawerContent`, `DrawerHeader`, `DrawerBody`, `DrawerFooter`, `DrawerTitle`, `DrawerDescription`.
- `Drawer`: **`autoFocus` 기본값을 true로 둔다**(vaul 기본은 false). vaul 기본값은 Radix의 초기 focus를 취소해, 열린 뒤에도 focus가 aria-hidden 처리된 trigger에 남고 Tab이 Drawer 밖으로 나간다(실측). `autoFocus={false}`로 vaul 동작을 되돌릴 수 있다.
- `DrawerContent`: 방향은 root의 `direction`(기본 bottom), `data-vaul-drawer-direction`으로 출력.
- `DrawerBody`(신규): scroll owner.
- `DrawerClose`: `asChild`일 때 자식의 `data-slot`을 유지(DialogClose와 같은 규칙).
- 새 prop은 만들지 않았다.
## 6. Token
Component Mapping 위치: `packages/ui/src/theme/drawer.css` (`--hm-drawer-*`).
`background`, `text`, `border-width`, `border-color`, `radius`, `shadow`, `max-height`, `width`, `edge-gap`, `padding`, `header-gap`, `footer-gap`, `handle-width|height|color|margin-top`, `title-*`, `description-*`, `body-*`. Semantic과 Theme palette만 참조하며 `--hm-sheet-*`·`--hm-dialog-*`를 참조하지 않는다. 신규 Primitive 없음.
## 7. shadcn / vaul / Radix에서 유지한 항목
vaul의 drag·snap·dismiss·body 처리와 `data-vaul-*` 속성, Radix의 Portal·focus trap·Escape·ARIA·focus 복귀, 방향별 position class, `data-slot`, `cn(..., className)`, handle 요소(bottom 방향에서만 표시), Overlay.
## 8. HM에서 변경한 항목
- shadcn `bg-background`, 방향별 `rounded-*`/`border-*`/`max-h-[80vh]`, handle `h-2 w-[100px] bg-muted`, Header/Footer `p-4` 제거 → theme CSS.
- `Drawer`의 `autoFocus` 기본 true(위 §5).
- `DrawerBody` 추가, `DrawerClose asChild` data-slot 처리.
- Handle을 `data-slot="drawer-handle"` 요소로 구분해 token으로 스타일.
## 9. Storybook
- 대표 Story `All`("Drawer"): 트리거 3개(기본, 폼, 긴 내용)가 각각 실제 Drawer를 연다. 핸들을 끌어 닫을 수 있다.
- verification: `Open`, `Form`, `Long Content`(처음부터 열림).
## 10. 사용자 확정 결정
- 2026-10-02: ui-viz는 참고 자료이며 최종 기준은 현재 HM 구현과 이 문서다(작업 지시). 개별 수치에 대한 별도 결정은 없다.
## 11. 미확정 / 보류
- 수치(85dvh, 18px Title, handle 크기, 그림자)는 현재 HM 규격이며 디자인 확정 시 재검토.
- Overlay, drag/transition Motion은 vaul·shadcn 기본 유지. `shouldScaleBackground`는 쓰지 않았다.
- snap points 사용 예는 만들지 않았다(API는 vaul 그대로 사용 가능).
- left/right/top 방향의 시각은 CSS에 두었으나 Story로는 검증하지 않았다(bottom만 사용 예).
- Drawer와 Sheet의 사용 경계 가이드는 후속.
## 12. 관련 파일
```
packages/ui/src/components/ui/drawer.tsx
packages/ui/src/theme/drawer.css
packages/ui/package.json (dependency: vaul)
packages/ui/src/theme.css (import)
packages/ui/src/index.ts (export)
src/components/drawer.stories.tsx
```
## 13. 검증 상태
- `npm run typecheck`, `npm run build`: 통과(2026-10-02).
- 실제 interaction(Browser pane): 클릭 열기, focus가 "확인"으로 이동(`autoFocus` 적용 전에는 trigger에 남음), Tab 3회 후에도 Drawer 안(focus trap), Escape 닫힘과 trigger 복귀, handle을 아래로 drag해 닫힘과 focus 복귀, 바깥(Overlay) 클릭 닫힘.
- 390×844 긴 내용: Drawer 높이 717px(85dvh), Body만 scroll, Footer가 viewport 하단, 가로 overflow 없음, Footer 버튼 세로 쌓임.
- computed style: radius `8px 8px 0 0`, border-top `rgb(222,219,210)`, handle 48×4 `rgb(154,146,132)`.
- 미확인: 실제 터치 drag(마우스 drag만 확인), snap points, scroll 영역 안에서 drag와 scroll 구분, 닫기 animation 시각, left/right/top 방향 시각.