116 lines
7.2 KiB
Markdown
116 lines
7.2 KiB
Markdown
# Drawer
|
||
|
||
> 2026-10-10 추가 수정 요청 기준: 데스크톱 하단 우측 가로 취소 → 실행, 640px 이하 전체 폭 세로 실행 위 / 취소 아래. DOM은 취소 → 실행 순서이며 모바일 CSS column-reverse 적용. vaul 드래그 닫기 유지. 과거 검증 기록은 당시 결과이며 현재 검증은 [추가 수정 보고서](../guidelines/publisher-additional-review-2026-10-10.md)를 따릅니다.
|
||
|
||
현재 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. 데스크톱 우측 가로 취소 → 실행, 640px 이하 전체 폭 세로 실행 → 취소 |
|
||
| 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 방향 시각.
|