# 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 방향 시각.