sh-ui-cli 0.15.0 → 0.21.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.
Files changed (162) hide show
  1. package/bin/sh-ui.mjs +6 -0
  2. package/data/changelog/versions.json +354 -0
  3. package/data/registry/flutter/foundation/sh_ui_tokens.dart +385 -0
  4. package/data/registry/flutter/registry.json +336 -0
  5. package/data/registry/flutter/widgets/sh_ui_accordion.dart +255 -0
  6. package/data/registry/flutter/widgets/sh_ui_app_shell.dart +267 -0
  7. package/data/registry/flutter/widgets/sh_ui_avatar.dart +95 -0
  8. package/data/registry/flutter/widgets/sh_ui_badge.dart +82 -0
  9. package/data/registry/flutter/widgets/sh_ui_breadcrumb.dart +107 -0
  10. package/data/registry/flutter/widgets/sh_ui_button.dart +201 -0
  11. package/data/registry/flutter/widgets/sh_ui_card.dart +159 -0
  12. package/data/registry/flutter/widgets/sh_ui_carousel.dart +204 -0
  13. package/data/registry/flutter/widgets/sh_ui_checkbox.dart +154 -0
  14. package/data/registry/flutter/widgets/sh_ui_color_picker.dart +264 -0
  15. package/data/registry/flutter/widgets/sh_ui_combobox.dart +614 -0
  16. package/data/registry/flutter/widgets/sh_ui_context_menu.dart +71 -0
  17. package/data/registry/flutter/widgets/sh_ui_date_picker.dart +648 -0
  18. package/data/registry/flutter/widgets/sh_ui_dialog.dart +567 -0
  19. package/data/registry/flutter/widgets/sh_ui_dropdown_menu.dart +251 -0
  20. package/data/registry/flutter/widgets/sh_ui_file_upload.dart +200 -0
  21. package/data/registry/flutter/widgets/sh_ui_header.dart +488 -0
  22. package/data/registry/flutter/widgets/sh_ui_input.dart +664 -0
  23. package/data/registry/flutter/widgets/sh_ui_label.dart +145 -0
  24. package/data/registry/flutter/widgets/sh_ui_menubar.dart +98 -0
  25. package/data/registry/flutter/widgets/sh_ui_pagination.dart +276 -0
  26. package/data/registry/flutter/widgets/sh_ui_popover.dart +248 -0
  27. package/data/registry/flutter/widgets/sh_ui_progress.dart +47 -0
  28. package/data/registry/flutter/widgets/sh_ui_radio.dart +108 -0
  29. package/data/registry/flutter/widgets/sh_ui_select.dart +904 -0
  30. package/data/registry/flutter/widgets/sh_ui_separator.dart +42 -0
  31. package/data/registry/flutter/widgets/sh_ui_sidebar.dart +1116 -0
  32. package/data/registry/flutter/widgets/sh_ui_skeleton.dart +129 -0
  33. package/data/registry/flutter/widgets/sh_ui_slider.dart +147 -0
  34. package/data/registry/flutter/widgets/sh_ui_spinner.dart +56 -0
  35. package/data/registry/flutter/widgets/sh_ui_switch.dart +109 -0
  36. package/data/registry/flutter/widgets/sh_ui_tabs.dart +329 -0
  37. package/data/registry/flutter/widgets/sh_ui_textarea.dart +126 -0
  38. package/data/registry/flutter/widgets/sh_ui_toast.dart +362 -0
  39. package/data/registry/flutter/widgets/sh_ui_toggle.dart +229 -0
  40. package/data/registry/flutter/widgets/sh_ui_tooltip.dart +62 -0
  41. package/data/registry/react/components/accordion/index.tsx +85 -0
  42. package/data/registry/react/components/accordion/styles.css +94 -0
  43. package/data/registry/react/components/animations/animations.css +51 -0
  44. package/data/registry/react/components/avatar/index.tsx +75 -0
  45. package/data/registry/react/components/avatar/styles.css +36 -0
  46. package/data/registry/react/components/badge/index.tsx +42 -0
  47. package/data/registry/react/components/badge/styles.css +57 -0
  48. package/data/registry/react/components/base/base.css +102 -0
  49. package/data/registry/react/components/breadcrumb/index.tsx +154 -0
  50. package/data/registry/react/components/breadcrumb/styles.css +82 -0
  51. package/data/registry/react/components/breakpoints/breakpoints.css +17 -0
  52. package/data/registry/react/components/button/index.tsx +47 -0
  53. package/data/registry/react/components/button/styles.css +93 -0
  54. package/data/registry/react/components/card/index.tsx +86 -0
  55. package/data/registry/react/components/card/styles.css +73 -0
  56. package/data/registry/react/components/carousel/index.tsx +432 -0
  57. package/data/registry/react/components/carousel/styles.css +155 -0
  58. package/data/registry/react/components/checkbox/index.tsx +98 -0
  59. package/data/registry/react/components/checkbox/styles.css +75 -0
  60. package/data/registry/react/components/code-panel/copy.tsx +56 -0
  61. package/data/registry/react/components/code-panel/index.tsx +193 -0
  62. package/data/registry/react/components/code-panel/styles.css +124 -0
  63. package/data/registry/react/components/color-picker/index.tsx +466 -0
  64. package/data/registry/react/components/color-picker/styles.css +166 -0
  65. package/data/registry/react/components/combobox/index.tsx +167 -0
  66. package/data/registry/react/components/combobox/styles.css +151 -0
  67. package/data/registry/react/components/context-menu/index.tsx +253 -0
  68. package/data/registry/react/components/context-menu/styles.css +140 -0
  69. package/data/registry/react/components/date-picker/index.tsx +757 -0
  70. package/data/registry/react/components/date-picker/styles.css +279 -0
  71. package/data/registry/react/components/dialog/index.tsx +97 -0
  72. package/data/registry/react/components/dialog/styles.css +127 -0
  73. package/data/registry/react/components/dropdown-menu/index.tsx +257 -0
  74. package/data/registry/react/components/dropdown-menu/styles.css +150 -0
  75. package/data/registry/react/components/file-upload/index.tsx +489 -0
  76. package/data/registry/react/components/file-upload/styles.css +170 -0
  77. package/data/registry/react/components/focus-ring/focus-ring.css +23 -0
  78. package/data/registry/react/components/form/context.ts +92 -0
  79. package/data/registry/react/components/form/field.test.tsx +230 -0
  80. package/data/registry/react/components/form/field.tsx +236 -0
  81. package/data/registry/react/components/form/focus-first-error.ts +54 -0
  82. package/data/registry/react/components/form/form.section.test.tsx +58 -0
  83. package/data/registry/react/components/form/form.test.tsx +146 -0
  84. package/data/registry/react/components/form/form.tsx +180 -0
  85. package/data/registry/react/components/form/index.tsx +61 -0
  86. package/data/registry/react/components/form/steps.test.tsx +106 -0
  87. package/data/registry/react/components/form/steps.tsx +193 -0
  88. package/data/registry/react/components/form/store.test.ts +206 -0
  89. package/data/registry/react/components/form/store.ts +318 -0
  90. package/data/registry/react/components/form/styles.css +47 -0
  91. package/data/registry/react/components/form/types.ts +104 -0
  92. package/data/registry/react/components/form/use-sh-ui-form.ts +15 -0
  93. package/data/registry/react/components/form/utils.test.ts +44 -0
  94. package/data/registry/react/components/form/utils.ts +49 -0
  95. package/data/registry/react/components/form/validation.test.ts +67 -0
  96. package/data/registry/react/components/form/validation.ts +64 -0
  97. package/data/registry/react/components/form-rhf/README.md +27 -0
  98. package/data/registry/react/components/form-rhf/index.tsx +289 -0
  99. package/data/registry/react/components/form-rhf/rhf.test.tsx +42 -0
  100. package/data/registry/react/components/form-tanstack/README.md +27 -0
  101. package/data/registry/react/components/form-tanstack/index.tsx +352 -0
  102. package/data/registry/react/components/form-tanstack/tanstack.test.tsx +45 -0
  103. package/data/registry/react/components/form-yup/README.md +22 -0
  104. package/data/registry/react/components/form-yup/index.tsx +50 -0
  105. package/data/registry/react/components/form-yup/yup.test.ts +27 -0
  106. package/data/registry/react/components/header/index.tsx +257 -0
  107. package/data/registry/react/components/header/styles.css +190 -0
  108. package/data/registry/react/components/input/index.tsx +517 -0
  109. package/data/registry/react/components/input/styles.css +203 -0
  110. package/data/registry/react/components/label/index.tsx +54 -0
  111. package/data/registry/react/components/label/styles.css +90 -0
  112. package/data/registry/react/components/menubar/index.tsx +34 -0
  113. package/data/registry/react/components/menubar/styles.css +45 -0
  114. package/data/registry/react/components/pagination/index.tsx +271 -0
  115. package/data/registry/react/components/pagination/styles.css +105 -0
  116. package/data/registry/react/components/popover/index.tsx +115 -0
  117. package/data/registry/react/components/popover/styles.css +65 -0
  118. package/data/registry/react/components/progress/index.tsx +56 -0
  119. package/data/registry/react/components/progress/styles.css +41 -0
  120. package/data/registry/react/components/radio/index.tsx +67 -0
  121. package/data/registry/react/components/radio/styles.css +80 -0
  122. package/data/registry/react/components/select/index.tsx +236 -0
  123. package/data/registry/react/components/select/styles.css +193 -0
  124. package/data/registry/react/components/separator/index.tsx +48 -0
  125. package/data/registry/react/components/separator/styles.css +15 -0
  126. package/data/registry/react/components/sidebar/index.tsx +1084 -0
  127. package/data/registry/react/components/sidebar/styles.css +502 -0
  128. package/data/registry/react/components/skeleton/index.tsx +24 -0
  129. package/data/registry/react/components/skeleton/styles.css +24 -0
  130. package/data/registry/react/components/slider/index.tsx +300 -0
  131. package/data/registry/react/components/slider/styles.css +64 -0
  132. package/data/registry/react/components/spinner/index.tsx +40 -0
  133. package/data/registry/react/components/spinner/styles.css +37 -0
  134. package/data/registry/react/components/switch/index.tsx +41 -0
  135. package/data/registry/react/components/switch/styles.css +83 -0
  136. package/data/registry/react/components/tabs/index.tsx +93 -0
  137. package/data/registry/react/components/tabs/styles.css +148 -0
  138. package/data/registry/react/components/textarea/index.tsx +25 -0
  139. package/data/registry/react/components/textarea/styles.css +54 -0
  140. package/data/registry/react/components/theme/index.tsx +91 -0
  141. package/data/registry/react/components/toast/index.tsx +257 -0
  142. package/data/registry/react/components/toast/styles.css +290 -0
  143. package/data/registry/react/components/toggle/index.tsx +133 -0
  144. package/data/registry/react/components/toggle/styles.css +85 -0
  145. package/data/registry/react/components/tooltip/index.tsx +85 -0
  146. package/data/registry/react/components/tooltip/styles.css +44 -0
  147. package/data/registry/react/components/z-index/z-index.css +16 -0
  148. package/data/registry/react/hooks/use-active-section.ts +104 -0
  149. package/data/registry/react/hooks/use-media-query.ts +27 -0
  150. package/data/registry/react/lib/cn.ts +39 -0
  151. package/data/registry/react/registry.json +835 -0
  152. package/data/summaries/flutter.json +42 -0
  153. package/data/summaries/react.json +50 -0
  154. package/data/tokens/build.mjs +553 -0
  155. package/data/tokens/src/primitives.json +146 -0
  156. package/data/tokens/src/semantic.json +146 -0
  157. package/package.json +9 -2
  158. package/src/add.mjs +13 -12
  159. package/src/list.mjs +3 -11
  160. package/src/mcp.mjs +308 -0
  161. package/src/paths.mjs +52 -0
  162. package/src/remove.mjs +4 -11
