@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,548 @@
1
+ <!--
2
+ @component Select
3
+
4
+ An accessible select dropdown component with support for options,
5
+ groups, and custom styling. Designed for healthcare forms.
6
+
7
+ @example
8
+ <Select
9
+ id="gender"
10
+ label="Gender"
11
+ value={gender}
12
+ options={[
13
+ { value: 'male', label: 'Male' },
14
+ { value: 'female', label: 'Female' },
15
+ { value: 'other', label: 'Other' }
16
+ ]}
17
+ onchange={(e) => gender = e.target.value}
18
+ />
19
+ -->
20
+ <script lang="ts">
21
+ import type { Snippet } from 'svelte';
22
+ import { ChevronDown, AlertCircle, Check } from 'lucide-svelte';
23
+ import { cn } from '../../utils/cn.js';
24
+ import { generateId } from '../../utils/keyboard.js';
25
+
26
+ type SelectSize = 'sm' | 'md' | 'lg';
27
+
28
+ interface SelectOption {
29
+ value: string;
30
+ label: string;
31
+ disabled?: boolean;
32
+ }
33
+
34
+ interface SelectGroup {
35
+ label: string;
36
+ options: SelectOption[];
37
+ }
38
+
39
+ interface Props {
40
+ /** Unique identifier for the select */
41
+ id?: string;
42
+ /** Form field name */
43
+ name?: string;
44
+ /** Label text for the select */
45
+ label: string;
46
+ /** Current value */
47
+ value?: string;
48
+ /** Placeholder text */
49
+ placeholder?: string;
50
+ /** Array of options or option groups */
51
+ options: SelectOption[] | SelectGroup[];
52
+ /** Whether the field is required */
53
+ required?: boolean;
54
+ /** Whether the field is disabled */
55
+ disabled?: boolean;
56
+ /** Error message to display */
57
+ error?: string;
58
+ /** Success message when valid */
59
+ successMessage?: string;
60
+ /** Hint text to display below the select */
61
+ hint?: string;
62
+ /** Size of the select */
63
+ size?: SelectSize;
64
+ /** Whether to hide the label visually (still accessible) */
65
+ hideLabel?: boolean;
66
+ /** Additional CSS classes for the wrapper */
67
+ class?: string;
68
+ /** Icon to display on the left */
69
+ iconLeft?: Snippet;
70
+ /** Change handler */
71
+ onchange?: (event: Event) => void;
72
+ /** Blur handler */
73
+ onblur?: (event: FocusEvent) => void;
74
+ /** Test ID for e2e testing (Playwright, Cypress) */
75
+ testId?: string;
76
+ /** Show validation state immediately without waiting for interaction */
77
+ validateOnMount?: boolean;
78
+ }
79
+
80
+ let {
81
+ id = generateId('select'),
82
+ name,
83
+ label,
84
+ value = '',
85
+ placeholder = 'Select an option',
86
+ options,
87
+ required = false,
88
+ disabled = false,
89
+ error = '',
90
+ successMessage = '',
91
+ hint = '',
92
+ size = 'md',
93
+ hideLabel = false,
94
+ class: className = '',
95
+ iconLeft,
96
+ onchange,
97
+ onblur,
98
+ testId,
99
+ validateOnMount = false
100
+ }: Props = $props();
101
+
102
+ // Internal state
103
+ let hasInteracted = $state(false);
104
+
105
+ // Derived states
106
+ // Validation shows: after interaction OR if validateOnMount is enabled
107
+ const shouldValidate = $derived(hasInteracted || validateOnMount);
108
+ const showError = $derived(shouldValidate && !!error);
109
+ const showSuccess = $derived(shouldValidate && !error && !!value && !!successMessage);
110
+ const isValid = $derived(shouldValidate && !error && !!value && !!successMessage);
111
+ const errorId = $derived(`${id}-error`);
112
+ const hintId = $derived(`${id}-hint`);
113
+
114
+ // Check if options are grouped
115
+ function isGrouped(opts: SelectOption[] | SelectGroup[]): opts is SelectGroup[] {
116
+ return opts.length > 0 && 'options' in opts[0];
117
+ }
118
+
119
+ function handleChange(e: Event) {
120
+ hasInteracted = true;
121
+ onchange?.(e);
122
+ }
123
+
124
+ function handleBlur(e: FocusEvent) {
125
+ hasInteracted = true;
126
+ onblur?.(e);
127
+ }
128
+
129
+ // Build aria-describedby
130
+ const ariaDescribedBy = $derived(
131
+ [error && errorId, hint && hintId].filter(Boolean).join(' ') || undefined
132
+ );
133
+ </script>
134
+
135
+ <div class={cn('select-wrapper', `select-${size}`, disabled && 'select-disabled', className)}>
136
+ <!-- Label -->
137
+ <label for={id} class={cn('select-label', hideLabel && 'sr-only')}>
138
+ {label}
139
+ {#if required}
140
+ <span class="select-required" aria-hidden="true">*</span>
141
+ {/if}
142
+ </label>
143
+
144
+ <!-- Select wrapper -->
145
+ <div class="select-field-wrapper">
146
+ {#if iconLeft}
147
+ <span class="select-icon-left" aria-hidden="true">
148
+ {@render iconLeft()}
149
+ </span>
150
+ {/if}
151
+
152
+ <select
153
+ {id}
154
+ {name}
155
+ {value}
156
+ {disabled}
157
+ {required}
158
+ aria-invalid={showError}
159
+ aria-describedby={ariaDescribedBy}
160
+ aria-required={required}
161
+ data-testid={testId}
162
+ class={cn(
163
+ 'select-field',
164
+ iconLeft && 'select-has-icon-left',
165
+ (showError || isValid) && 'select-has-icon-right',
166
+ showError && 'select-error',
167
+ isValid && 'select-valid',
168
+ !value && 'select-placeholder'
169
+ )}
170
+ onchange={handleChange}
171
+ onblur={handleBlur}
172
+ >
173
+ {#if placeholder}
174
+ <option value="" disabled>{placeholder}</option>
175
+ {/if}
176
+
177
+ {#if isGrouped(options)}
178
+ {#each options as group}
179
+ <optgroup label={group.label}>
180
+ {#each group.options as option}
181
+ <option value={option.value} disabled={option.disabled}>
182
+ {option.label}
183
+ </option>
184
+ {/each}
185
+ </optgroup>
186
+ {/each}
187
+ {:else}
188
+ {#each options as option}
189
+ <option value={option.value} disabled={option.disabled}>
190
+ {option.label}
191
+ </option>
192
+ {/each}
193
+ {/if}
194
+ </select>
195
+
196
+ <!-- Status icon -->
197
+ {#if showError}
198
+ <span class="select-status-icon select-status-error" aria-hidden="true">
199
+ <AlertCircle size={size === 'sm' ? 14 : size === 'lg' ? 18 : 16} />
200
+ </span>
201
+ {:else if isValid}
202
+ <span class="select-status-icon select-status-success" aria-hidden="true">
203
+ <Check size={size === 'sm' ? 14 : size === 'lg' ? 18 : 16} />
204
+ </span>
205
+ {/if}
206
+
207
+ <!-- Chevron icon -->
208
+ <span class="select-chevron" aria-hidden="true">
209
+ <ChevronDown size={size === 'sm' ? 16 : size === 'lg' ? 22 : 18} />
210
+ </span>
211
+ </div>
212
+
213
+ <!-- Footer messages -->
214
+ <div class="select-footer">
215
+ {#if showError}
216
+ <p id={errorId} class="select-message select-message-error" role="alert" aria-live="assertive">
217
+ <AlertCircle size={12} />
218
+ {error}
219
+ </p>
220
+ {:else if showSuccess}
221
+ <p class="select-message select-message-success">
222
+ <Check size={12} />
223
+ {successMessage}
224
+ </p>
225
+ {:else if hint}
226
+ <p id={hintId} class="select-message select-message-hint">
227
+ {hint}
228
+ </p>
229
+ {/if}
230
+ </div>
231
+ </div>
232
+
233
+ <style>
234
+ /* ========================================
235
+ BASE WRAPPER
236
+ ======================================== */
237
+ .select-wrapper {
238
+ display: flex;
239
+ flex-direction: column;
240
+ gap: 0.375rem;
241
+ }
242
+
243
+ .select-disabled {
244
+ opacity: 0.6;
245
+ }
246
+
247
+ /* ========================================
248
+ LABEL
249
+ ======================================== */
250
+ .select-label {
251
+ display: flex;
252
+ align-items: center;
253
+ gap: 0.375rem;
254
+ font-weight: 500;
255
+ color: var(--ui-text-primary);
256
+ }
257
+
258
+ .select-sm .select-label {
259
+ font-size: 0.75rem;
260
+ }
261
+
262
+ .select-md .select-label,
263
+ .select-lg .select-label {
264
+ font-size: 0.875rem;
265
+ }
266
+
267
+ .select-required {
268
+ color: rgb(var(--ui-color-error));
269
+ }
270
+
271
+ :global([data-theme='dark']) .select-required {
272
+ color: rgb(var(--ui-color-error-light));
273
+ }
274
+
275
+ @media (prefers-color-scheme: dark) {
276
+ :global(:root:not([data-theme='light'])) .select-required {
277
+ color: rgb(var(--ui-color-error-light));
278
+ }
279
+ }
280
+
281
+ /* ========================================
282
+ FIELD WRAPPER
283
+ ======================================== */
284
+ .select-field-wrapper {
285
+ position: relative;
286
+ }
287
+
288
+ /* ========================================
289
+ SELECT FIELD
290
+ ======================================== */
291
+ .select-field {
292
+ width: 100%;
293
+ appearance: none;
294
+ border: 2px solid var(--ui-border-default);
295
+ border-radius: 0.5rem;
296
+ background: var(--ui-bg-secondary);
297
+ color: var(--ui-text-primary);
298
+ cursor: pointer;
299
+ transition: all 0.2s ease;
300
+ }
301
+
302
+ .select-field:focus-visible {
303
+ outline: none;
304
+ background: var(--ui-bg-primary);
305
+ border-color: rgb(var(--ui-color-primary));
306
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-primary) / 0.4);
307
+ }
308
+
309
+ :global([data-theme='dark']) .select-field:focus-visible {
310
+ border-color: rgb(var(--ui-color-primary-light));
311
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-primary-light) / 0.5);
312
+ }
313
+
314
+ @media (prefers-color-scheme: dark) {
315
+ :global(:root:not([data-theme='light'])) .select-field:focus-visible {
316
+ border-color: rgb(var(--ui-color-primary-light));
317
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-primary-light) / 0.5);
318
+ }
319
+ }
320
+
321
+ .select-field:disabled {
322
+ cursor: not-allowed;
323
+ background: var(--ui-bg-tertiary);
324
+ }
325
+
326
+ /* Placeholder styling */
327
+ .select-placeholder {
328
+ color: var(--ui-text-tertiary);
329
+ }
330
+
331
+ /* Size variants */
332
+ .select-sm .select-field {
333
+ height: 2rem;
334
+ padding-left: 0.75rem;
335
+ padding-right: 2.25rem;
336
+ font-size: 0.875rem;
337
+ }
338
+
339
+ .select-md .select-field {
340
+ height: 2.5rem;
341
+ padding-left: 0.875rem;
342
+ padding-right: 2.75rem;
343
+ font-size: 0.875rem;
344
+ }
345
+
346
+ .select-lg .select-field {
347
+ height: 3rem;
348
+ padding-left: 1rem;
349
+ padding-right: 3.25rem;
350
+ font-size: 1rem;
351
+ }
352
+
353
+ /* Icon padding adjustments - use !important to override size-specific padding */
354
+ .select-field.select-has-icon-left {
355
+ padding-left: 2.5rem !important;
356
+ }
357
+
358
+ .select-field.select-has-icon-right {
359
+ padding-right: 4rem !important;
360
+ }
361
+
362
+ /* Error state */
363
+ .select-error {
364
+ border-color: rgb(var(--ui-color-error));
365
+ }
366
+
367
+ .select-error:focus-visible {
368
+ border-color: rgb(var(--ui-color-error));
369
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-error) / 0.4);
370
+ }
371
+
372
+ :global([data-theme='dark']) .select-error {
373
+ border-color: rgb(var(--ui-color-error-light));
374
+ }
375
+
376
+ :global([data-theme='dark']) .select-error:focus-visible {
377
+ border-color: rgb(var(--ui-color-error-light));
378
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-error-light) / 0.5);
379
+ }
380
+
381
+ @media (prefers-color-scheme: dark) {
382
+ :global(:root:not([data-theme='light'])) .select-error {
383
+ border-color: rgb(var(--ui-color-error-light));
384
+ }
385
+
386
+ :global(:root:not([data-theme='light'])) .select-error:focus-visible {
387
+ border-color: rgb(var(--ui-color-error-light));
388
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-error-light) / 0.5);
389
+ }
390
+ }
391
+
392
+ /* Valid state */
393
+ .select-valid {
394
+ border-color: rgb(var(--ui-color-success));
395
+ }
396
+
397
+ .select-valid:focus-visible {
398
+ border-color: rgb(var(--ui-color-success));
399
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-success) / 0.4);
400
+ }
401
+
402
+ :global([data-theme='dark']) .select-valid {
403
+ border-color: rgb(var(--ui-color-success-light));
404
+ }
405
+
406
+ :global([data-theme='dark']) .select-valid:focus-visible {
407
+ border-color: rgb(var(--ui-color-success-light));
408
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-success-light) / 0.5);
409
+ }
410
+
411
+ @media (prefers-color-scheme: dark) {
412
+ :global(:root:not([data-theme='light'])) .select-valid {
413
+ border-color: rgb(var(--ui-color-success-light));
414
+ }
415
+
416
+ :global(:root:not([data-theme='light'])) .select-valid:focus-visible {
417
+ border-color: rgb(var(--ui-color-success-light));
418
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-success-light) / 0.5);
419
+ }
420
+ }
421
+
422
+ /* ========================================
423
+ ICONS
424
+ ======================================== */
425
+ .select-icon-left {
426
+ position: absolute;
427
+ left: 0.75rem;
428
+ top: 50%;
429
+ transform: translateY(-50%);
430
+ pointer-events: none;
431
+ color: var(--ui-text-tertiary);
432
+ }
433
+
434
+ .select-chevron {
435
+ position: absolute;
436
+ right: 0.875rem;
437
+ top: 50%;
438
+ transform: translateY(-50%);
439
+ pointer-events: none;
440
+ color: var(--ui-text-secondary);
441
+ transition: color 0.2s ease;
442
+ }
443
+
444
+ /* Improve chevron visibility on hover/focus */
445
+ .select-field-wrapper:hover .select-chevron,
446
+ .select-field:focus + .select-status-icon + .select-chevron,
447
+ .select-field:focus + .select-chevron,
448
+ .select-field:focus ~ .select-chevron {
449
+ color: var(--ui-text-primary);
450
+ }
451
+
452
+ .select-status-icon {
453
+ position: absolute;
454
+ right: 2.5rem;
455
+ top: 50%;
456
+ transform: translateY(-50%);
457
+ pointer-events: none;
458
+ }
459
+
460
+ .select-status-error {
461
+ color: rgb(var(--ui-color-error));
462
+ }
463
+
464
+ :global([data-theme='dark']) .select-status-error {
465
+ color: rgb(var(--ui-color-error-light));
466
+ }
467
+
468
+ @media (prefers-color-scheme: dark) {
469
+ :global(:root:not([data-theme='light'])) .select-status-error {
470
+ color: rgb(var(--ui-color-error-light));
471
+ }
472
+ }
473
+
474
+ .select-status-success {
475
+ color: rgb(var(--ui-color-success));
476
+ }
477
+
478
+ :global([data-theme='dark']) .select-status-success {
479
+ color: rgb(var(--ui-color-success-light));
480
+ }
481
+
482
+ @media (prefers-color-scheme: dark) {
483
+ :global(:root:not([data-theme='light'])) .select-status-success {
484
+ color: rgb(var(--ui-color-success-light));
485
+ }
486
+ }
487
+
488
+ /* ========================================
489
+ FOOTER & MESSAGES
490
+ ======================================== */
491
+ .select-footer {
492
+ min-height: 1.25rem;
493
+ }
494
+
495
+ .select-message {
496
+ display: flex;
497
+ align-items: center;
498
+ gap: 0.25rem;
499
+ font-size: 0.75rem;
500
+ }
501
+
502
+ .select-message-error {
503
+ color: rgb(var(--ui-color-error));
504
+ }
505
+
506
+ :global([data-theme='dark']) .select-message-error {
507
+ color: rgb(var(--ui-color-error-light));
508
+ }
509
+
510
+ @media (prefers-color-scheme: dark) {
511
+ :global(:root:not([data-theme='light'])) .select-message-error {
512
+ color: rgb(var(--ui-color-error-light));
513
+ }
514
+ }
515
+
516
+ .select-message-success {
517
+ color: rgb(var(--ui-color-success));
518
+ }
519
+
520
+ :global([data-theme='dark']) .select-message-success {
521
+ color: rgb(var(--ui-color-success-light));
522
+ }
523
+
524
+ @media (prefers-color-scheme: dark) {
525
+ :global(:root:not([data-theme='light'])) .select-message-success {
526
+ color: rgb(var(--ui-color-success-light));
527
+ }
528
+ }
529
+
530
+ .select-message-hint {
531
+ color: var(--ui-text-tertiary);
532
+ }
533
+
534
+ /* ========================================
535
+ UTILITIES
536
+ ======================================== */
537
+ .sr-only {
538
+ position: absolute;
539
+ width: 1px;
540
+ height: 1px;
541
+ padding: 0;
542
+ margin: -1px;
543
+ overflow: hidden;
544
+ clip: rect(0, 0, 0, 0);
545
+ white-space: nowrap;
546
+ border: 0;
547
+ }
548
+ </style>
@@ -0,0 +1,74 @@
1
+ import type { Snippet } from 'svelte';
2
+ type SelectSize = 'sm' | 'md' | 'lg';
3
+ interface SelectOption {
4
+ value: string;
5
+ label: string;
6
+ disabled?: boolean;
7
+ }
8
+ interface SelectGroup {
9
+ label: string;
10
+ options: SelectOption[];
11
+ }
12
+ interface Props {
13
+ /** Unique identifier for the select */
14
+ id?: string;
15
+ /** Form field name */
16
+ name?: string;
17
+ /** Label text for the select */
18
+ label: string;
19
+ /** Current value */
20
+ value?: string;
21
+ /** Placeholder text */
22
+ placeholder?: string;
23
+ /** Array of options or option groups */
24
+ options: SelectOption[] | SelectGroup[];
25
+ /** Whether the field is required */
26
+ required?: boolean;
27
+ /** Whether the field is disabled */
28
+ disabled?: boolean;
29
+ /** Error message to display */
30
+ error?: string;
31
+ /** Success message when valid */
32
+ successMessage?: string;
33
+ /** Hint text to display below the select */
34
+ hint?: string;
35
+ /** Size of the select */
36
+ size?: SelectSize;
37
+ /** Whether to hide the label visually (still accessible) */
38
+ hideLabel?: boolean;
39
+ /** Additional CSS classes for the wrapper */
40
+ class?: string;
41
+ /** Icon to display on the left */
42
+ iconLeft?: Snippet;
43
+ /** Change handler */
44
+ onchange?: (event: Event) => void;
45
+ /** Blur handler */
46
+ onblur?: (event: FocusEvent) => void;
47
+ /** Test ID for e2e testing (Playwright, Cypress) */
48
+ testId?: string;
49
+ /** Show validation state immediately without waiting for interaction */
50
+ validateOnMount?: boolean;
51
+ }
52
+ /**
53
+ * Select
54
+ *
55
+ * An accessible select dropdown component with support for options,
56
+ * groups, and custom styling. Designed for healthcare forms.
57
+ *
58
+ * @example
59
+ * <Select
60
+ * id="gender"
61
+ * label="Gender"
62
+ * value={gender}
63
+ * options={[
64
+ * { value: 'male', label: 'Male' },
65
+ * { value: 'female', label: 'Female' },
66
+ * { value: 'other', label: 'Other' }
67
+ * ]}
68
+ * onchange={(e) => gender = e.target.value}
69
+ * />
70
+ */
71
+ declare const Select: import("svelte").Component<Props, {}, "">;
72
+ type Select = ReturnType<typeof Select>;
73
+ export default Select;
74
+ //# sourceMappingURL=Select.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Select.svelte.d.ts","sourceRoot":"","sources":["../../../src/forms/Select/Select.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAMrC,KAAK,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAErC,UAAU,YAAY;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,WAAW;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,UAAU,KAAK;IACd,uCAAuC;IACvC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,sBAAsB;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,OAAO,EAAE,YAAY,EAAE,GAAG,WAAW,EAAE,CAAC;IACxC,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4CAA4C;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAClC,mBAAmB;IACnB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B;AA4JF;;;;;;;;;;;;;;;;;;GAkBG;AACH,QAAA,MAAM,MAAM,2CAAwC,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { default as Select } from './Select.svelte';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/forms/Select/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1 @@
1
+ export { default as Select } from './Select.svelte';