Files
HMDesign/docs/components/dialog.md
T

15 KiB
Raw Blame History

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 허용 조합·기본값 주의를 따른다.

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 안에 둔 조합.