@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,66 @@
1
+ import type { Snippet } from 'svelte';
2
+ type DialogVariant = 'default' | 'danger' | 'warning' | 'success';
3
+ interface Props {
4
+ /** Whether the dialog is open */
5
+ open?: boolean;
6
+ /** Dialog title */
7
+ title?: string;
8
+ /** Dialog message */
9
+ message?: string;
10
+ /** Text for confirm button */
11
+ confirmText?: string;
12
+ /** Text for cancel button */
13
+ cancelText?: string;
14
+ /** Visual variant */
15
+ variant?: DialogVariant;
16
+ /** Whether the dialog is in loading state */
17
+ loading?: boolean;
18
+ /** Error message to display */
19
+ error?: string;
20
+ /** Focus confirm button on open (default: focus cancel for destructive actions) */
21
+ focusConfirm?: boolean;
22
+ /** Show close button in header */
23
+ showCloseButton?: boolean;
24
+ /** Require typing confirmation text for destructive actions */
25
+ requireConfirmation?: string;
26
+ /** Custom content instead of message */
27
+ children?: Snippet;
28
+ /** Custom icon snippet */
29
+ icon?: Snippet;
30
+ /** Additional CSS classes */
31
+ class?: string;
32
+ /** Confirm handler */
33
+ onconfirm?: () => void;
34
+ /** Cancel handler */
35
+ oncancel?: () => void;
36
+ }
37
+ /**
38
+ * ConfirmDialog
39
+ *
40
+ * A world-class confirmation dialog for destructive or important actions in healthcare apps.
41
+ * Features proper focus management, semantic variants, and full accessibility.
42
+ *
43
+ * Features:
44
+ * - Multiple variants (default, danger, warning, success)
45
+ * - Focus management per WCAG guidelines
46
+ * - Loading state with spinner
47
+ * - Error display
48
+ * - Optional destructive confirmation (type to confirm)
49
+ * - Custom content support
50
+ * - Role-based semantics (dialog vs alertdialog)
51
+ *
52
+ * @example
53
+ * <ConfirmDialog
54
+ * open={showConfirm}
55
+ * title="Delete Patient Record"
56
+ * message="This action cannot be undone. Are you sure you want to delete this patient record?"
57
+ * variant="danger"
58
+ * confirmText="Delete"
59
+ * onconfirm={handleDelete}
60
+ * oncancel={() => showConfirm = false}
61
+ * />
62
+ */
63
+ declare const ConfirmDialog: import("svelte").Component<Props, {}, "">;
64
+ type ConfirmDialog = ReturnType<typeof ConfirmDialog>;
65
+ export default ConfirmDialog;
66
+ //# sourceMappingURL=ConfirmDialog.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfirmDialog.svelte.d.ts","sourceRoot":"","sources":["../../../src/dialogs/ConfirmDialog/ConfirmDialog.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAKrC,KAAK,aAAa,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAElE,UAAU,KAAK;IACd,iCAAiC;IACjC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6BAA6B;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qBAAqB;IACrB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,kCAAkC;IAClC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,+DAA+D;IAC/D,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0BAA0B;IAC1B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAmOF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { default as ConfirmDialog } from './ConfirmDialog.svelte';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dialogs/ConfirmDialog/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1 @@
1
+ export { default as ConfirmDialog } from './ConfirmDialog.svelte';
@@ -0,0 +1,441 @@
1
+ <!--
2
+ @component Modal
3
+
4
+ A world-class modal dialog component designed for healthcare applications.
5
+ Features proper focus management, keyboard navigation, and full accessibility.
6
+
7
+ Features:
8
+ - Focus trap and focus restoration (WCAG 2.1 compliant)
9
+ - Multiple size variants including full-screen
10
+ - Scroll behavior options (body vs modal)
11
+ - Smooth enter/exit animations
12
+ - Dark mode support
13
+ - Prevent accidental close for critical modals
14
+ - Customizable header, body, and footer
15
+
16
+ @example
17
+ <Modal open={isOpen} onclose={() => isOpen = false} title="Patient Details">
18
+ <p>Modal content here</p>
19
+
20
+ {#snippet footer()}
21
+ <Button variant="secondary" onclick={() => isOpen = false}>Cancel</Button>
22
+ <Button variant="primary" onclick={handleSave}>Save</Button>
23
+ {/snippet}
24
+ </Modal>
25
+ -->
26
+ <script lang="ts">
27
+ import type { Snippet } from 'svelte';
28
+ import { X } from 'lucide-svelte';
29
+ import { createFocusTrap, Keys, generateId } from '../../utils/keyboard.js';
30
+
31
+ type ModalSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full';
32
+ type ModalPosition = 'center' | 'top';
33
+ type ScrollBehavior = 'inside' | 'outside';
34
+
35
+ interface Props {
36
+ /** Whether the modal is open */
37
+ open?: boolean;
38
+ /** Custom ID for the modal (auto-generated if not provided) */
39
+ id?: string;
40
+ /** Modal title for header and accessibility */
41
+ title?: string;
42
+ /** Size of the modal */
43
+ size?: ModalSize;
44
+ /** Position of the modal */
45
+ position?: ModalPosition;
46
+ /** Scroll behavior - 'inside' scrolls modal body, 'outside' scrolls the page */
47
+ scrollBehavior?: ScrollBehavior;
48
+ /** Whether to show close button in header */
49
+ showCloseButton?: boolean;
50
+ /** Whether clicking backdrop closes modal */
51
+ closeOnBackdrop?: boolean;
52
+ /** Whether pressing Escape closes modal */
53
+ closeOnEscape?: boolean;
54
+ /** Prevent closing (for critical modals with required actions) */
55
+ preventClose?: boolean;
56
+ /** Element to focus when modal opens (defaults to first focusable or close button) */
57
+ initialFocus?: HTMLElement | null;
58
+ /** Header content snippet (overrides title) */
59
+ header?: Snippet;
60
+ /** Footer content snippet */
61
+ footer?: Snippet;
62
+ /** Main content */
63
+ children?: Snippet;
64
+ /** Additional CSS classes for the modal container */
65
+ class?: string;
66
+ /** Close handler */
67
+ onclose?: () => void;
68
+ /** Test ID for e2e testing */
69
+ testId?: string;
70
+ }
71
+
72
+ let {
73
+ open = false,
74
+ id,
75
+ title = 'Dialog',
76
+ size = 'md',
77
+ position = 'center',
78
+ scrollBehavior = 'inside',
79
+ showCloseButton = true,
80
+ closeOnBackdrop = true,
81
+ closeOnEscape = true,
82
+ preventClose = false,
83
+ initialFocus,
84
+ header,
85
+ footer,
86
+ children,
87
+ class: className = '',
88
+ onclose,
89
+ testId
90
+ }: Props = $props();
91
+
92
+ const generatedId = generateId('modal');
93
+ const modalId = $derived(id || generatedId);
94
+ const titleId = $derived(`${modalId}-title`);
95
+ const descriptionId = $derived(`${modalId}-description`);
96
+
97
+ let dialogElement = $state<HTMLDivElement | null>(null);
98
+ let closeButtonElement = $state<HTMLButtonElement | null>(null);
99
+ let focusTrap: ReturnType<typeof createFocusTrap> | null = null;
100
+ let previouslyFocusedElement: HTMLElement | null = null;
101
+
102
+ // Calculate scrollbar width to prevent layout shift
103
+ function getScrollbarWidth(): number {
104
+ return window.innerWidth - document.documentElement.clientWidth;
105
+ }
106
+
107
+ // Handle focus trap and focus restoration
108
+ $effect(() => {
109
+ if (open && dialogElement) {
110
+ // Save the currently focused element
111
+ previouslyFocusedElement = document.activeElement as HTMLElement;
112
+
113
+ // Create and activate focus trap
114
+ focusTrap = createFocusTrap(dialogElement);
115
+ focusTrap.activate();
116
+
117
+ // Lock body scroll and prevent layout shift
118
+ const scrollbarWidth = getScrollbarWidth();
119
+ document.body.style.overflow = 'hidden';
120
+ if (scrollbarWidth > 0) {
121
+ document.body.style.paddingRight = `${scrollbarWidth}px`;
122
+ }
123
+
124
+ // Focus initial element or first focusable
125
+ setTimeout(() => {
126
+ if (initialFocus) {
127
+ initialFocus.focus();
128
+ } else if (closeButtonElement) {
129
+ closeButtonElement.focus();
130
+ }
131
+ }, 0);
132
+ }
133
+
134
+ return () => {
135
+ focusTrap?.deactivate();
136
+ document.body.style.overflow = '';
137
+ document.body.style.paddingRight = '';
138
+
139
+ // Restore focus
140
+ if (previouslyFocusedElement?.focus) {
141
+ setTimeout(() => {
142
+ previouslyFocusedElement?.focus();
143
+ }, 0);
144
+ }
145
+ };
146
+ });
147
+
148
+ function handleClose() {
149
+ if (!preventClose) {
150
+ onclose?.();
151
+ }
152
+ }
153
+
154
+ function handleBackdropClick(e: MouseEvent) {
155
+ if (closeOnBackdrop && e.target === e.currentTarget) {
156
+ handleClose();
157
+ }
158
+ }
159
+
160
+ function handleKeyDown(e: KeyboardEvent) {
161
+ if (closeOnEscape && e.key === Keys.Escape) {
162
+ e.preventDefault();
163
+ handleClose();
164
+ }
165
+ }
166
+ </script>
167
+
168
+ <svelte:window onkeydown={open ? handleKeyDown : undefined} />
169
+
170
+ {#if open}
171
+ <!-- Backdrop -->
172
+ <div
173
+ class="modal-backdrop modal-position-{position}"
174
+ role="presentation"
175
+ onclick={handleBackdropClick}
176
+ onkeydown={() => {}}
177
+ >
178
+ <!-- Modal container -->
179
+ <div
180
+ bind:this={dialogElement}
181
+ id={modalId}
182
+ class="modal-container modal-{size} modal-scroll-{scrollBehavior} {className}"
183
+ role="dialog"
184
+ aria-modal="true"
185
+ aria-labelledby={titleId}
186
+ aria-describedby={children ? descriptionId : undefined}
187
+ data-testid={testId}
188
+ >
189
+ <!-- Header -->
190
+ {#if header || title || showCloseButton}
191
+ <div class="modal-header">
192
+ <div id={titleId} class="modal-title-wrapper">
193
+ {#if header}
194
+ {@render header()}
195
+ {:else if title}
196
+ <h2 class="modal-title">{title}</h2>
197
+ {/if}
198
+ </div>
199
+
200
+ {#if showCloseButton && !preventClose}
201
+ <button
202
+ bind:this={closeButtonElement}
203
+ type="button"
204
+ class="modal-close-btn"
205
+ onclick={handleClose}
206
+ aria-label="Close dialog"
207
+ >
208
+ <X size={20} aria-hidden="true" />
209
+ </button>
210
+ {/if}
211
+ </div>
212
+ {/if}
213
+
214
+ <!-- Body -->
215
+ {#if children}
216
+ <div id={descriptionId} class="modal-body">
217
+ {@render children()}
218
+ </div>
219
+ {/if}
220
+
221
+ <!-- Footer -->
222
+ {#if footer}
223
+ <div class="modal-footer">
224
+ {@render footer()}
225
+ </div>
226
+ {/if}
227
+ </div>
228
+ </div>
229
+ {/if}
230
+
231
+ <style>
232
+ /* ========================================
233
+ BACKDROP
234
+ ======================================== */
235
+ .modal-backdrop {
236
+ position: fixed;
237
+ inset: 0;
238
+ z-index: 1000;
239
+ display: flex;
240
+ justify-content: center;
241
+ padding: 1rem;
242
+ background-color: var(--ui-backdrop);
243
+ backdrop-filter: blur(4px);
244
+ -webkit-backdrop-filter: blur(4px);
245
+ animation: modal-fade-in 0.2s ease-out;
246
+ overflow-y: auto;
247
+ }
248
+
249
+ .modal-position-center {
250
+ align-items: center;
251
+ }
252
+
253
+ .modal-position-top {
254
+ align-items: flex-start;
255
+ padding-top: 5rem;
256
+ }
257
+
258
+ @keyframes modal-fade-in {
259
+ from { opacity: 0; }
260
+ to { opacity: 1; }
261
+ }
262
+
263
+ /* ========================================
264
+ CONTAINER
265
+ ======================================== */
266
+ .modal-container {
267
+ position: relative;
268
+ display: flex;
269
+ flex-direction: column;
270
+ width: 100%;
271
+ max-height: calc(100vh - 2rem);
272
+ background-color: var(--ui-bg-primary);
273
+ border: 1px solid var(--ui-border-default);
274
+ border-radius: 1rem;
275
+ box-shadow:
276
+ 0 20px 25px -5px rgb(0 0 0 / 0.1),
277
+ 0 8px 10px -6px rgb(0 0 0 / 0.1);
278
+ animation: modal-scale-in 0.3s ease-out;
279
+ overflow: hidden;
280
+ }
281
+
282
+ @keyframes modal-scale-in {
283
+ from {
284
+ opacity: 0;
285
+ transform: scale(0.95) translateY(10px);
286
+ }
287
+ to {
288
+ opacity: 1;
289
+ transform: scale(1) translateY(0);
290
+ }
291
+ }
292
+
293
+ /* Size variants */
294
+ .modal-sm { max-width: 24rem; }
295
+ .modal-md { max-width: 28rem; }
296
+ .modal-lg { max-width: 32rem; }
297
+ .modal-xl { max-width: 36rem; }
298
+ .modal-2xl { max-width: 42rem; }
299
+ .modal-full { max-width: 56rem; }
300
+
301
+ /* Scroll behavior */
302
+ .modal-scroll-inside {
303
+ max-height: calc(100vh - 4rem);
304
+ }
305
+
306
+ .modal-scroll-inside .modal-body {
307
+ overflow-y: auto;
308
+ }
309
+
310
+ .modal-scroll-outside {
311
+ max-height: none;
312
+ }
313
+
314
+ /* ========================================
315
+ HEADER
316
+ ======================================== */
317
+ .modal-header {
318
+ display: flex;
319
+ align-items: center;
320
+ justify-content: space-between;
321
+ gap: 1rem;
322
+ padding: 1rem 1.5rem;
323
+ border-bottom: 1px solid var(--ui-border-default);
324
+ background-color: var(--ui-bg-secondary);
325
+ border-radius: 1rem 1rem 0 0;
326
+ flex-shrink: 0;
327
+ }
328
+
329
+ .modal-title-wrapper {
330
+ flex: 1;
331
+ min-width: 0;
332
+ }
333
+
334
+ .modal-title {
335
+ margin: 0;
336
+ font-size: 1.125rem;
337
+ font-weight: 600;
338
+ color: var(--ui-text-primary);
339
+ line-height: 1.4;
340
+ }
341
+
342
+ /* ========================================
343
+ CLOSE BUTTON
344
+ ======================================== */
345
+ .modal-close-btn {
346
+ display: flex;
347
+ align-items: center;
348
+ justify-content: center;
349
+ padding: 0.375rem;
350
+ background: transparent;
351
+ border: none;
352
+ border-radius: 0.5rem;
353
+ color: var(--ui-text-tertiary);
354
+ cursor: pointer;
355
+ transition: all 0.15s ease;
356
+ flex-shrink: 0;
357
+ }
358
+
359
+ .modal-close-btn:hover {
360
+ background-color: var(--ui-bg-tertiary);
361
+ color: var(--ui-text-primary);
362
+ }
363
+
364
+ .modal-close-btn:focus-visible {
365
+ outline: none;
366
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-primary) / 0.4);
367
+ }
368
+
369
+ :global([data-theme='dark']) .modal-close-btn:focus-visible {
370
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-primary-light) / 0.5);
371
+ }
372
+
373
+ @media (prefers-color-scheme: dark) {
374
+ :global(:root:not([data-theme='light'])) .modal-close-btn:focus-visible {
375
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-primary-light) / 0.5);
376
+ }
377
+ }
378
+
379
+ /* ========================================
380
+ BODY
381
+ ======================================== */
382
+ .modal-body {
383
+ padding: 1.25rem 1.5rem;
384
+ flex: 1;
385
+ min-height: 0;
386
+ }
387
+
388
+ /* ========================================
389
+ FOOTER
390
+ ======================================== */
391
+ .modal-footer {
392
+ display: flex;
393
+ align-items: center;
394
+ justify-content: flex-end;
395
+ gap: 0.75rem;
396
+ padding: 1rem 1.5rem;
397
+ border-top: 1px solid var(--ui-border-default);
398
+ background-color: var(--ui-bg-secondary);
399
+ border-radius: 0 0 1rem 1rem;
400
+ flex-shrink: 0;
401
+ }
402
+
403
+ /* ========================================
404
+ REDUCED MOTION
405
+ ======================================== */
406
+ @media (prefers-reduced-motion: reduce) {
407
+ .modal-backdrop,
408
+ .modal-container {
409
+ animation: none;
410
+ }
411
+ }
412
+
413
+ /* ========================================
414
+ RESPONSIVE
415
+ ======================================== */
416
+ @media (max-width: 640px) {
417
+ .modal-backdrop {
418
+ padding: 0;
419
+ align-items: flex-end;
420
+ }
421
+
422
+ .modal-position-top {
423
+ padding-top: 0;
424
+ }
425
+
426
+ .modal-container {
427
+ max-width: 100%;
428
+ max-height: 90vh;
429
+ border-radius: 1rem 1rem 0 0;
430
+ margin: 0;
431
+ }
432
+
433
+ .modal-header {
434
+ border-radius: 1rem 1rem 0 0;
435
+ }
436
+
437
+ .modal-footer {
438
+ border-radius: 0;
439
+ }
440
+ }
441
+ </style>
@@ -0,0 +1,69 @@
1
+ import type { Snippet } from 'svelte';
2
+ type ModalSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full';
3
+ type ModalPosition = 'center' | 'top';
4
+ type ScrollBehavior = 'inside' | 'outside';
5
+ interface Props {
6
+ /** Whether the modal is open */
7
+ open?: boolean;
8
+ /** Custom ID for the modal (auto-generated if not provided) */
9
+ id?: string;
10
+ /** Modal title for header and accessibility */
11
+ title?: string;
12
+ /** Size of the modal */
13
+ size?: ModalSize;
14
+ /** Position of the modal */
15
+ position?: ModalPosition;
16
+ /** Scroll behavior - 'inside' scrolls modal body, 'outside' scrolls the page */
17
+ scrollBehavior?: ScrollBehavior;
18
+ /** Whether to show close button in header */
19
+ showCloseButton?: boolean;
20
+ /** Whether clicking backdrop closes modal */
21
+ closeOnBackdrop?: boolean;
22
+ /** Whether pressing Escape closes modal */
23
+ closeOnEscape?: boolean;
24
+ /** Prevent closing (for critical modals with required actions) */
25
+ preventClose?: boolean;
26
+ /** Element to focus when modal opens (defaults to first focusable or close button) */
27
+ initialFocus?: HTMLElement | null;
28
+ /** Header content snippet (overrides title) */
29
+ header?: Snippet;
30
+ /** Footer content snippet */
31
+ footer?: Snippet;
32
+ /** Main content */
33
+ children?: Snippet;
34
+ /** Additional CSS classes for the modal container */
35
+ class?: string;
36
+ /** Close handler */
37
+ onclose?: () => void;
38
+ /** Test ID for e2e testing */
39
+ testId?: string;
40
+ }
41
+ /**
42
+ * Modal
43
+ *
44
+ * A world-class modal dialog component designed for healthcare applications.
45
+ * Features proper focus management, keyboard navigation, and full accessibility.
46
+ *
47
+ * Features:
48
+ * - Focus trap and focus restoration (WCAG 2.1 compliant)
49
+ * - Multiple size variants including full-screen
50
+ * - Scroll behavior options (body vs modal)
51
+ * - Smooth enter/exit animations
52
+ * - Dark mode support
53
+ * - Prevent accidental close for critical modals
54
+ * - Customizable header, body, and footer
55
+ *
56
+ * @example
57
+ * <Modal open={isOpen} onclose={() => isOpen = false} title="Patient Details">
58
+ * <p>Modal content here</p>
59
+ *
60
+ * {#snippet footer()}
61
+ * <Button variant="secondary" onclick={() => isOpen = false}>Cancel</Button>
62
+ * <Button variant="primary" onclick={handleSave}>Save</Button>
63
+ * {/snippet}
64
+ * </Modal>
65
+ */
66
+ declare const Modal: import("svelte").Component<Props, {}, "">;
67
+ type Modal = ReturnType<typeof Modal>;
68
+ export default Modal;
69
+ //# sourceMappingURL=Modal.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Modal.svelte.d.ts","sourceRoot":"","sources":["../../../src/dialogs/Modal/Modal.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAKrC,KAAK,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC;AAC5D,KAAK,aAAa,GAAG,QAAQ,GAAG,KAAK,CAAC;AACtC,KAAK,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE3C,UAAU,KAAK;IACd,gCAAgC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,+DAA+D;IAC/D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wBAAwB;IACxB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,4BAA4B;IAC5B,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,gFAAgF;IAChF,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,6CAA6C;IAC7C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,6CAA6C;IAC7C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,2CAA2C;IAC3C,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kEAAkE;IAClE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,sFAAsF;IACtF,YAAY,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAClC,+CAA+C;IAC/C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,6BAA6B;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,mBAAmB;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oBAAoB;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AA0JF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,QAAA,MAAM,KAAK,2CAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { default as Modal } from './Modal.svelte';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dialogs/Modal/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1 @@
1
+ export { default as Modal } from './Modal.svelte';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @synthaxai/ui - Dialog Components
3
+ *
4
+ * Modal and dialog components for overlays and confirmations.
5
+ */
6
+ export { Modal } from './Modal/index.js';
7
+ export { ConfirmDialog } from './ConfirmDialog/index.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dialogs/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @synthaxai/ui - Dialog Components
3
+ *
4
+ * Modal and dialog components for overlays and confirmations.
5
+ */
6
+ export { Modal } from './Modal/index.js';
7
+ export { ConfirmDialog } from './ConfirmDialog/index.js';