@synthaxai/ui 1.0.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 (185) hide show
  1. package/README.md +262 -0
  2. package/dist/app.css +2 -0
  3. package/dist/app.html +12 -0
  4. package/dist/data-display/DataTable/DataTable.svelte +773 -0
  5. package/dist/data-display/DataTable/DataTable.svelte.d.ts +120 -0
  6. package/dist/data-display/DataTable/DataTable.svelte.d.ts.map +1 -0
  7. package/dist/data-display/DataTable/index.d.ts +2 -0
  8. package/dist/data-display/DataTable/index.d.ts.map +1 -0
  9. package/dist/data-display/DataTable/index.js +1 -0
  10. package/dist/data-display/StatCard/StatCard.svelte +409 -0
  11. package/dist/data-display/StatCard/StatCard.svelte.d.ts +63 -0
  12. package/dist/data-display/StatCard/StatCard.svelte.d.ts.map +1 -0
  13. package/dist/data-display/StatCard/index.d.ts +2 -0
  14. package/dist/data-display/StatCard/index.d.ts.map +1 -0
  15. package/dist/data-display/StatCard/index.js +1 -0
  16. package/dist/data-display/index.d.ts +8 -0
  17. package/dist/data-display/index.d.ts.map +1 -0
  18. package/dist/data-display/index.js +7 -0
  19. package/dist/dialogs/ConfirmDialog/ConfirmDialog.svelte +693 -0
  20. package/dist/dialogs/ConfirmDialog/ConfirmDialog.svelte.d.ts +66 -0
  21. package/dist/dialogs/ConfirmDialog/ConfirmDialog.svelte.d.ts.map +1 -0
  22. package/dist/dialogs/ConfirmDialog/index.d.ts +2 -0
  23. package/dist/dialogs/ConfirmDialog/index.d.ts.map +1 -0
  24. package/dist/dialogs/ConfirmDialog/index.js +1 -0
  25. package/dist/dialogs/Modal/Modal.svelte +441 -0
  26. package/dist/dialogs/Modal/Modal.svelte.d.ts +69 -0
  27. package/dist/dialogs/Modal/Modal.svelte.d.ts.map +1 -0
  28. package/dist/dialogs/Modal/index.d.ts +2 -0
  29. package/dist/dialogs/Modal/index.d.ts.map +1 -0
  30. package/dist/dialogs/Modal/index.js +1 -0
  31. package/dist/dialogs/index.d.ts +8 -0
  32. package/dist/dialogs/index.d.ts.map +1 -0
  33. package/dist/dialogs/index.js +7 -0
  34. package/dist/feedback/Alert/Alert.svelte +565 -0
  35. package/dist/feedback/Alert/Alert.svelte.d.ts +60 -0
  36. package/dist/feedback/Alert/Alert.svelte.d.ts.map +1 -0
  37. package/dist/feedback/Alert/index.d.ts +2 -0
  38. package/dist/feedback/Alert/index.d.ts.map +1 -0
  39. package/dist/feedback/Alert/index.js +1 -0
  40. package/dist/feedback/EmptyState/EmptyState.svelte +377 -0
  41. package/dist/feedback/EmptyState/EmptyState.svelte.d.ts +63 -0
  42. package/dist/feedback/EmptyState/EmptyState.svelte.d.ts.map +1 -0
  43. package/dist/feedback/EmptyState/index.d.ts +2 -0
  44. package/dist/feedback/EmptyState/index.d.ts.map +1 -0
  45. package/dist/feedback/EmptyState/index.js +1 -0
  46. package/dist/feedback/ProgressBar/ProgressBar.svelte +585 -0
  47. package/dist/feedback/ProgressBar/ProgressBar.svelte.d.ts +68 -0
  48. package/dist/feedback/ProgressBar/ProgressBar.svelte.d.ts.map +1 -0
  49. package/dist/feedback/ProgressBar/index.d.ts +2 -0
  50. package/dist/feedback/ProgressBar/index.d.ts.map +1 -0
  51. package/dist/feedback/ProgressBar/index.js +1 -0
  52. package/dist/feedback/Skeleton/Skeleton.svelte +568 -0
  53. package/dist/feedback/Skeleton/Skeleton.svelte.d.ts +54 -0
  54. package/dist/feedback/Skeleton/Skeleton.svelte.d.ts.map +1 -0
  55. package/dist/feedback/Skeleton/index.d.ts +2 -0
  56. package/dist/feedback/Skeleton/index.d.ts.map +1 -0
  57. package/dist/feedback/Skeleton/index.js +1 -0
  58. package/dist/feedback/Spinner/Spinner.svelte +434 -0
  59. package/dist/feedback/Spinner/Spinner.svelte.d.ts +49 -0
  60. package/dist/feedback/Spinner/Spinner.svelte.d.ts.map +1 -0
  61. package/dist/feedback/Spinner/index.d.ts +2 -0
  62. package/dist/feedback/Spinner/index.d.ts.map +1 -0
  63. package/dist/feedback/Spinner/index.js +1 -0
  64. package/dist/feedback/Toast/Toast.svelte +587 -0
  65. package/dist/feedback/Toast/Toast.svelte.d.ts +55 -0
  66. package/dist/feedback/Toast/Toast.svelte.d.ts.map +1 -0
  67. package/dist/feedback/Toast/ToastContainer.svelte +168 -0
  68. package/dist/feedback/Toast/ToastContainer.svelte.d.ts +28 -0
  69. package/dist/feedback/Toast/ToastContainer.svelte.d.ts.map +1 -0
  70. package/dist/feedback/Toast/index.d.ts +4 -0
  71. package/dist/feedback/Toast/index.d.ts.map +1 -0
  72. package/dist/feedback/Toast/index.js +3 -0
  73. package/dist/feedback/Toast/toast-store.d.ts +72 -0
  74. package/dist/feedback/Toast/toast-store.d.ts.map +1 -0
  75. package/dist/feedback/Toast/toast-store.js +157 -0
  76. package/dist/feedback/index.d.ts +13 -0
  77. package/dist/feedback/index.d.ts.map +1 -0
  78. package/dist/feedback/index.js +12 -0
  79. package/dist/forms/Checkbox/Checkbox.svelte +404 -0
  80. package/dist/forms/Checkbox/Checkbox.svelte.d.ts +62 -0
  81. package/dist/forms/Checkbox/Checkbox.svelte.d.ts.map +1 -0
  82. package/dist/forms/Checkbox/index.d.ts +2 -0
  83. package/dist/forms/Checkbox/index.d.ts.map +1 -0
  84. package/dist/forms/Checkbox/index.js +1 -0
  85. package/dist/forms/FormField/FormField.svelte +299 -0
  86. package/dist/forms/FormField/FormField.svelte.d.ts +43 -0
  87. package/dist/forms/FormField/FormField.svelte.d.ts.map +1 -0
  88. package/dist/forms/FormField/index.d.ts +2 -0
  89. package/dist/forms/FormField/index.d.ts.map +1 -0
  90. package/dist/forms/FormField/index.js +1 -0
  91. package/dist/forms/RadioGroup/RadioGroup.svelte +418 -0
  92. package/dist/forms/RadioGroup/RadioGroup.svelte.d.ts +70 -0
  93. package/dist/forms/RadioGroup/RadioGroup.svelte.d.ts.map +1 -0
  94. package/dist/forms/RadioGroup/index.d.ts +2 -0
  95. package/dist/forms/RadioGroup/index.d.ts.map +1 -0
  96. package/dist/forms/RadioGroup/index.js +1 -0
  97. package/dist/forms/Select/Select.svelte +548 -0
  98. package/dist/forms/Select/Select.svelte.d.ts +74 -0
  99. package/dist/forms/Select/Select.svelte.d.ts.map +1 -0
  100. package/dist/forms/Select/index.d.ts +2 -0
  101. package/dist/forms/Select/index.d.ts.map +1 -0
  102. package/dist/forms/Select/index.js +1 -0
  103. package/dist/forms/TextInput/TextInput.svelte +628 -0
  104. package/dist/forms/TextInput/TextInput.svelte.d.ts +97 -0
  105. package/dist/forms/TextInput/TextInput.svelte.d.ts.map +1 -0
  106. package/dist/forms/TextInput/index.d.ts +2 -0
  107. package/dist/forms/TextInput/index.d.ts.map +1 -0
  108. package/dist/forms/TextInput/index.js +1 -0
  109. package/dist/forms/Textarea/Textarea.svelte +587 -0
  110. package/dist/forms/Textarea/Textarea.svelte.d.ts +71 -0
  111. package/dist/forms/Textarea/Textarea.svelte.d.ts.map +1 -0
  112. package/dist/forms/Textarea/index.d.ts +2 -0
  113. package/dist/forms/Textarea/index.d.ts.map +1 -0
  114. package/dist/forms/Textarea/index.js +1 -0
  115. package/dist/forms/index.d.ts +13 -0
  116. package/dist/forms/index.d.ts.map +1 -0
  117. package/dist/forms/index.js +12 -0
  118. package/dist/index.d.ts +37 -0
  119. package/dist/index.d.ts.map +1 -0
  120. package/dist/index.js +65 -0
  121. package/dist/layout/Card/Card.svelte +316 -0
  122. package/dist/layout/Card/Card.svelte.d.ts +63 -0
  123. package/dist/layout/Card/Card.svelte.d.ts.map +1 -0
  124. package/dist/layout/Card/index.d.ts +2 -0
  125. package/dist/layout/Card/index.d.ts.map +1 -0
  126. package/dist/layout/Card/index.js +1 -0
  127. package/dist/layout/Container/Container.svelte +252 -0
  128. package/dist/layout/Container/Container.svelte.d.ts +50 -0
  129. package/dist/layout/Container/Container.svelte.d.ts.map +1 -0
  130. package/dist/layout/Container/index.d.ts +2 -0
  131. package/dist/layout/Container/index.d.ts.map +1 -0
  132. package/dist/layout/Container/index.js +1 -0
  133. package/dist/layout/index.d.ts +8 -0
  134. package/dist/layout/index.d.ts.map +1 -0
  135. package/dist/layout/index.js +7 -0
  136. package/dist/navigation/StepIndicator/StepIndicator.svelte +601 -0
  137. package/dist/navigation/StepIndicator/StepIndicator.svelte.d.ts +70 -0
  138. package/dist/navigation/StepIndicator/StepIndicator.svelte.d.ts.map +1 -0
  139. package/dist/navigation/StepIndicator/index.d.ts +2 -0
  140. package/dist/navigation/StepIndicator/index.d.ts.map +1 -0
  141. package/dist/navigation/StepIndicator/index.js +1 -0
  142. package/dist/navigation/index.d.ts +7 -0
  143. package/dist/navigation/index.d.ts.map +1 -0
  144. package/dist/navigation/index.js +6 -0
  145. package/dist/primitives/Badge/Badge.svelte +365 -0
  146. package/dist/primitives/Badge/Badge.svelte.d.ts +39 -0
  147. package/dist/primitives/Badge/Badge.svelte.d.ts.map +1 -0
  148. package/dist/primitives/Badge/index.d.ts +2 -0
  149. package/dist/primitives/Badge/index.d.ts.map +1 -0
  150. package/dist/primitives/Badge/index.js +1 -0
  151. package/dist/primitives/Button/Button.svelte +430 -0
  152. package/dist/primitives/Button/Button.svelte.d.ts +50 -0
  153. package/dist/primitives/Button/Button.svelte.d.ts.map +1 -0
  154. package/dist/primitives/Button/index.d.ts +2 -0
  155. package/dist/primitives/Button/index.d.ts.map +1 -0
  156. package/dist/primitives/Button/index.js +1 -0
  157. package/dist/primitives/index.d.ts +9 -0
  158. package/dist/primitives/index.d.ts.map +1 -0
  159. package/dist/primitives/index.js +8 -0
  160. package/dist/routes/+layout.svelte +12 -0
  161. package/dist/routes/+layout.svelte.d.ts +12 -0
  162. package/dist/routes/+layout.svelte.d.ts.map +1 -0
  163. package/dist/routes/+page.svelte +53 -0
  164. package/dist/routes/+page.svelte.d.ts +27 -0
  165. package/dist/routes/+page.svelte.d.ts.map +1 -0
  166. package/dist/styles/tokens.css +399 -0
  167. package/dist/types/index.d.ts +175 -0
  168. package/dist/types/index.d.ts.map +1 -0
  169. package/dist/types/index.js +7 -0
  170. package/dist/utils/accessibility.d.ts +103 -0
  171. package/dist/utils/accessibility.d.ts.map +1 -0
  172. package/dist/utils/accessibility.js +202 -0
  173. package/dist/utils/cn.d.ts +71 -0
  174. package/dist/utils/cn.d.ts.map +1 -0
  175. package/dist/utils/cn.js +61 -0
  176. package/dist/utils/form-styles.d.ts +76 -0
  177. package/dist/utils/form-styles.d.ts.map +1 -0
  178. package/dist/utils/form-styles.js +95 -0
  179. package/dist/utils/index.d.ts +10 -0
  180. package/dist/utils/index.d.ts.map +1 -0
  181. package/dist/utils/index.js +13 -0
  182. package/dist/utils/keyboard.d.ts +94 -0
  183. package/dist/utils/keyboard.d.ts.map +1 -0
  184. package/dist/utils/keyboard.js +179 -0
  185. package/package.json +119 -0
