@makitt.io/mds-mcp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,259 @@
1
+ # MDS Playbook — Form
2
+
3
+ 어드민 화면에서 Form 사용 약속. **이 표를 보고 결정하라**.
4
+
5
+ > 인용 컴포넌트는 모두 `packages/mds/src/` 에 실재 — fabrication 0. 임의 룰은
6
+ > **TBD** 명시 — 사용자 합의 후 결정.
7
+
8
+ ---
9
+
10
+ ## 1. 컨테이너 결정
11
+
12
+ | 상황 | 컨테이너 | 이유 | Mobile 변형 |
13
+ |---|---|---|---|
14
+ | 페이지 본문 메인 작업 (상품 생성/수정, 설정 페이지) | **page-form** (`Page` shell 안 직접 Form) | 본업 — 가장 큰 공간 | Sidebar → drawer, content full-width, sticky action bar bottom |
15
+ | 목록 화면 옆에서 행 1개 부분 편집 (이름·상태 빠른 수정) | **drawer-form** (`Drawer` 안 Form) | 목록 컨텍스트 유지 + side-by-side | `Drawer side="bottom"` (Sheet style) |
16
+ | 짧은 입력 (필드 ≤ 5 · 확인성 액션, 쿠폰 발급) | **modal-form** (`Modal` 안 Form) | 짧은 입력 + 강제 집중. 5 초과면 page 또는 drawer 로 | `Modal` → full-screen Sheet |
17
+ | 메인 form 안 sub-entity 편집 (variant 추가) | **nested drawer-form** (메인 form 살아있는 채 Drawer 오픈) | nested modal 보다 mental stack 얕음 | 동일 — Drawer 가 적합 |
18
+ | 단순 확인 ("정말 삭제?") | **`Dialog` (confirm)** — Form 아님 | input 없음, action 만 | `Modal.confirm` 그대로 |
19
+
20
+ ### 절대 금지
21
+
22
+ - ❌ Modal **안에** Modal 안 Form — 3단 mental stack. drawer 로 분리
23
+ - ❌ Drawer **안에** Drawer — 같은 이유
24
+
25
+ ---
26
+
27
+ ## 2. Form 안 input 배치 — Section 개수 기준 + caller 재량
28
+
29
+ | 패턴 | 사용 시점 | 예시 | Mobile 변형 |
30
+ |---|---|---|---|
31
+ | **vertical stack** (단일 column) | **1 section** (단일 의미 단위) | 회원가입, 단순 modal-form (쿠폰 발급), 단일 entity 의 simple form | 그대로 |
32
+ | **2-col pair** (horizontal grid) | 의미상 쌍 (시작-종료, 가로-세로, 이름-이메일) — section 내부 또는 단독 | 주소 입력 (이름&이메일 가로 / 도시&우편 가로) | 1-col vertical |
33
+ | **Section + AnchorNav** | **6+ section** (큰 form, 의미 단위 분명) | 상품 등록 (9 section), 큰 설정 페이지 | AnchorNav → drawer (좌측 sidebar 모드) |
34
+ | **2-5 section** | 의미 단위 명확 시 vertical or 2-col pair — caller 재량 | 정책 편집 (3 section), 사용자 프로필 (4 section) | 그대로 또는 1-col |
35
+
36
+ > **결정 근거** — Section 개수가 form 의 정신적 무게의 진짜 기준. 필드 수보다 의미 단위 분명.
37
+ > 동일 9 필드라도 1 section (단순 form) vs 6 section (복잡 form) 의 UX 가 다름.
38
+
39
+ ---
40
+
41
+ ## 3. Field 타입 매핑 (데이터 → MDS 컴포넌트)
42
+
43
+ > 모든 컴포넌트 `packages/mds/src/primitives/inputs/` + `compounds/` 실재 검증됨.
44
+
45
+ | 데이터 형태 | MDS 컴포넌트 | 비고 |
46
+ |---|---|---|
47
+ | 짧은 문자열 (이름, 슬러그) | `TextField` | |
48
+ | 긴 문자열 (설명, 메모) | `Textarea` | |
49
+ | 리치 텍스트 (블로그 본문) | `RichText` (compounds) | 도메인 한정 |
50
+ | 정수 / 통화 / 수량 | `NumberInput` | ArrowUp/Down + Shift x10 + Home/End 키보드 ✓ |
51
+ | 단일 선택 (≤ 7) | `RadioGroup` | 시각적 비교 가능 |
52
+ | 단일 선택 (8+) | `Select` | dropdown |
53
+ | 다중 선택 (작은, ≤ 5) | `Checkbox` ×N | 시각적 비교 |
54
+ | 다중 선택 (큰, 6+) | `<Select multiple>` | discriminated union — `multiple: true` 시 value 가 array. token chip 표시 |
55
+ | boolean 즉시 효과 toggle | `Switch` | TBD: 자동 저장 / 수동 저장 |
56
+ | boolean 약관 동의 | `Checkbox` | submit 시 평가 |
57
+ | 날짜 1개 | `DatePicker` | |
58
+ | 시간 | `TimePicker` | |
59
+ | 숫자 슬라이더 | `Slider` / `RangeSlider` | |
60
+ | 색상 / 토큰 선택 | `Swatch` | |
61
+ | chip 형태 다중 토글 | `ChipToggle` | |
62
+ | 단계형 입력 (1→2→3) | `Stepper` (primitives/feedback) + 페이지 분할 | wizard |
63
+
64
+ ### `Field` compound — label + control + error + hint
65
+
66
+ `packages/mds/src/compounds/Field/` 가 input 을 감싸는 표준 compound:
67
+ - `Field.Label`, control (TextField 등), `Field.Error`, `Field.Hint`
68
+ - input 의 a11y attribute (htmlFor / aria-describedby / aria-errormessage / aria-invalid) 자동 wire
69
+ - 원자 input 직접 사용 보다 `Field` compound 권장 (Storybook 의 stories 도 Field 사용 예시)
70
+
71
+ ### 파일 / 이미지 — Step 13 별도
72
+
73
+ > mds 에 일반 `FileUpload` 컴포넌트 없음 (Step 13 — 별도 신규 작업). 현재 도메인별 구현 (예: web 의 PersonCell avatar / blog 의 cover image).
74
+ > 임시 — domain feature 의 `shared/ui/FileInput/` (TBD), 또는 raw `<input type="file">` + mds wrapper (a11y 보강).
75
+
76
+ ---
77
+
78
+ ## 4. 검증 + 에러 표시
79
+
80
+ | 에러 종류 | 표시 위치 | 컴포넌트 |
81
+ |---|---|---|
82
+ | 단일 필드 검증 (format / 필수 / length) | **inline 필드 아래** | `Field.Error` slot |
83
+ | Cross-field (비밀번호 ≠ 확인) | inline 두 필드 + form summary | `Field.Error` + `Banner` 상단 |
84
+ | Form 전체 검증 (서버 422 + 필드 에러) | 필드별 분배 + 상단 `Banner` 요약 | `Banner` + `Field.Error` |
85
+ | 비검증성 서버 에러 (500, 네트워크) — **일시적** | **toast.error** | `toast.error(title, {description})` |
86
+ | 단순 정보 (저장됐다) — **일시적** | **toast.success** | `toast.success(...)` |
87
+ | **영속** 경고 (라이선스 만료, 결제 만료) | **Notification** (banner, 직접 닫기) | mds 의 영속 Notification — Step 14 별도 신규 작업. 임시 — `Banner` (variant=`warning` / `error`) 의 dismissible mode 활용 |
88
+
89
+ > **핵심 구분**: **Toast = 일시적** (자동 사라짐), **Notification = 영속** (명시적 닫기 전 유지).
90
+
91
+ ### 검증 시점 (RHF mode)
92
+
93
+ | Form 종류 | RHF mode | 동작 |
94
+ |---|---|---|
95
+ | **default** (modal-form / drawer-form / 작은 form) | `onSubmit` (RHF default) | submit 시 검증. 입력 중 방해 X |
96
+ | **page-form** (큰 form / multi-section) | `onTouched` 권장 | blur 후 첫 검증 + 이후 change 시 즉시 재검증 (한 번 틀린 필드는 실시간 fix 도움) |
97
+ | **특수 케이스** (비밀번호 강도, slug 중복) | `onChange` (caller 결정) | 실시간 도움 필수 |
98
+
99
+ 확정 룰:
100
+ | 시점 | 동작 |
101
+ |---|---|
102
+ | submit | 전체 form 검증 + 첫 에러 필드로 scroll/focus |
103
+ | 서버 응답 422 | 서버 에러를 RHF `setError` 로 필드 분배 |
104
+
105
+ ---
106
+
107
+ ## 5. 저장 액션
108
+
109
+ | 액션 종류 | 버튼 위치 | 비활성 조건 |
110
+ |---|---|---|
111
+ | 페이지 form 저장 | 페이지 하단 sticky `Toolbar` 또는 `PageHeader` 우측 primary | `isDirty === false` 또는 `isSubmitting` |
112
+ | Modal/Drawer form 저장 | Footer 우측 primary | 동일 |
113
+ | 위험 액션 (삭제, 환불) | `Modal.confirm` + 빨간 primary | 항상 |
114
+
115
+ ### Inline 편집 (DataGrid 의 1 필드 빠른 수정)
116
+
117
+ | 동작 | 결정 |
118
+ |---|---|
119
+ | 활성화 | cell click → input mode (pencil icon optional) |
120
+ | **저장** | **Enter 키 또는 체크 버튼 명시 click** (blur 자동 저장 ❌) |
121
+ | 취소 | Esc 키 또는 X 버튼 |
122
+ | 위험 / 큰 필드 변경 | Inline 안 함 — drawer / modal 사용 (caller 재량) |
123
+
124
+ > **이유** — Notion/Airtable 같은 blur 자동 저장은 실수 위험 + 의도 불명확.
125
+ > 명시 저장 = click 1 회 더지만 의도 확실.
126
+
127
+ ### Submit 후 결과 처리
128
+
129
+ | 결과 | 처리 |
130
+ |---|---|
131
+ | 200 OK (modal/drawer) | 컨테이너 닫기 + 부모 query invalidate + `toast.success` |
132
+ | 200 OK (페이지) | 페이지 유지 + `isDirty=false` reset + `toast.success` |
133
+ | 4xx 검증 | 컨테이너 유지 + 필드 에러 분배 + 첫 에러 scroll |
134
+ | 5xx / 네트워크 | 컨테이너 유지 + `toast.error` |
135
+
136
+ ### Optimistic update — 기본 적용
137
+
138
+ | 결정 | 동작 |
139
+ |---|---|
140
+ | **기본** | **모든 mutation optimistic** — UI 즉시 갱신, 서버 응답 비동기 |
141
+ | Rollback | 5xx / network error 시 자동 rollback + `toast.error("저장 실패")` |
142
+ | 4xx 검증 에러 | rollback + 필드 에러 분배 + 첫 에러 scroll |
143
+ | **예외** (caller 결정) | 결제 / 환불 / 큰 금액 mutation — 명시 응답 대기 (`isSubmitting` loading) |
144
+
145
+ > **이유** — UX 가장 빠름. React Query 의 `onMutate` + `setQueryData` + `onError rollback`
146
+ > 패턴 표준화.
147
+
148
+ ---
149
+
150
+ ## 6. 배열형 input (반복 필드)
151
+
152
+ 상세 → [array-input Playbook](./array-input.md) (Step 4.6).
153
+
154
+ | 패턴 | 컴포넌트 | 비고 |
155
+ |---|---|---|
156
+ | 짧은 동질 배열 (태그) | `ChipToggle` | item = 단순 string |
157
+ | 가변 row (variant 가격) | RHF `useFieldArray` + `Stack` | 각 row = sub-form |
158
+ | nested entity 다수 (정책, 결제 옵션) | `useFieldArray` + row 클릭 시 **drawer 편집** | row = 요약 |
159
+ | reorder | `Tree` 또는 `useFieldArray` + dnd-kit (mds 자체 dnd 없음) | |
160
+
161
+ ---
162
+
163
+ ## 7. AI fill (자동 채우기)
164
+
165
+ 상세 → [ai-fill Playbook](./ai-fill.md) (Step 4.8).
166
+
167
+ - `useSkillAutofill(agentType)` — generic hook
168
+ - `FormFillApprovalBanner` — Apply / Reject UI
169
+ - `skill-registry` — 등록 → AI 가 catalog query
170
+ - 도메인별 fill hook / Banner / Prompt 작성 금지 (`useSkillAutofill` + shared 사용)
171
+
172
+ ---
173
+
174
+ ## 8. 사용 시점 결정표 (lookup)
175
+
176
+ | 케이스 | 답 |
177
+ |---|---|
178
+ | "필드 ≤ 5 짧은 입력" | modal-form |
179
+ | "10+ 필드 + 설정 페이지" | page-form |
180
+ | "리스트 행 빠른 편집" | drawer-form |
181
+ | "정말 삭제?" | `Modal.confirm` |
182
+ | "저장됐다 일시 알림" | `toast.success` |
183
+ | "서버 500 일시 알림" | `toast.error` |
184
+ | "라이선스 만료 영속 경고" | `Notification` (TBD) |
185
+ | "input 옆 즉시 검증" | `Field.Error` inline |
186
+ | "form 위 전체 요약 에러" | `Banner` 상단 |
187
+ | "variant 줄 추가" | `useFieldArray` + 행 편집 = drawer |
188
+ | "boolean 즉시 효과" | `Switch` (TBD 자동 저장 여부) |
189
+ | "boolean submit 시 평가" | `Checkbox` |
190
+ | "8+ 선택지" | `Select` |
191
+ | "≤ 7 선택지" | `RadioGroup` |
192
+ | "숫자 ↑↓ 키보드" | `NumberInput` (ArrowUp/Down + Shift x10) |
193
+
194
+ ---
195
+
196
+ ## 9. 안티 패턴
197
+
198
+ - ❌ Modal 안 Modal 안 Form — drawer 분리
199
+ - ❌ inline error + toast.error 동시 표시 — 한 곳
200
+ - ❌ 가로 정렬 input 두 개가 서로 의미 무관 — vertical
201
+ - ❌ `Notification` 으로 "저장됨" — toast 가 맞음 (일시적)
202
+ - ❌ Toast 로 "라이선스 만료" — Notification (영속)
203
+ - ❌ raw `<input>` / `<textarea>` 사용 — mds `TextField` / `Textarea` 만
204
+ - ❌ form value 를 `useState` 로 직접 관리 — RHF + Zod 만 (현재 ESLint 룰 추가 검토 중)
205
+ - ❌ Field compound 외부에 input — Field 의 a11y wire 누락
206
+ - ❌ 도메인별 fill hook 작성 — `useSkillAutofill` 사용
207
+
208
+ ---
209
+
210
+ ## 10. Cross-cutting
211
+
212
+ | Axis | 적용 |
213
+ |---|---|
214
+ | **Responsive** | 모든 표의 "Mobile 변형" 컬럼 — 자동 적용 |
215
+ | **A11y** | Field compound 의 aria-* 자동 wire / RHF 의 setFocus 첫 에러 |
216
+ | **i18n** | label / error / placeholder 모두 `t()` 통과 — apps/web 의 ESLint rule 강제 (Step 7) |
217
+ | **AI fill** | skill-registry 등록만 — AI 가 자동 query |
218
+ | **Catalog** | 각 컴포넌트 (TextField / Select / Field 등) 의 catalog.json 자동 추출 (Step 5) |
219
+
220
+ ---
221
+
222
+ ## 11. 결정 완료
223
+
224
+ ### 확정 (cycle 1)
225
+
226
+ | § | 결정 |
227
+ |---|---|
228
+ | §2 Form 배치 | Section 개수 기준 (1 vertical / 2-5 caller 재량 / 6+ AnchorNav) |
229
+ | §4 RHF mode | default `onSubmit`, page-form `onTouched`, 특수 `onChange` |
230
+ | §5 Inline 편집 | 허용 + Enter/체크 명시 저장 (blur 자동 ❌) + Esc 취소 |
231
+ | §5 Optimistic | 모든 mutation 기본 적용 + rollback + error toast. 결제/환불 예외 |
232
+
233
+ ### 확정 (cycle 2 — Step 4.1)
234
+
235
+ | § | 결정 | 근거 |
236
+ |---|---|---|
237
+ | §3 다중 선택 (큰 list) | `<Select multiple>` 사용 — discriminated union (SelectSingleProps vs SelectMultipleProps) | mds 의 Select 가 이미 multiple prop 지원 검증됨 (Select.types.ts §79 + tests §102-194 + stories §100-151) |
238
+ | §5 저장 button i18n key | `t('common.save')` / `t('common.cancel')` / `t('common.delete')` / `t('common.discard')` — 공통 namespace | apps/web 의 i18n.md §3 — common namespace |
239
+ | §5 위험 액션 typing rule | 일반 삭제 = `Modal.confirm` (단순). **영향 큰 액션** (계정 삭제 / 가입자 전체 삭제 / DB drop / 환불 전체) = typing confirm — Modal 안 input + 정확한 문구 입력 시 만 destructive button enabled | 실수 방지 — confirm click 하나 보다 typing 의 의도 명시 강함. caller 가 판단 ("이게 영향 큰가") |
240
+ | §5 위험 액션 typing 의 표준 문구 | i18n key `confirm.typeXToConfirm` + `{x}` 가 entity name (shop slug / customer email / etc.) — 영문 또는 일치 텍스트 | 일관 UX (caller 별 다른 typing rule 안 됨) |
241
+
242
+ ### 별도 step (큰 작업 — Phase 2 계획)
243
+
244
+ | 항목 | 별도 step |
245
+ |---|---|
246
+ | §3 FileUpload | **Step 13 — mds FileUpload 추가** (필요한 a11y / drag-drop / 진행률 / multi / preview / type validation 전체 컴포넌트 설계) |
247
+ | §4 Notification 영속 | **Step 14 — mds Notification 추가** (Toast 와 분리 — Toast 는 일시, Notification 은 영속. 직접 닫기 전 유지. badge 또는 inline) |
248
+
249
+ > cycle 2 마무리 — form.md 의 모든 TBD 확정 또는 별도 step 으로 이관. mds Form 영역 = 완벽 마무리.
250
+
251
+ ---
252
+
253
+ ## Related Playbooks
254
+
255
+ - [feedback.md](./feedback.md) — Toast / Notification / Banner / Modal Dialog (Step 4.2)
256
+ - [overlay.md](./overlay.md) — Modal / Drawer / Sheet / Dialog (Step 4.4)
257
+ - [array-input.md](./array-input.md) — useFieldArray + drawer 편집 (Step 4.6)
258
+ - [async-states.md](./async-states.md) — loading/error/empty/skeleton (Step 4.7)
259
+ - [ai-fill.md](./ai-fill.md) — skill-registry + FormFillApprovalBanner (Step 4.8)
@@ -0,0 +1,222 @@
1
+ # MDS Playbook — Overlay (Modal / Drawer / Sheet / Dialog / Popover)
2
+
3
+ 화면 위 떠다니는 컨테이너 의 선택 표.
4
+
5
+ > mds 의 overlay 군 — **Modal / Drawer / Sheet / Dialog / Popover** 5종.
6
+ > 각자 의도 분명. 헷갈리면 잘못된 mental model.
7
+
8
+ ---
9
+
10
+ ## 1. 5 Overlay 의도 분리
11
+
12
+ | Overlay | 의도 | mental model | 사용 예 |
13
+ |---|---|---|---|
14
+ | **Modal** | **메인 작업 중단 + 작은 sub-task** (form 입력, 확인) | 페이지 위 작은 창 | 회원 생성, 쿠폰 발급, 설정 변경 |
15
+ | **Drawer** | **목록 컨텍스트 유지 + side-by-side 편집** | 메인 옆 슬라이드 | 주문 행 클릭 → drawer 에서 상세, variant 편집 |
16
+ | **Sheet** | **모바일 bottom 시트** (mobile-friendly drawer) | 아래에서 올라오는 시트 | 모바일 의 모든 overlay (Modal / Drawer auto 변환) |
17
+ | **Dialog** | **확인 / 알림 / prompt** (form 아님, action 만) | 짧은 결정 강제 | "정말 삭제?", "환불 처리하시겠습니까?" |
18
+ | **Popover** | **inline 부가 정보 / 작은 picker** | trigger 옆 floating | DatePicker, ColorPicker, dropdown menu |
19
+
20
+ ### 결정 기준
21
+
22
+ | 의도 | overlay |
23
+ |---|---|
24
+ | Form 입력 필요 (sub-entity 생성 / 수정) | Modal (페이지 작업 X) 또는 Drawer (목록 컨텍스트 유지) |
25
+ | 확인 / 경고 / 짧은 prompt | Dialog (`Modal.confirm` / `Modal.alert` / `Modal.prompt`) |
26
+ | 목록 + 행 클릭 → 상세 | Drawer |
27
+ | 모바일 first | Sheet |
28
+ | inline trigger 옆 작은 UI | Popover |
29
+
30
+ ---
31
+
32
+ ## 2. 선택 표 (lookup)
33
+
34
+ | 케이스 | 답 |
35
+ |---|---|
36
+ | "고객 새로 만들기 from 페이지" | Modal (sub-task) |
37
+ | "고객 행 클릭 → 상세 편집" | Drawer (목록 컨텍스트 유지) |
38
+ | "정말 삭제?" | `Modal.confirm` (Dialog) |
39
+ | "환불 처리하시겠습니까?" + 사유 입력 | `Modal.prompt` (Dialog with input) |
40
+ | "라이선스 만료 임박" 영속 알림 | Banner (Step 4.2 feedback) — Modal 아님 |
41
+ | "DatePicker / TimePicker" | Popover (자체 컴포넌트가 사용) |
42
+ | "DropdownMenu" | Popover (Menu.* 사용) |
43
+ | "큰 form (상품 등록)" | page-form (overlay 아님) |
44
+ | "모바일 의 form" | Sheet 또는 full-screen page-form |
45
+ | "Variant 추가 in 상품 등록 form" | nested Drawer (modal 안 modal 금지) |
46
+
47
+ ---
48
+
49
+ ## 3. Modal — sub-task 표준
50
+
51
+ | Slot | mds 컴포넌트 |
52
+ |---|---|
53
+ | Trigger | imperative API (`Modal.open(...)`) — overlay 가 stack-managed |
54
+ | Root | `Modal.Root` (Radix Dialog 의 wrapper) |
55
+ | Overlay (backdrop) | `Modal.Overlay` (자동) |
56
+ | Container | `Modal.Content` (centered, size variant) |
57
+ | Header | `Modal.Header` (title + close button) |
58
+ | Body | `Modal.Body` |
59
+ | Footer | `Modal.Footer` (cancel + confirm primary, 우측 정렬) |
60
+
61
+ ### Size variant
62
+
63
+ | Size | Width | 사용 |
64
+ |---|---|---|
65
+ | xs | 320px | 짧은 confirm dialog |
66
+ | sm | 400px | 단순 form (3-5 필드) |
67
+ | md (default) | 480px | 기본 form |
68
+ | lg | 640px | 복잡 form / 데이터 list |
69
+ | xl | 800px | 대형 (편집기 / 설정 다단) |
70
+
71
+ > Mobile 변형 — md 이상 = full-screen Sheet (자동 변환 — Step 3.4 의 useBreakpoint 활용)
72
+
73
+ ---
74
+
75
+ ## 4. Drawer — 목록 컨텍스트 표준
76
+
77
+ | Slot | mds 컴포넌트 |
78
+ |---|---|
79
+ | Trigger | row click handler (의도된 사용) |
80
+ | Root | `Drawer.Root` |
81
+ | Container | `Drawer.Content` (side variant) |
82
+ | Header | `Drawer.Header` (title + close) |
83
+ | Body | `Drawer.Body` (scroll) |
84
+ | Footer | `Drawer.Footer` (action — Modal 과 동일 패턴) |
85
+
86
+ ### Side variant
87
+
88
+ | Side | 사용 | Default size |
89
+ |---|---|---|
90
+ | right (default) | 메인 옆 슬라이드 in (가장 일반) | 480px (--size-drawer-default-horizontal) |
91
+ | left | 별 사용 안 함 (sidebar 와 헷갈림) | 480px |
92
+ | top / bottom | 모바일 bottom Sheet / 알림 | 320px (--size-drawer-default-vertical) |
93
+
94
+ ### Drawer vs Modal 결정
95
+
96
+ - **Drawer** — 메인 페이지 컨텍스트 유지 필요 (목록 봐가면서 편집)
97
+ - **Modal** — 메인 페이지 작업 중단 + 새 sub-task
98
+
99
+ ---
100
+
101
+ ## 5. Dialog — 확인 / 알림 / prompt
102
+
103
+ Imperative API:
104
+
105
+ ```ts
106
+ import { Modal } from '@makitt/mds';
107
+
108
+ // confirm
109
+ const confirmed = await Modal.confirm({
110
+ title: '정말 삭제할까요?',
111
+ message: '이 작업은 되돌릴 수 없습니다.',
112
+ confirmText: '삭제',
113
+ variant: 'danger',
114
+ });
115
+
116
+ // alert (정보 알림)
117
+ await Modal.alert({
118
+ title: '완료',
119
+ message: '저장됐습니다.',
120
+ });
121
+
122
+ // prompt (단일 input)
123
+ const reason = await Modal.prompt({
124
+ title: '환불 사유',
125
+ placeholder: '사유 입력...',
126
+ required: true,
127
+ });
128
+ ```
129
+
130
+ > mds 의 ConfirmDialog / AlertDialog / PromptDialog — `imperative API` (forwardRef-required 룰 예외).
131
+
132
+ ---
133
+
134
+ ## 6. Popover — inline floating
135
+
136
+ | Slot | mds 컴포넌트 |
137
+ |---|---|
138
+ | Trigger | `Popover.Trigger` (자체 컴포넌트 또는 asChild) |
139
+ | Content | `Popover.Content` (자동 positioning by Radix) |
140
+ | Arrow | `Popover.Arrow` (선택) |
141
+
142
+ ### Popover vs Modal
143
+
144
+ - **Popover** — inline trigger 옆 (close 자유). esc / outside click 닫힘
145
+ - **Modal** — focus trap (강제 결정). 사용자가 닫기 명시
146
+
147
+ ---
148
+
149
+ ## 7. 합성 규칙
150
+
151
+ ### 허용 합성
152
+
153
+ | 합성 | 사용 |
154
+ |---|---|
155
+ | Modal 안 Form | sub-task 의 form (modal-form) |
156
+ | Drawer 안 Form | 목록 옆 편집 (drawer-form) |
157
+ | Modal 안 Popover | DatePicker / DropdownMenu 가 modal 안 ok |
158
+ | Drawer 안 Popover | 동일 |
159
+ | **nested Drawer** | 메인 form (page or drawer) 안 sub-entity 편집 (variant 추가) |
160
+
161
+ ### 절대 금지
162
+
163
+ - ❌ **Modal 안 Modal** — 3단 mental stack. drawer 또는 페이지 이동
164
+ - ❌ **Drawer 안 Drawer** — 동일 이유
165
+ - ❌ **Modal 안 Drawer** / **Drawer 안 Modal** — sub-overlay 의 layer / focus 충돌
166
+ - ❌ **Popover 안 Popover** — Radix Popover Root 의 자식이 또 다른 Popover (auto-position 충돌)
167
+
168
+ ---
169
+
170
+ ## 8. Mobile 변형
171
+
172
+ | Desktop | Mobile (< md) |
173
+ |---|---|
174
+ | Modal (xs / sm) | 그대로 (작아서 모바일 OK) |
175
+ | Modal (md+) | full-screen Sheet 또는 그대로 |
176
+ | Drawer (right / left) | bottom Sheet (`side="bottom"`) |
177
+ | Sheet | 그대로 |
178
+ | Dialog | 그대로 |
179
+ | Popover | 그대로 (자체 positioning) |
180
+
181
+ > Mobile 변환 mechanism — `useIsBreakpointUp('md')` hook 으로 caller 가 명시.
182
+ > 자동 변환 — mds 의 SheetAdapter 추가 (TBD).
183
+
184
+ ---
185
+
186
+ ## 9. 안티 패턴
187
+
188
+ - ❌ Modal 안 Modal (nested form 강제)
189
+ - ❌ Dialog (`Modal.confirm`) 안 form (input multiple) — `Modal.prompt` 또는 form Modal 사용
190
+ - ❌ Popover 안 큰 form — Popover 의 의도 (작은 inline) 아님
191
+ - ❌ Drawer 의 width 변경 (사용자 resize 가능) — caller 가 fix size
192
+ - ❌ Modal close 시 force confirmation 없음 — `isDirty` 시 confirm
193
+ - ❌ Sheet 의 height 가 viewport 의 90%+ — full-screen modal 이 차라리 명확
194
+
195
+ ---
196
+
197
+ ## 10. Cross-cutting
198
+
199
+ | Axis | 적용 |
200
+ |---|---|
201
+ | **Responsive** | useIsBreakpointUp + side 변환 |
202
+ | **A11y** | Radix Dialog 기반 — focus trap / aria / esc 자동 |
203
+ | **Imperative API** | Modal.confirm / Modal.open — overlay stack 관리 (caller 가 ref 안 받음) |
204
+ | **Telemetry** | overlay 의 open/close 추적 (Step 7) |
205
+
206
+ ---
207
+
208
+ ## 11. TBD
209
+
210
+ 1. **SheetAdapter 자동 변환** — `<Modal>` 가 mobile 에서 `<Sheet>` 자동 변환 컴포넌트 추가 여부
211
+ 2. **isDirty 감지** — form 안 dirty 자동 confirm dialog (RHF 의 isDirty 의존)
212
+ 3. **Modal stacking** 최대 depth — 1 default, 2 까지 허용? (현재 nested Drawer 만)
213
+ 4. **Drawer resize handle** — 사용자 resize 가능 vs caller fix
214
+ 5. **Sheet 의 default snap point** — 30% / 60% / 100% (Vaul 같은 패턴)
215
+
216
+ ---
217
+
218
+ ## Related Playbooks
219
+
220
+ - [form.md](./form.md) — Modal-form / Drawer-form / nested-drawer (Step 4.1)
221
+ - [feedback.md](./feedback.md) — Toast / Notification / Banner / Modal Dialog (Step 4.2)
222
+ - [page-layout.md](./page-layout.md) — Page Shell / Sidebar 와 overlay 관계 (Step 4.5)