@@ -0,0 +1,279 @@
1
+ /* ��─ Trigger (input-like) ── */
2
+
3
+ .sh-ui-date-picker__trigger {
4
+ display: inline-flex;
5
+ align-items: center;
6
+ justify-content: space-between;
7
+ width: 100%;
8
+ height: var(--control-md);
9
+ padding: 0 var(--space-3);
10
+ background: var(--background);
11
+ color: var(--foreground);
12
+ border: 1px solid var(--border);
13
+ border-radius: var(--radius);
14
+ font-family: inherit;
15
+ font-size: var(--text-sm);
16
+ line-height: 1;
17
+ cursor: pointer;
18
+ transition: border-color var(--duration-fast), box-shadow var(--duration-fast);
19
+ -webkit-tap-highlight-color: transparent;
20
+ }
21
+
22
+ @media (hover: none) and (pointer: coarse) {
23
+ .sh-ui-date-picker__trigger {
24
+ height: 2.75rem;
25
+ font-size: var(--text-base);
26
+ }
27
+ }
28
+
29
+ .sh-ui-date-picker__trigger:hover:not(:disabled) {
30
+ border-color: var(--border-strong);
31
+ }
32
+
33
+ .sh-ui-date-picker__trigger:focus-visible {
34
+ outline: none;
35
+ border-color: var(--foreground);
36
+ box-shadow: 0 0 0 1px var(--foreground);
37
+ }
38
+
39
+ .sh-ui-date-picker__trigger:disabled {
40
+ opacity: var(--opacity-disabled);
41
+ cursor: not-allowed;
42
+ background: var(--background-subtle);
43
+ }
44
+
45
+ .sh-ui-date-picker__trigger[aria-invalid="true"] {
46
+ border-color: var(--danger);
47
+ }
48
+ .sh-ui-date-picker__trigger[aria-invalid="true"]:focus-visible {
49
+ box-shadow: 0 0 0 1px var(--danger);
50
+ }
51
+
52
+ .sh-ui-date-picker__value {
53
+ overflow: hidden;
54
+ text-overflow: ellipsis;
55
+ white-space: nowrap;
56
+ }
57
+
58
+ .sh-ui-date-picker__placeholder {
59
+ color: var(--foreground-subtle);
60
+ }
61
+
62
+ .sh-ui-date-picker__icon {
63
+ flex-shrink: 0;
64
+ display: inline-flex;
65
+ color: var(--foreground-muted);
66
+ margin-left: var(--space-2);
67
+ }
68
+
69
+ /* ── Positioner / Popup ── */
70
+
71
+ .sh-ui-date-picker__positioner {
72
+ z-index: var(--z-popover);
73
+ outline: none;
74
+ }
75
+
76
+ .sh-ui-date-picker__popup {
77
+ background: var(--background);
78
+ border: 1px solid var(--border);
79
+ border-radius: var(--radius);
80
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
81
+ outline: none;
82
+ padding: var(--space-3);
83
+ transform-origin: var(--transform-origin);
84
+ transition: opacity 140ms ease, transform 140ms ease;
85
+ }
86
+
87
+ .sh-ui-date-picker__popup[data-starting-style],
88
+ .sh-ui-date-picker__popup[data-ending-style] {
89
+ opacity: 0;
90
+ transform: scale(0.96);
91
+ }
92
+
93
+ /* ── Calendar ���─ */
94
+
95
+ .sh-ui-calendar {
96
+ width: 17.5rem;
97
+ user-select: none;
98
+ }
99
+
100
+ .sh-ui-calendar__header {
101
+ display: flex;
102
+ align-items: center;
103
+ justify-content: space-between;
104
+ margin-bottom: var(--space-2);
105
+ }
106
+
107
+ .sh-ui-calendar__title {
108
+ font-size: var(--text-sm);
109
+ font-weight: var(--weight-semibold);
110
+ }
111
+
112
+ .sh-ui-calendar__nav {
113
+ display: inline-flex;
114
+ align-items: center;
115
+ justify-content: center;
116
+ width: 1.75rem;
117
+ height: 1.75rem;
118
+ padding: 0;
119
+ border: none;
120
+ border-radius: calc(var(--radius) - 2px);
121
+ background: transparent;
122
+ color: var(--foreground-muted);
123
+ cursor: pointer;
124
+ transition: background-color var(--duration-fast), color var(--duration-fast);
125
+ }
126
+
127
+ .sh-ui-calendar__nav:hover {
128
+ background: var(--background-muted);
129
+ color: var(--foreground);
130
+ }
131
+
132
+ .sh-ui-calendar__nav:focus-visible {
133
+ outline: var(--border-width-strong) solid var(--foreground);
134
+ outline-offset: 2px;
135
+ }
136
+
137
+ /* ── Weekdays ── */
138
+
139
+ .sh-ui-calendar__weekdays {
140
+ display: grid;
141
+ grid-template-columns: repeat(7, 1fr);
142
+ margin-bottom: var(--space-1);
143
+ }
144
+
145
+ .sh-ui-calendar__weekday {
146
+ display: flex;
147
+ align-items: center;
148
+ justify-content: center;
149
+ height: 2rem;
150
+ font-size: var(--text-xs);
151
+ font-weight: var(--weight-medium);
152
+ color: var(--foreground-muted);
153
+ }
154
+
155
+ /* ── Grid ��─ */
156
+
157
+ .sh-ui-calendar__grid {
158
+ display: grid;
159
+ grid-template-columns: repeat(7, 1fr);
160
+ outline: none;
161
+ }
162
+
163
+ .sh-ui-calendar__grid:focus-visible {
164
+ outline: var(--border-width-strong) solid var(--foreground);
165
+ outline-offset: 2px;
166
+ border-radius: calc(var(--radius) - 2px);
167
+ }
168
+
169
+ /* ── Day cell ── */
170
+
171
+ .sh-ui-calendar__day {
172
+ display: flex;
173
+ align-items: center;
174
+ justify-content: center;
175
+ width: 2.25rem;
176
+ height: 2.25rem;
177
+ margin: 0.0625rem auto;
178
+ padding: 0;
179
+ border: none;
180
+ border-radius: calc(var(--radius) - 2px);
181
+ background: transparent;
182
+ color: var(--foreground);
183
+ font-size: 0.8125rem;
184
+ font-family: inherit;
185
+ cursor: pointer;
186
+ transition: background-color var(--duration-fast), color var(--duration-fast);
187
+ }
188
+
189
+ .sh-ui-calendar__day:hover:not(:disabled) {
190
+ background: var(--background-muted);
191
+ }
192
+
193
+ .sh-ui-calendar__day:focus-visible {
194
+ outline: var(--border-width-strong) solid var(--foreground);
195
+ outline-offset: 2px;
196
+ }
197
+
198
+ .sh-ui-calendar__day--outside {
199
+ color: var(--foreground-subtle, var(--foreground-muted));
200
+ opacity: 0.4;
201
+ }
202
+
203
+ .sh-ui-calendar__day--today {
204
+ font-weight: var(--weight-bold);
205
+ text-decoration: underline;
206
+ text-underline-offset: 0.125rem;
207
+ }
208
+
209
+ .sh-ui-calendar__day--selected {
210
+ background: var(--foreground);
211
+ color: var(--background);
212
+ font-weight: var(--weight-semibold);
213
+ }
214
+
215
+ .sh-ui-calendar__day--selected:hover:not(:disabled) {
216
+ background: var(--foreground);
217
+ opacity: 0.9;
218
+ }
219
+
220
+ /* ── Range ── */
221
+
222
+ .sh-ui-calendar__day--in-range {
223
+ background: color-mix(in srgb, var(--foreground) 10%, transparent);
224
+ border-radius: 0;
225
+ }
226
+
227
+ .sh-ui-calendar__day--in-range:hover:not(:disabled) {
228
+ background: color-mix(in srgb, var(--foreground) 18%, transparent);
229
+ }
230
+
231
+ .sh-ui-calendar__day--range-start {
232
+ background: var(--foreground);
233
+ color: var(--background);
234
+ font-weight: var(--weight-semibold);
235
+ border-radius: calc(var(--radius) - 2px) 0 0 calc(var(--radius) - 2px);
236
+ }
237
+
238
+ .sh-ui-calendar__day--range-end {
239
+ background: var(--foreground);
240
+ color: var(--background);
241
+ font-weight: var(--weight-semibold);
242
+ border-radius: 0 calc(var(--radius) - 2px) calc(var(--radius) - 2px) 0;
243
+ }
244
+
245
+ .sh-ui-calendar__day--range-start.sh-ui-calendar__day--range-end {
246
+ border-radius: calc(var(--radius) - 2px);
247
+ }
248
+
249
+ .sh-ui-calendar__day--range-start:hover:not(:disabled),
250
+ .sh-ui-calendar__day--range-end:hover:not(:disabled) {
251
+ background: var(--foreground);
252
+ opacity: 0.9;
253
+ }
254
+
255
+ /* ── Hint (range picker) ── */
256
+
257
+ .sh-ui-date-picker__hint {
258
+ margin: 0 0 var(--space-2);
259
+ font-size: var(--text-xs);
260
+ color: var(--foreground-muted);
261
+ text-align: center;
262
+ }
263
+
264
+ .sh-ui-calendar__day:disabled {
265
+ opacity: 0.3;
266
+ cursor: not-allowed;
267
+ }
268
+
269
+ /* ── Footer ── */
270
+
271
+ .sh-ui-date-picker__footer {
272
+ display: flex;
273
+ align-items: center;
274
+ justify-content: flex-end;
275
+ gap: var(--space-2);
276
+ margin-top: var(--space-2);
277
+ padding-top: var(--space-2);
278
+ border-top: 1px solid var(--border);
279
+ }
@@ -0,0 +1,97 @@
1
+ import * as React from "react";
2
+ import { Dialog as BaseDialog } from "@base-ui-components/react/dialog";
3
+ import "./styles.css";
4
+
5
+ type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
6
+
7
+ function cx(...args: (string | undefined | false)[]) {
8
+ return args.filter(Boolean).join(" ");
9
+ }
10
+
11
+ /**
12
+ * 모달 다이얼로그 루트. 열림 상태를 가지며 자식으로 Trigger·Content를 둔다.
13
+ * 주의를 강제하는 흐름(확인/입력)에 사용하고, 단순 안내는 Popover나 Toast를 권장.
14
+ */
15
+ export const Dialog = BaseDialog.Root;
16
+
17
+ /** Dialog를 여는 트리거. 보통 Button을 감싸 사용. */
18
+ export const DialogTrigger = BaseDialog.Trigger;
19
+
20
+ /** 클릭 시 Dialog를 닫는 요소. footer의 취소 버튼 등에 사용. */
21
+ export const DialogClose = BaseDialog.Close;
22
+
23
+ /** 우상단에 배치되는 X 닫기 버튼. `aria-label="닫기"`가 자동 부여된다. */
24
+ export function DialogCloseX({ className, children, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement>) {
25
+ return (
26
+ <BaseDialog.Close
27
+ className={cx("sh-ui-dialog__close", className)}
28
+ aria-label="닫기"
29
+ {...props}
30
+ >
31
+ {children ?? "×"}
32
+ </BaseDialog.Close>
33
+ );
34
+ }
35
+
36
+ /** Dialog 본문 하단의 액션 버튼 영역. 보통 [취소, 확인] 순서로 배치. */
37
+ export function DialogFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
38
+ return <div className={cx("sh-ui-dialog__footer", className)} {...props} />;
39
+ }
40
+
41
+ export interface DialogContentProps
42
+ extends WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseDialog.Popup>> {
43
+ /**
44
+ * Portal이 마운트될 DOM 노드. 모달이 다른 stacking context에 갇혀야 할 때 지정한다.
45
+ * @default document.body
46
+ */
47
+ container?: React.ComponentPropsWithoutRef<typeof BaseDialog.Portal>["container"];
48
+ }
49
+
50
+ /**
51
+ * Dialog의 실제 콘텐츠. Portal로 body 끝에 마운트되며 backdrop·focus trap·ESC 닫힘 등이 자동 처리된다.
52
+ * 접근성: 안에 반드시 `DialogTitle`을 두고, 추가 설명은 `DialogDescription`으로 연결할 것.
53
+ */
54
+ export const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps>(
55
+ function DialogContent({ className, children, container, ...props }, ref) {
56
+ return (
57
+ <BaseDialog.Portal container={container}>
58
+ <BaseDialog.Backdrop className="sh-ui-dialog__backdrop" />
59
+ <BaseDialog.Popup
60
+ ref={ref}
61
+ className={cx("sh-ui-dialog__content", className)}
62
+ {...props}
63
+ >
64
+ {children}
65
+ </BaseDialog.Popup>
66
+ </BaseDialog.Portal>
67
+ );
68
+ },
69
+ );
70
+
71
+ /** Dialog의 제목. 접근성을 위해 DialogContent 안에 항상 포함시킬 것. */
72
+ export const DialogTitle = React.forwardRef<
73
+ HTMLHeadingElement,
74
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseDialog.Title>>
75
+ >(function DialogTitle({ className, ...props }, ref) {
76
+ return (
77
+ <BaseDialog.Title
78
+ ref={ref}
79
+ className={cx("sh-ui-dialog__title", className)}
80
+ {...props}
81
+ />
82
+ );
83
+ });
84
+
85
+ /** Dialog의 보조 설명. 제목만으로 맥락이 부족할 때 사용한다. */
86
+ export const DialogDescription = React.forwardRef<
87
+ HTMLParagraphElement,
88
+ WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseDialog.Description>>
89
+ >(function DialogDescription({ className, ...props }, ref) {
90
+ return (
91
+ <BaseDialog.Description
92
+ ref={ref}
93
+ className={cx("sh-ui-dialog__description", className)}
94
+ {...props}
95
+ />
96
+ );
97
+ });
@@ -0,0 +1,127 @@
1
+ /* ── Backdrop ── */
2
+
3
+ .sh-ui-dialog__backdrop {
4
+ position: fixed;
5
+ inset: 0;
6
+ z-index: var(--z-overlay);
7
+ background: rgba(0, 0, 0, 0.25);
8
+ backdrop-filter: blur(8px);
9
+ transition: opacity var(--duration-slow) ease;
10
+ }
11
+
12
+ .sh-ui-dialog__backdrop[data-starting-style],
13
+ .sh-ui-dialog__backdrop[data-ending-style] {
14
+ opacity: 0;
15
+ }
16
+
17
+ /* ── Content (Popup) ── */
18
+
19
+ .sh-ui-dialog__content {
20
+ position: fixed;
21
+ top: 50%;
22
+ left: 50%;
23
+ transform: translate(-50%, -50%);
24
+ z-index: var(--z-modal);
25
+ display: flex;
26
+ flex-direction: column;
27
+ width: calc(100% - 2rem);
28
+ max-width: 28rem;
29
+ max-height: calc(100dvh - 4rem);
30
+ padding: var(--space-6);
31
+ background: var(--background);
32
+ color: var(--foreground);
33
+ border: 1px solid var(--border);
34
+ border-radius: var(--radius);
35
+ box-shadow: var(--shadow-xl);
36
+ outline: none;
37
+ overflow-y: auto;
38
+ transition:
39
+ opacity var(--duration-slow) ease,
40
+ transform var(--duration-slow) ease;
41
+ }
42
+
43
+ .sh-ui-dialog__content[data-starting-style] {
44
+ opacity: 0;
45
+ transform: translate(-50%, calc(-50% + 0.5rem)) scale(0.97);
46
+ }
47
+
48
+ .sh-ui-dialog__content[data-ending-style] {
49
+ opacity: 0;
50
+ transform: translate(-50%, calc(-50% + 0.25rem)) scale(0.98);
51
+ }
52
+
53
+ .sh-ui-dialog__content:focus-visible {
54
+ outline: var(--border-width-strong) solid var(--foreground);
55
+ outline-offset: 2px;
56
+ }
57
+
58
+ /* ── Title ── */
59
+
60
+ .sh-ui-dialog__title {
61
+ margin: 0 0 var(--space-1);
62
+ font-weight: var(--weight-semibold);
63
+ font-size: var(--text-lg);
64
+ line-height: 1.4;
65
+ }
66
+
67
+ /* ── Description ── */
68
+
69
+ .sh-ui-dialog__description {
70
+ margin: 0 0 var(--space-5);
71
+ color: var(--foreground-muted);
72
+ font-size: var(--text-sm);
73
+ line-height: 1.5;
74
+ }
75
+
76
+ /* ── Footer (액션 버튼 영역) ── */
77
+
78
+ .sh-ui-dialog__footer {
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: flex-end;
82
+ gap: var(--space-2);
83
+ padding-top: var(--space-4);
84
+ border-top: 1px solid var(--border);
85
+ margin-top: auto;
86
+ }
87
+
88
+ /* ── Close (× 버튼) ── */
89
+
90
+ .sh-ui-dialog__close {
91
+ position: absolute;
92
+ top: var(--space-3);
93
+ right: var(--space-3);
94
+ display: inline-flex;
95
+ align-items: center;
96
+ justify-content: center;
97
+ width: 2rem;
98
+ height: 2rem;
99
+ border: 0;
100
+ border-radius: calc(var(--radius) - 2px);
101
+ background: transparent;
102
+ color: var(--foreground-muted);
103
+ font-size: var(--text-lg);
104
+ line-height: 1;
105
+ cursor: pointer;
106
+ transition: background-color var(--duration-fast), color var(--duration-fast);
107
+ }
108
+ .sh-ui-dialog__close:hover {
109
+ background: var(--background-muted);
110
+ color: var(--foreground);
111
+ }
112
+ .sh-ui-dialog__close:focus-visible {
113
+ outline: var(--border-width-strong) solid var(--foreground);
114
+ outline-offset: 2px;
115
+ }
116
+
117
+ @media (prefers-reduced-motion: reduce) {
118
+ .sh-ui-dialog__backdrop,
119
+ .sh-ui-dialog__content,
120
+ .sh-ui-dialog__close {
121
+ transition: none;
122
+ }
123
+ .sh-ui-dialog__content[data-starting-style],
124
+ .sh-ui-dialog__content[data-ending-style] {
125
+ transform: translate(-50%, -50%);
126
+ }
127
+ }