@@ -0,0 +1,693 @@
1
+ <!--
2
+ @component ConfirmDialog
3
+
4
+ A world-class confirmation dialog for destructive or important actions in healthcare apps.
5
+ Features proper focus management, semantic variants, and full accessibility.
6
+
7
+ Features:
8
+ - Multiple variants (default, danger, warning, success)
9
+ - Focus management per WCAG guidelines
10
+ - Loading state with spinner
11
+ - Error display
12
+ - Optional destructive confirmation (type to confirm)
13
+ - Custom content support
14
+ - Role-based semantics (dialog vs alertdialog)
15
+
16
+ @example
17
+ <ConfirmDialog
18
+ open={showConfirm}
19
+ title="Delete Patient Record"
20
+ message="This action cannot be undone. Are you sure you want to delete this patient record?"
21
+ variant="danger"
22
+ confirmText="Delete"
23
+ onconfirm={handleDelete}
24
+ oncancel={() => showConfirm = false}
25
+ />
26
+ -->
27
+ <script lang="ts">
28
+ import type { Snippet } from 'svelte';
29
+ import { X, AlertTriangle, Info, CheckCircle, AlertCircle, Loader2 } from 'lucide-svelte';
30
+ import { createFocusTrap, Keys, generateId } from '../../utils/keyboard.js';
31
+
32
+ type DialogVariant = 'default' | 'danger' | 'warning' | 'success';
33
+
34
+ interface Props {
35
+ /** Whether the dialog is open */
36
+ open?: boolean;
37
+ /** Dialog title */
38
+ title?: string;
39
+ /** Dialog message */
40
+ message?: string;
41
+ /** Text for confirm button */
42
+ confirmText?: string;
43
+ /** Text for cancel button */
44
+ cancelText?: string;
45
+ /** Visual variant */
46
+ variant?: DialogVariant;
47
+ /** Whether the dialog is in loading state */
48
+ loading?: boolean;
49
+ /** Error message to display */
50
+ error?: string;
51
+ /** Focus confirm button on open (default: focus cancel for destructive actions) */
52
+ focusConfirm?: boolean;
53
+ /** Show close button in header */
54
+ showCloseButton?: boolean;
55
+ /** Require typing confirmation text for destructive actions */
56
+ requireConfirmation?: string;
57
+ /** Custom content instead of message */
58
+ children?: Snippet;
59
+ /** Custom icon snippet */
60
+ icon?: Snippet;
61
+ /** Additional CSS classes */
62
+ class?: string;
63
+ /** Confirm handler */
64
+ onconfirm?: () => void;
65
+ /** Cancel handler */
66
+ oncancel?: () => void;
67
+ }
68
+
69
+ let {
70
+ open = false,
71
+ title = 'Confirm',
72
+ message = 'Are you sure you want to proceed?',
73
+ confirmText = 'Confirm',
74
+ cancelText = 'Cancel',
75
+ variant = 'default',
76
+ loading = false,
77
+ error = '',
78
+ focusConfirm = false,
79
+ showCloseButton = true,
80
+ requireConfirmation = '',
81
+ children,
82
+ icon,
83
+ class: className = '',
84
+ onconfirm,
85
+ oncancel
86
+ }: Props = $props();
87
+
88
+ const dialogId = generateId('confirm-dialog');
89
+ const titleId = `${dialogId}-title`;
90
+ const descriptionId = `${dialogId}-description`;
91
+
92
+ let dialogElement = $state<HTMLDivElement | null>(null);
93
+ let cancelButtonElement = $state<HTMLButtonElement | null>(null);
94
+ let confirmButtonElement = $state<HTMLButtonElement | null>(null);
95
+ let confirmationInput = $state('');
96
+ let focusTrap: ReturnType<typeof createFocusTrap> | null = null;
97
+ let previouslyFocusedElement: HTMLElement | null = null;
98
+
99
+ // Check if confirmation is valid
100
+ const isConfirmationValid = $derived(
101
+ !requireConfirmation || confirmationInput === requireConfirmation
102
+ );
103
+
104
+ // Determine initial focus based on variant and props
105
+ const shouldFocusConfirm = $derived(
106
+ focusConfirm || (variant !== 'danger' && variant !== 'warning')
107
+ );
108
+
109
+ // Icon config based on variant
110
+ const iconConfig: Record<DialogVariant, { icon: typeof AlertTriangle; colorClass: string }> = {
111
+ default: { icon: Info, colorClass: 'confirm-dialog-icon-info' },
112
+ danger: { icon: AlertTriangle, colorClass: 'confirm-dialog-icon-danger' },
113
+ warning: { icon: AlertCircle, colorClass: 'confirm-dialog-icon-warning' },
114
+ success: { icon: CheckCircle, colorClass: 'confirm-dialog-icon-success' }
115
+ };
116
+
117
+ // Calculate scrollbar width to prevent layout shift
118
+ function getScrollbarWidth(): number {
119
+ return window.innerWidth - document.documentElement.clientWidth;
120
+ }
121
+
122
+ // Handle focus trap and focus management
123
+ $effect(() => {
124
+ if (open && dialogElement) {
125
+ previouslyFocusedElement = document.activeElement as HTMLElement;
126
+ confirmationInput = ''; // Reset on open
127
+
128
+ focusTrap = createFocusTrap(dialogElement);
129
+
130
+ // Lock body scroll and prevent layout shift
131
+ const scrollbarWidth = getScrollbarWidth();
132
+ document.body.style.overflow = 'hidden';
133
+ if (scrollbarWidth > 0) {
134
+ document.body.style.paddingRight = `${scrollbarWidth}px`;
135
+ }
136
+
137
+ // Focus appropriate button
138
+ setTimeout(() => {
139
+ if (requireConfirmation) {
140
+ // Focus the input for destructive confirmation
141
+ dialogElement?.querySelector('input')?.focus();
142
+ } else if (shouldFocusConfirm) {
143
+ confirmButtonElement?.focus();
144
+ } else {
145
+ cancelButtonElement?.focus();
146
+ }
147
+ }, 0);
148
+ }
149
+
150
+ return () => {
151
+ focusTrap?.deactivate();
152
+ document.body.style.overflow = '';
153
+ document.body.style.paddingRight = '';
154
+
155
+ if (previouslyFocusedElement?.focus) {
156
+ setTimeout(() => {
157
+ previouslyFocusedElement?.focus();
158
+ }, 0);
159
+ }
160
+ };
161
+ });
162
+
163
+ function handleConfirm() {
164
+ if (isConfirmationValid && !loading) {
165
+ onconfirm?.();
166
+ }
167
+ }
168
+
169
+ function handleCancel() {
170
+ if (!loading) {
171
+ oncancel?.();
172
+ }
173
+ }
174
+
175
+ function handleBackdropClick(e: MouseEvent) {
176
+ if (e.target === e.currentTarget && !loading) {
177
+ handleCancel();
178
+ }
179
+ }
180
+
181
+ function handleKeyDown(e: KeyboardEvent) {
182
+ if (e.key === Keys.Escape && !loading) {
183
+ e.preventDefault();
184
+ handleCancel();
185
+ }
186
+
187
+ // Focus trap
188
+ if (e.key === Keys.Tab && dialogElement) {
189
+ const focusable = dialogElement.querySelectorAll<HTMLElement>(
190
+ 'button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex="-1"])'
191
+ );
192
+ const first = focusable[0];
193
+ const last = focusable[focusable.length - 1];
194
+
195
+ if (e.shiftKey && document.activeElement === first) {
196
+ e.preventDefault();
197
+ last?.focus();
198
+ } else if (!e.shiftKey && document.activeElement === last) {
199
+ e.preventDefault();
200
+ first?.focus();
201
+ }
202
+ }
203
+ }
204
+ </script>
205
+
206
+ <svelte:window onkeydown={open ? handleKeyDown : undefined} />
207
+
208
+ {#if open}
209
+ <!-- Backdrop -->
210
+ <div
211
+ class="confirm-dialog-backdrop"
212
+ role="presentation"
213
+ onclick={handleBackdropClick}
214
+ onkeydown={() => {}}
215
+ >
216
+ <!-- Dialog -->
217
+ <div
218
+ bind:this={dialogElement}
219
+ id={dialogId}
220
+ class="confirm-dialog {className}"
221
+ role={variant === 'danger' || variant === 'warning' ? 'alertdialog' : 'dialog'}
222
+ aria-modal="true"
223
+ aria-labelledby={titleId}
224
+ aria-describedby={descriptionId}
225
+ >
226
+ <!-- Header -->
227
+ <div class="confirm-dialog-header">
228
+ <div class="confirm-dialog-header-content">
229
+ {#if icon}
230
+ <span class="confirm-dialog-icon {iconConfig[variant].colorClass}">
231
+ {@render icon()}
232
+ </span>
233
+ {:else}
234
+ {@const IconComponent = iconConfig[variant].icon}
235
+ <span class="confirm-dialog-icon {iconConfig[variant].colorClass}">
236
+ <IconComponent size={20} aria-hidden="true" />
237
+ </span>
238
+ {/if}
239
+ <h2 id={titleId} class="confirm-dialog-title">
240
+ {title}
241
+ </h2>
242
+ </div>
243
+
244
+ {#if showCloseButton && !loading}
245
+ <button
246
+ type="button"
247
+ class="confirm-dialog-close-btn"
248
+ onclick={handleCancel}
249
+ aria-label="Close"
250
+ >
251
+ <X size={18} aria-hidden="true" />
252
+ </button>
253
+ {/if}
254
+ </div>
255
+
256
+ <!-- Body -->
257
+ <div class="confirm-dialog-body">
258
+ <div id={descriptionId}>
259
+ {#if children}
260
+ {@render children()}
261
+ {:else}
262
+ <p class="confirm-dialog-message">{message}</p>
263
+ {/if}
264
+ </div>
265
+
266
+ {#if requireConfirmation}
267
+ <div class="confirm-dialog-confirmation">
268
+ <label for="{dialogId}-confirm-input" class="confirm-dialog-confirmation-label">
269
+ Type <strong>{requireConfirmation}</strong> to confirm:
270
+ </label>
271
+ <input
272
+ id="{dialogId}-confirm-input"
273
+ type="text"
274
+ class="confirm-dialog-confirmation-input"
275
+ bind:value={confirmationInput}
276
+ placeholder={requireConfirmation}
277
+ disabled={loading}
278
+ autocomplete="off"
279
+ />
280
+ </div>
281
+ {/if}
282
+
283
+ {#if error}
284
+ <div class="confirm-dialog-error" role="alert">
285
+ <p>{error}</p>
286
+ </div>
287
+ {/if}
288
+ </div>
289
+
290
+ <!-- Footer -->
291
+ <div class="confirm-dialog-footer">
292
+ <button
293
+ bind:this={cancelButtonElement}
294
+ type="button"
295
+ class="confirm-dialog-btn confirm-dialog-btn-secondary"
296
+ onclick={handleCancel}
297
+ disabled={loading}
298
+ >
299
+ {cancelText}
300
+ </button>
301
+
302
+ <button
303
+ bind:this={confirmButtonElement}
304
+ type="button"
305
+ class="confirm-dialog-btn confirm-dialog-btn-{variant}"
306
+ onclick={handleConfirm}
307
+ disabled={loading || !isConfirmationValid}
308
+ >
309
+ {#if loading}
310
+ <Loader2 size={16} class="confirm-dialog-spinner" aria-hidden="true" />
311
+ <span>Please wait...</span>
312
+ {:else}
313
+ {confirmText}
314
+ {/if}
315
+ </button>
316
+ </div>
317
+ </div>
318
+ </div>
319
+ {/if}
320
+
321
+ <style>
322
+ /* ========================================
323
+ BACKDROP
324
+ ======================================== */
325
+ .confirm-dialog-backdrop {
326
+ position: fixed;
327
+ inset: 0;
328
+ z-index: 1000;
329
+ display: flex;
330
+ align-items: center;
331
+ justify-content: center;
332
+ padding: 1rem;
333
+ background-color: var(--ui-backdrop);
334
+ backdrop-filter: blur(4px);
335
+ -webkit-backdrop-filter: blur(4px);
336
+ animation: confirm-fade-in 0.2s ease-out;
337
+ }
338
+
339
+ @keyframes confirm-fade-in {
340
+ from { opacity: 0; }
341
+ to { opacity: 1; }
342
+ }
343
+
344
+ /* ========================================
345
+ DIALOG CONTAINER
346
+ ======================================== */
347
+ .confirm-dialog {
348
+ width: 100%;
349
+ max-width: 26rem;
350
+ background-color: var(--ui-bg-primary);
351
+ border: 1px solid var(--ui-border-default);
352
+ border-radius: 1rem;
353
+ box-shadow:
354
+ 0 20px 25px -5px rgb(0 0 0 / 0.1),
355
+ 0 8px 10px -6px rgb(0 0 0 / 0.1);
356
+ animation: confirm-scale-in 0.3s ease-out;
357
+ overflow: hidden;
358
+ }
359
+
360
+ @keyframes confirm-scale-in {
361
+ from {
362
+ opacity: 0;
363
+ transform: scale(0.95) translateY(10px);
364
+ }
365
+ to {
366
+ opacity: 1;
367
+ transform: scale(1) translateY(0);
368
+ }
369
+ }
370
+
371
+ /* ========================================
372
+ HEADER
373
+ ======================================== */
374
+ .confirm-dialog-header {
375
+ display: flex;
376
+ align-items: center;
377
+ justify-content: space-between;
378
+ gap: 1rem;
379
+ padding: 1rem 1.5rem;
380
+ border-bottom: 1px solid var(--ui-border-default);
381
+ background-color: var(--ui-bg-secondary);
382
+ }
383
+
384
+ .confirm-dialog-header-content {
385
+ display: flex;
386
+ align-items: center;
387
+ gap: 0.75rem;
388
+ min-width: 0;
389
+ }
390
+
391
+ .confirm-dialog-icon {
392
+ display: flex;
393
+ align-items: center;
394
+ justify-content: center;
395
+ flex-shrink: 0;
396
+ }
397
+
398
+ .confirm-dialog-icon-info {
399
+ color: rgb(var(--ui-color-info));
400
+ }
401
+
402
+ .confirm-dialog-icon-danger {
403
+ color: rgb(var(--ui-color-error));
404
+ }
405
+
406
+ .confirm-dialog-icon-warning {
407
+ color: rgb(var(--ui-color-warning));
408
+ }
409
+
410
+ .confirm-dialog-icon-success {
411
+ color: rgb(var(--ui-color-success));
412
+ }
413
+
414
+ .confirm-dialog-title {
415
+ margin: 0;
416
+ font-size: 1rem;
417
+ font-weight: 600;
418
+ color: var(--ui-text-primary);
419
+ line-height: 1.4;
420
+ }
421
+
422
+ /* ========================================
423
+ CLOSE BUTTON
424
+ ======================================== */
425
+ .confirm-dialog-close-btn {
426
+ display: flex;
427
+ align-items: center;
428
+ justify-content: center;
429
+ padding: 0.375rem;
430
+ background: transparent;
431
+ border: none;
432
+ border-radius: 0.5rem;
433
+ color: var(--ui-text-tertiary);
434
+ cursor: pointer;
435
+ transition: all 0.15s ease;
436
+ flex-shrink: 0;
437
+ }
438
+
439
+ .confirm-dialog-close-btn:hover {
440
+ background-color: var(--ui-bg-tertiary);
441
+ color: var(--ui-text-primary);
442
+ }
443
+
444
+ .confirm-dialog-close-btn:focus-visible {
445
+ outline: none;
446
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-primary) / 0.4);
447
+ }
448
+
449
+ /* ========================================
450
+ BODY
451
+ ======================================== */
452
+ .confirm-dialog-body {
453
+ padding: 1.25rem 1.5rem;
454
+ }
455
+
456
+ .confirm-dialog-message {
457
+ margin: 0;
458
+ font-size: 0.875rem;
459
+ line-height: 1.6;
460
+ color: var(--ui-text-secondary);
461
+ }
462
+
463
+ /* ========================================
464
+ CONFIRMATION INPUT
465
+ ======================================== */
466
+ .confirm-dialog-confirmation {
467
+ margin-top: 1rem;
468
+ }
469
+
470
+ .confirm-dialog-confirmation-label {
471
+ display: block;
472
+ margin-bottom: 0.5rem;
473
+ font-size: 0.8125rem;
474
+ color: var(--ui-text-secondary);
475
+ }
476
+
477
+ .confirm-dialog-confirmation-label strong {
478
+ color: var(--ui-text-primary);
479
+ font-family: monospace;
480
+ background-color: var(--ui-bg-tertiary);
481
+ padding: 0.125rem 0.375rem;
482
+ border-radius: 0.25rem;
483
+ }
484
+
485
+ .confirm-dialog-confirmation-input {
486
+ width: 100%;
487
+ padding: 0.625rem 0.875rem;
488
+ font-size: 0.875rem;
489
+ font-family: monospace;
490
+ color: var(--ui-text-primary);
491
+ background-color: var(--ui-bg-primary);
492
+ border: 1px solid var(--ui-border-default);
493
+ border-radius: 0.5rem;
494
+ transition: all 0.15s ease;
495
+ }
496
+
497
+ .confirm-dialog-confirmation-input:focus {
498
+ outline: none;
499
+ border-color: rgb(var(--ui-color-primary));
500
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-primary) / 0.1);
501
+ }
502
+
503
+ .confirm-dialog-confirmation-input:disabled {
504
+ opacity: 0.6;
505
+ cursor: not-allowed;
506
+ }
507
+
508
+ /* ========================================
509
+ ERROR
510
+ ======================================== */
511
+ .confirm-dialog-error {
512
+ margin-top: 1rem;
513
+ padding: 0.75rem 1rem;
514
+ background-color: rgb(var(--ui-color-error) / 0.1);
515
+ border: 1px solid rgb(var(--ui-color-error) / 0.2);
516
+ border-radius: 0.5rem;
517
+ }
518
+
519
+ .confirm-dialog-error p {
520
+ margin: 0;
521
+ font-size: 0.8125rem;
522
+ color: rgb(var(--ui-color-error));
523
+ }
524
+
525
+ :global([data-theme='dark']) .confirm-dialog-error p {
526
+ color: rgb(var(--ui-color-error-light));
527
+ }
528
+
529
+ @media (prefers-color-scheme: dark) {
530
+ :global(:root:not([data-theme='light'])) .confirm-dialog-error p {
531
+ color: rgb(var(--ui-color-error-light));
532
+ }
533
+ }
534
+
535
+ /* ========================================
536
+ FOOTER
537
+ ======================================== */
538
+ .confirm-dialog-footer {
539
+ display: flex;
540
+ align-items: center;
541
+ justify-content: flex-end;
542
+ gap: 0.75rem;
543
+ padding: 1rem 1.5rem;
544
+ border-top: 1px solid var(--ui-border-default);
545
+ background-color: var(--ui-bg-secondary);
546
+ }
547
+
548
+ /* ========================================
549
+ BUTTONS
550
+ ======================================== */
551
+ .confirm-dialog-btn {
552
+ display: inline-flex;
553
+ align-items: center;
554
+ justify-content: center;
555
+ gap: 0.5rem;
556
+ padding: 0.625rem 1.25rem;
557
+ font-size: 0.875rem;
558
+ font-weight: 500;
559
+ border-radius: 0.5rem;
560
+ cursor: pointer;
561
+ transition: all 0.15s ease;
562
+ border: none;
563
+ }
564
+
565
+ .confirm-dialog-btn:disabled {
566
+ opacity: 0.6;
567
+ cursor: not-allowed;
568
+ }
569
+
570
+ /* Secondary button */
571
+ .confirm-dialog-btn-secondary {
572
+ background-color: var(--ui-bg-tertiary);
573
+ color: var(--ui-text-primary);
574
+ border: 1px solid var(--ui-border-default);
575
+ }
576
+
577
+ .confirm-dialog-btn-secondary:hover:not(:disabled) {
578
+ background-color: var(--ui-bg-secondary);
579
+ border-color: var(--ui-border-hover);
580
+ }
581
+
582
+ .confirm-dialog-btn-secondary:focus-visible {
583
+ outline: none;
584
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-primary) / 0.4);
585
+ }
586
+
587
+ /* Default/Primary confirm button */
588
+ .confirm-dialog-btn-default {
589
+ background-color: rgb(var(--ui-color-primary));
590
+ color: white;
591
+ }
592
+
593
+ .confirm-dialog-btn-default:hover:not(:disabled) {
594
+ background-color: rgb(var(--ui-color-primary-hover));
595
+ }
596
+
597
+ .confirm-dialog-btn-default:focus-visible {
598
+ outline: none;
599
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-primary) / 0.4);
600
+ }
601
+
602
+ /* Danger confirm button */
603
+ .confirm-dialog-btn-danger {
604
+ background-color: rgb(var(--ui-color-error));
605
+ color: white;
606
+ }
607
+
608
+ .confirm-dialog-btn-danger:hover:not(:disabled) {
609
+ background-color: rgb(var(--ui-color-error-light));
610
+ }
611
+
612
+ .confirm-dialog-btn-danger:focus-visible {
613
+ outline: none;
614
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-error) / 0.4);
615
+ }
616
+
617
+ /* Warning confirm button */
618
+ .confirm-dialog-btn-warning {
619
+ background-color: rgb(var(--ui-color-warning));
620
+ color: white;
621
+ }
622
+
623
+ .confirm-dialog-btn-warning:hover:not(:disabled) {
624
+ filter: brightness(0.95);
625
+ }
626
+
627
+ .confirm-dialog-btn-warning:focus-visible {
628
+ outline: none;
629
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-warning) / 0.4);
630
+ }
631
+
632
+ /* Success confirm button */
633
+ .confirm-dialog-btn-success {
634
+ background-color: rgb(var(--ui-color-success));
635
+ color: white;
636
+ }
637
+
638
+ .confirm-dialog-btn-success:hover:not(:disabled) {
639
+ filter: brightness(0.95);
640
+ }
641
+
642
+ .confirm-dialog-btn-success:focus-visible {
643
+ outline: none;
644
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-success) / 0.4);
645
+ }
646
+
647
+ /* ========================================
648
+ SPINNER
649
+ ======================================== */
650
+ .confirm-dialog-spinner {
651
+ animation: confirm-spin 0.8s linear infinite;
652
+ }
653
+
654
+ @keyframes confirm-spin {
655
+ to { transform: rotate(360deg); }
656
+ }
657
+
658
+ /* ========================================
659
+ REDUCED MOTION
660
+ ======================================== */
661
+ @media (prefers-reduced-motion: reduce) {
662
+ .confirm-dialog-backdrop,
663
+ .confirm-dialog,
664
+ .confirm-dialog-spinner {
665
+ animation: none;
666
+ }
667
+ }
668
+
669
+ /* ========================================
670
+ DARK MODE ADJUSTMENTS
671
+ ======================================== */
672
+ :global([data-theme='dark']) .confirm-dialog-close-btn:focus-visible,
673
+ :global([data-theme='dark']) .confirm-dialog-btn-secondary:focus-visible,
674
+ :global([data-theme='dark']) .confirm-dialog-btn-default:focus-visible {
675
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-primary-light) / 0.5);
676
+ }
677
+
678
+ :global([data-theme='dark']) .confirm-dialog-btn-danger:focus-visible {
679
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-error-light) / 0.5);
680
+ }
681
+
682
+ @media (prefers-color-scheme: dark) {
683
+ :global(:root:not([data-theme='light'])) .confirm-dialog-close-btn:focus-visible,
684
+ :global(:root:not([data-theme='light'])) .confirm-dialog-btn-secondary:focus-visible,
685
+ :global(:root:not([data-theme='light'])) .confirm-dialog-btn-default:focus-visible {
686
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-primary-light) / 0.5);
687
+ }
688
+
689
+ :global(:root:not([data-theme='light'])) .confirm-dialog-btn-danger:focus-visible {
690
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-error-light) / 0.5);
691
+ }
692
+ }
693
+ </style>