15 KiB
Dialog
2026-10-10 추가 수정 요청 기준: 일반 확인과 삭제 확인의 취소·실행은 모두 Standard Solid. 과거 검증 기록은 당시 결과이며 현재 검증은 추가 수정 보고서를 따릅니다.
현재 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다. Foundationelevation.*,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), Header24/24/16·Title↔Description 8·Body 24·Actions0/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-md20/28 중 weight 700은 Dialog Title에만 적용(D-006, 600 대체). 이는 당시 ui_viz 근거다. 2026-10-14 HM 정책은 400 / 500 / 700이며 카드 제목도 700을 사용한다. Dialog Title의 700은 유지한다. - 현재 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,showCloseButtonprop. - 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+solid / 삭제 destructive+solid. 같은 세트의 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는 Solid와 Outline을 지원한다. Button 허용 조합·기본값 주의를 따른다.
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-dialogclass 제거. 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-md600→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 1pxrgb(222,219,210), radius 8px, shadow0 0 12px rgba(0,0,0,.2), 크기 344×227(fit-content), 화면 정중앙, max-width 1248px(=1280−32)·max-height 768px(=800−32), Title 20px/28px/700rgb(10,34,29), Description 14px/20px/400rgb(110,103,89), Title↔Description 8px, Header margin-inline 24px·padding 24/16·divider 1px, Body padding 24px·16px/24px, Footer padding0 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가 shadcnleft: 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 안에 둔 조합.