157 lines
15 KiB
Markdown
157 lines
15 KiB
Markdown
# Dialog
|
||
|
||
현재 HM 구현의 기준 문서. 공통 원칙은 `docs/guidelines/프로젝트-지침.md`(§6, §7), Token 구조 원칙은 `테마-토큰-가이드.md`를 따른다. 이 문서는 Dialog 고유의 API·Token·상태·결정만 기록한다.
|
||
|
||
> 근거 문서: ui_viz `docs/components/dialog.md`(Dialog / Modal). 문서가 스스로 `NO_SOURCE_EVIDENCE`(Source Dashboard에 Dialog specimen 없음)라고 밝히며, 값의 상당수는 D-093(2026-09-29) Dialog-local Confirmed다. Foundation `elevation.*`, `z-index.*`, `motion.*`는 TBD이고 Dialog 범위 값을 Foundation token으로 올리지 않는다.
|
||
|
||
## 1. 상태
|
||
|
||
| 항목 | 내용 |
|
||
|---|---|
|
||
| 구현 상태 | HM 스타일 적용 완료 (`<DialogContent />`만으로 적용, `hm-dialog` class 없음) |
|
||
| 기준일 | 2026-10-02 |
|
||
| 기반 | shadcn/ui Dialog + Radix `Dialog` |
|
||
| 대표 Story | `Components/Overlay/Dialog` → `All` (이름 "Dialog") |
|
||
| 보류 | Overlay 수치, Close 버튼 규격, 초기 focus 대상, 좁은 화면 Actions 배치 등 (§11) |
|
||
|
||
## 2. 적용 근거
|
||
|
||
- **ui_viz Confirmed (Dialog-local)**: radius 8px(USER_DECISION 2026-09-17), shadow `0 0 12px rgba(0,0,0,.2)`(D-093 D-088), 내용 기준 크기와 최대 너비·높이 공식(D-088), Header `24/24/16`·Title↔Description 8·Body 24·Actions `0/24/24`·Action gap 8(D-093), Header 아래 inset divider 위치·두께와 Footer divider 없음, Body만 scroll, 긴 텍스트 줄바꿈, 표는 Body 안 가로 scroll, 표면 `color.bg.surface`·전경 `color.fg.primary`, Title/Description/Body 타이포.
|
||
- **CANONICAL_OVERRIDE**: Title `heading-md` 20/28 중 weight **700**은 Dialog Title에만 적용(D-006, 600 대체). Foundation `heading-md`는 600 그대로.
|
||
- **현재 HM Actions**: Footer padding은 `16px 24px 24px`이며 divider는 없다. 위 ui_viz 도입 근거의 `0/24/24`와 구분한다.
|
||
- **Temporary / TBD(적용했으나 확정값 아님)**: 외곽선·Header divider 색 `#DEDBD2`는 `Temporary`, Token relation TBD(DIALOG-10 FOLLOW_UP). 이전 확정 색(`#9A9284`/`#E0DACE`)은 대체됐고 D-093 실행 화면과 같은 값이라 현재 구현을 따랐다. 색 역할이 정해지기 전까지 Dialog 범위 값이다.
|
||
- **PARTIAL / REFERENCE_ONLY(적용)**: Description 전경 `color.fg.secondary`(DIALOG-06 PARTIAL, 현재 style 보존). edge 16px(REVIEW).
|
||
- **REFERENCE_ONLY(미적용)**: Backdrop Black 42%. 확정값이 아니어서 shadcn 기본 Overlay를 유지했다.
|
||
- **HM 사용자 결정**: 없음.
|
||
- **shadcn 유지**: Overlay, 열림/닫힘 animation, Close 버튼, Footer `showCloseButton`, `showCloseButton` prop.
|
||
- **Radix 유지**: 아래 §7.
|
||
|
||
## 3. 기본 규격
|
||
|
||
| 항목 | 값 | 근거 |
|
||
|---|---|---|
|
||
| Overlay | shadcn 기본(`bg-black/10`, `backdrop-blur-xs`, `fixed inset-0`, z-50) | ui_viz backdrop 수치 TBD/REFERENCE_ONLY |
|
||
| Container 크기 | 내용 기준(`width: max-content` + max-width = `min(내용, 최대 너비)`). `fit-content`는 shadcn `left: 50%` 배치에서 가용 폭이 화면 절반이 되어 쓰지 않는다(2026-10-06) | Confirmed D-088 |
|
||
| max-width | `100vw − 2 × edge(16px)` | Confirmed 공식 / edge는 REVIEW |
|
||
| max-height | `100dvh − app header − app footer − 2 × edge` | Confirmed 공식, header/footer 기본 0 |
|
||
| 위치 | 가용 영역(앱 header·footer 제외) 가운데 | Confirmed |
|
||
| Background / 글자 | `--hm-sem-bg-surface` / `--hm-sem-fg-primary` | Confirmed reuse |
|
||
| Border | 1px solid `#DEDBD2` | 두께 Confirmed / 색 Temporary |
|
||
| Radius | 8px (Container만) | Confirmed |
|
||
| Shadow | `0 0 12px rgb(0 0 0 / 0.2)` | Confirmed (Dialog-local) |
|
||
| Container padding | 0 (영역별 padding) | 구조 |
|
||
| Header | padding `24px 0 15px` + 아래 1px divider `#DEDBD2`(= ui_viz `24/24/16`, 선은 16px 안쪽 마지막 1px), 좌우 margin 24px(inset 24px) | Confirmed 위치·두께 / 색 Temporary |
|
||
| Title ↔ Description | gap 8px (Description이 없으면 없음) | Confirmed |
|
||
| Title | 20px / 28px / 700, `fg.primary` | Confirmed (Dialog-local) |
|
||
| Description | 14px / 20px / 400, `fg.secondary` | 크기 Confirmed / 색 PARTIAL |
|
||
| Body | padding 24px 사방, 16px / 24px / 400, 첫·마지막 child margin 0 | Confirmed |
|
||
| Footer(Actions) | padding `16px 24px 24px`, gap 8px, 오른쪽 정렬, 위 divider 없음 | 현재 HM 구현 |
|
||
| 긴 텍스트 | `overflow-wrap: anywhere` | Confirmed |
|
||
| Close 버튼 | shadcn 기본 (ghost `icon-sm` 28px, 오른쪽 위 8px 안쪽, `XIcon`, sr-only "Close") | ui_viz 미정의 |
|
||
|
||
## 4. 상태 규격
|
||
|
||
| 상태 | 값 | 근거 |
|
||
|---|---|---|
|
||
| Closed / Open | Radix `data-state` (`open`/`closed`). Open 시 modal, 배경 inert, scroll lock | Radix |
|
||
| Open animation | shadcn 기본(fade + zoom, 100ms) | ui_viz motion TBD → shadcn 유지 |
|
||
| Focus | 전역 focus ring은 ui_viz TBD. 조합한 Button이 각자의 focus를 가짐 | TBD |
|
||
| Long Content | Header·Footer 고정, Body만 세로 scroll, 짧으면 scrollbar 없음 | Confirmed |
|
||
| Wide Content | Body 안 가로 scroll, 페이지 가로 overflow 없음 | Confirmed |
|
||
| Confirmation / Destructive | 컴포넌트 상태가 아니라 Button 조합: 일반 확인은 취소 `cancel`+`solid` / 확인 `primary`+`solid`, 삭제 확인은 취소 `cancel`+`outline` / 삭제 `destructive`+`outline`. 같은 세트의 appearance·size 통일, Brand 금지 | HM 사용자 확정 Button 세트 규칙 |
|
||
|
||
Loading, Selected, Disabled 등 Dialog 자체의 다른 상태는 ui_viz에 없어 만들지 않았다.
|
||
|
||
## 5. API
|
||
|
||
```
|
||
Dialog, DialogTrigger, DialogPortal, DialogOverlay, DialogContent,
|
||
DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose:
|
||
shadcn / Radix API 그대로 (open, defaultOpen, onOpenChange, modal, asChild, className ...)
|
||
|
||
DialogBody:
|
||
(신규) <div data-slot="dialog-body"> — ui_viz Body region
|
||
|
||
DialogClose:
|
||
shadcn: 항상 data-slot="dialog-close"
|
||
→
|
||
HM: asChild일 때는 data-slot을 붙이지 않음 (자식 Button의 data-slot="button" 유지)
|
||
```
|
||
|
||
- `DialogContent`의 `showCloseButton`(기본 true), `DialogFooter`의 `showCloseButton`(기본 false)은 shadcn 그대로.
|
||
- 새 prop(`size`, `variant`, `destructive`, `loading`, `placement`, `dismissible`)은 근거가 없어 만들지 않았다.
|
||
- `DialogBody`를 추가한 이유: ui_viz가 Body를 필수 region이자 유일한 scroll owner로 정의하기 때문이다. Body 없이도 동작하지만(자식이 그대로 놓임) 이 경우 Body padding과 내부 scroll이 적용되지 않는다.
|
||
- `DialogClose` 변경 이유: shadcn은 `DialogClose asChild`의 `data-slot="dialog-close"`가 자식 `<Button>`의 `data-slot="button"`을 덮어써 HM Button 스타일(`[data-slot="button"]`)이 사라진다. Dialog Actions는 Button 조합이 규격(§4)이라 Dialog 쪽에서 고쳤다.
|
||
- Actions의 Button에는 appearance를 명시한다. Button 기본값은 Brand이며, Destructive는 Outline만 지원한다. [Button 허용 조합·기본값 주의](button.md#61-허용-role--appearance-ai-reference)를 따른다.
|
||
|
||
## 6. Token
|
||
|
||
Component Mapping 위치: `packages/ui/src/theme/dialog.css` (`--hm-dialog-*`), `packages/ui/src/theme.css`에서 import.
|
||
|
||
- 재사용한 공통 값: `--hm-sem-bg-surface`, `--hm-sem-fg-primary`, `--hm-sem-fg-secondary`, `--hm-sem-stroke-input-default`(`#dedbd2`).
|
||
- Dialog 전용 값: `--hm-dialog-radius`(8px), `--hm-dialog-shadow`, `--hm-dialog-background`, `--hm-dialog-text`, `--hm-dialog-border-width|color`, `--hm-dialog-divider-color`, `--hm-dialog-padding`(24px), `--hm-dialog-header-padding-bottom`(16px), `--hm-dialog-header-gap`(8px), `--hm-dialog-actions-gap`(8px), Title/Description/Body 글자 크기·줄 높이·굵기, `--hm-dialog-description-color`, `--hm-dialog-edge`(16px, REVIEW).
|
||
- 소비 화면이 설정하는 Web Mapping 이름: `--hm-dialog-app-header-height`, `--hm-dialog-app-footer-height` (기본 0). ui_viz도 Canonical token이 아니라고 명시한다.
|
||
- Primitive 신규 추가 없음.
|
||
- Card(radius 4px, border `#dedbd2`)·Input·Button과 값이 겹쳐도 alias하지 않는다. 8px radius는 ui_viz가 Dialog container에만 허용하고 다른 곳으로 확장하지 말라고 명시한다.
|
||
- Overlay·Close 버튼·animation은 token을 만들지 않았다(shadcn 기본).
|
||
|
||
## 7. shadcn / Radix에서 유지한 항목
|
||
|
||
Radix 구조 전부를 그대로 둔다: Portal(`DialogPortal`), Overlay, modal 동작과 배경 `aria-hidden`/inert, focus trap, 열릴 때 initial focus(Radix 기본), 닫힐 때 trigger로 focus 복귀, Escape 닫기, outside interaction 처리, `role="dialog"`와 `aria-labelledby`/`aria-describedby` 연결(`DialogTitle`/`DialogDescription`), Tab 순환, body scroll lock, controlled/uncontrolled(`open`/`defaultOpen`/`onOpenChange`), `asChild`, Close 동작(`DialogPrimitive.Close` 유지, `setOpen` 직접 호출 없음), Close 버튼의 sr-only 레이블과 focus-visible, 열림/닫힘 animation class, `data-slot`, `cn(..., className)`.
|
||
|
||
## 8. HM에서 변경한 항목
|
||
|
||
- `DialogContent`에서 `grid`, `w-full max-w-[calc(100%-2rem)] sm:max-w-sm`, `gap-4`, `rounded-xl`, `bg-popover`, `p-4`, `ring-1`, `text-popover-foreground`를 제거하고 theme CSS로 이전: 내용 기준 크기(`max-content` + max-width), 최대 너비·높이 공식, 8px radius, 1px border, shadow, 흰 표면.
|
||
- Header 아래 inset divider, Header·Body·Footer 간격 24/16/8, 타이포.
|
||
- `DialogFooter`의 `-mx-4 -mb-4`, `border-t`, `bg-muted/50`, `rounded-b-xl`, `p-4`, `gap-2` 제거(경계·배경 없는 Actions 영역).
|
||
- `DialogBody` 추가, `DialogClose asChild`의 data-slot 처리.
|
||
- `hm-dialog` class 제거. Demo의 `.hm-dialog-summary`(Dialog 안 내용 레이아웃)는 Demo 전용으로 남겼다.
|
||
|
||
## 9. Storybook
|
||
|
||
- 대표 Story `All`("Dialog")이 기본 sidebar의 유일한 Dialog 항목이다 → 1뎁스. overlay라 StateBoard를 쓰지 않고, 트리거 버튼 6개(기본, 긴 내용, 넓은 표, 본문 스크롤, 삭제 확인, 폼)가 각각 실제 Dialog를 연다.
|
||
- 본문 스크롤은 ui_viz "세로 스크롤 · 본문만"과 같은 조건(앱 공통 Header 70px / Footer 48px 제외)이다. Portal 때문에 앱 높이는 trigger wrapper가 아니라 `DialogContent`의 `style`로 `--hm-dialog-app-header-height`/`--hm-dialog-app-footer-height`를 넘긴다.
|
||
- verification(`tags: ["verification"]`, 닫힌 채가 아니라 처음부터 열림): `Open`, `Long Content`, `Wide Content`, `Body Scroll`, `Delete Confirmation`.
|
||
- pseudo-state helper로 열린 상태를 흉내 내지 않는다.
|
||
- 운영 규칙은 `프로젝트-지침.md` §6.3, §7 참조.
|
||
|
||
## 10. 사용자 확정 결정
|
||
|
||
추가 사용자 결정 없음. (Radius 8px의 2026-09-17 USER_DECISION은 ui_viz 쪽 기록이다.)
|
||
|
||
## 11. 미확정 / 보류
|
||
|
||
- **Overlay**: Black 42%는 REFERENCE_ONLY, 색·opacity는 TBD. 현재 shadcn 기본 `black/10` + blur를 쓰며 ui_viz는 blur를 정의하지 않는다. DECISION_REQUIRED.
|
||
- **외곽선·Header divider 색**: `#DEDBD2`는 Temporary, Token relation TBD(DIALOG-10 FOLLOW_UP).
|
||
- **초기 focus**: ui_viz(DIALOG-07, 2026-09-17)는 미지정 시 Dialog 자체가 initial focus를 받는다고 정했다. 현재는 Radix 기본(첫 tabbable 요소; 대표 Story에서는 "취소")이다. Radix 동작을 바꾸지 않기 위해 구현하지 않았다. DECISION_REQUIRED.
|
||
- **Close(X) 버튼**: ui_viz에 규격이 없다. shadcn 기본이며 긴 Title이 버튼과 겹칠 수 있는지 확인하지 않았다.
|
||
- **좁은 화면 Actions 배치**: ui_viz TBD. shadcn(`flex-col-reverse sm:flex-row`)을 유지.
|
||
- **Description 전경**: PARTIAL. **edge 16px**: REVIEW. **motion·z-index·focus ring**: TBD.
|
||
- **ui_viz FOLLOW_UP**: Alert Dialog·non-dismissible(DIALOG-03), Nested Dialog(DIALOG-04), Select portal 조합과 업무 Dialog 검수(DIALOG-09), `heading-md` 600→700(DIALOG-11). 현재 Alert Dialog는 별도 Component로 구현되어 있다(`alert-dialog.md`). non-dismissible/Nested 조합의 별도 검수는 여전히 보류다.
|
||
- 공통 header/footer 높이 변수는 소비 화면이 연결해야 하며 이번에 실제 화면에 연결하지 않았다.
|
||
|
||
## 12. 관련 파일
|
||
|
||
```
|
||
packages/ui/src/components/ui/dialog.tsx
|
||
packages/ui/src/theme/dialog.css
|
||
packages/ui/src/theme.css (dialog.css import)
|
||
src/components/dialog.stories.tsx
|
||
src/App.tsx (Demo Dialog: hm-dialog 제거, DialogBody 사용)
|
||
src/hm-theme.css (.hm-dialog 규칙 제거, .hm-dialog-summary 유지)
|
||
```
|
||
|
||
## 13. 검증 상태
|
||
|
||
> HISTORICAL_RECORD: 아래 측정값은 각 검증 당시 기록이다. 현재 Footer top padding은 §3의 16px이며 과거 크기·padding 측정값을 현재값으로 덮어쓰지 않는다.
|
||
|
||
- TypeScript, `npm run build`: 통과(2026-10-02).
|
||
- Storybook 실제 상호작용(1280×800): 트리거 클릭으로 열림, Escape로 닫힘, 닫힌 뒤 trigger로 focus 복귀, Close(X) 클릭으로 닫힘 후 trigger focus 복귀, `role="dialog"`·`aria-labelledby`·`aria-describedby`, body scroll lock(`data-scroll-locked`) 확인.
|
||
- computed style(기본 Dialog): background `rgb(255,255,255)`, border 1px `rgb(222,219,210)`, radius 8px, shadow `0 0 12px rgba(0,0,0,.2)`, 크기 344×227(fit-content), 화면 정중앙, max-width 1248px(=1280−32)·max-height 768px(=800−32), Title 20px/28px/700 `rgb(10,34,29)`, Description 14px/20px/400 `rgb(110,103,89)`, Title↔Description 8px, Header margin-inline 24px·padding 24/16·divider 1px, Body padding 24px·16px/24px, Footer padding `0 24px 24px`·gap 8px·border 0·배경 투명, Close 28×28 오른쪽·위 9px(8px + 외곽선 1px).
|
||
- Long Content: Dialog 730px(viewport 안), Header·Footer 보임, Body만 scroll(client 613 / scroll 1712), 페이지 가로 overflow 없음. Wide Content: Body 가로 scroll, 페이지 가로 overflow 없음.
|
||
- Actions: 취소 Gray Solid(`rgb(118,118,118)`/`rgb(90,90,90)`), 삭제 Red Outline(`rgb(211,47,47)`) 적용 확인(`DialogClose` 수정 후).
|
||
- Story: `All` + verification 4개 ID 확인. 기본 sidebar는 `All` 하나만 보이는 1뎁스 구조(실제 사이드바 화면은 확인하지 못함).
|
||
- 2026-10-06 ui_viz Storybook 화면 비교(1280/768/390, Chromium): REGRESSION 2건 수정 — ① `fit-content`가 shadcn `left: 50%` 배치에서 화면 절반만 가용 폭으로 받아 390px에서 Dialog가 195px로 좁아짐 → `width: max-content` + max-width, ② Header가 divider 1px만큼 큼(97px) → bottom padding에서 divider 두께를 뺌(96px). 수정 후 Header 96px·기본 높이 228px이 ui_viz와 같고, 본문 스크롤 Dialog는 3개 viewport 모두 ui_viz와 같은 크기·위치(1280: 580×750 @350,86 / 390: 358×750 @16,86). Tab·Shift+Tab 순환, `aria-labelledby`/`aria-describedby`, scroll lock, Escape·액션·X 닫기 후 trigger focus 복귀 확인(1280/390).
|
||
- 의도적 차이(유지): Overlay(ui_viz 42% REFERENCE_ONLY), Close X(ui_viz 미정의), 640px 미만 Footer 세로 stack(ui_viz TBD), 초기 focus(Radix 기본 = 첫 focus 가능 요소, ui_viz는 Title/Cancel).
|
||
- 미확인: Demo(App)의 Dialog 열림 시각 확인, Select를 Dialog 안에 둔 조합.
|