@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,628 @@
1
+ <!--
2
+ @component TextInput
3
+
4
+ A fully accessible text input component with validation states,
5
+ icons, character counting, and comprehensive ARIA support.
6
+
7
+ Designed for healthcare applications with proper error handling
8
+ and clear visual feedback.
9
+
10
+ @example
11
+ <TextInput
12
+ id="patient-name"
13
+ label="Patient Name"
14
+ value={patientName}
15
+ oninput={(e) => patientName = e.target.value}
16
+ required
17
+ />
18
+
19
+ @example
20
+ <TextInput
21
+ id="member-id"
22
+ label="Member ID"
23
+ value={memberId}
24
+ error={memberIdError}
25
+ hint="Enter your insurance member ID"
26
+ oninput={(e) => handleInput(e)}
27
+ />
28
+ -->
29
+ <script lang="ts">
30
+ import type { Snippet } from 'svelte';
31
+ import type { HTMLInputAttributes } from 'svelte/elements';
32
+ import { Check, AlertCircle, Info } from 'lucide-svelte';
33
+ import { cn } from '../../utils/cn.js';
34
+ import { generateId } from '../../utils/keyboard.js';
35
+ import type { InputMode, AutoComplete } from '../../types/index.js';
36
+
37
+ type InputSize = 'sm' | 'md' | 'lg';
38
+ type InputType = 'text' | 'email' | 'password' | 'tel' | 'url' | 'search' | 'number';
39
+
40
+ interface Props extends Omit<HTMLInputAttributes, 'class' | 'size'> {
41
+ /** Unique identifier for the input */
42
+ id?: string;
43
+ /** Form field name */
44
+ name?: string;
45
+ /** Input type */
46
+ type?: InputType;
47
+ /** Label text for the input */
48
+ label: string;
49
+ /** Current value */
50
+ value?: string;
51
+ /** Placeholder text */
52
+ placeholder?: string;
53
+ /** Whether the field is required */
54
+ required?: boolean;
55
+ /** Whether the field is disabled */
56
+ disabled?: boolean;
57
+ /** Whether the field is read-only */
58
+ readonly?: boolean;
59
+ /** Error message to display */
60
+ error?: string;
61
+ /** Hint text to display below the input */
62
+ hint?: string;
63
+ /** Success message when valid */
64
+ successMessage?: string;
65
+ /** Maximum character length */
66
+ maxLength?: number;
67
+ /** Minimum character length */
68
+ minLength?: number;
69
+ /** Whether to show character count */
70
+ showCharCount?: boolean;
71
+ /** Pattern for validation (regex) */
72
+ pattern?: string;
73
+ /** Input mode for mobile keyboards */
74
+ inputMode?: InputMode;
75
+ /** Autocomplete value */
76
+ autocomplete?: AutoComplete;
77
+ /** Size of the input */
78
+ size?: InputSize;
79
+ /** Icon to display on the left */
80
+ iconLeft?: Snippet;
81
+ /** Icon to display on the right (for custom icons) */
82
+ iconRight?: Snippet;
83
+ /** Whether to hide the label visually (still accessible) */
84
+ hideLabel?: boolean;
85
+ /** Additional CSS classes for the wrapper */
86
+ class?: string;
87
+ /** Format function to transform input value */
88
+ formatFn?: (value: string) => string;
89
+ /** Input handler */
90
+ oninput?: (event: Event) => void;
91
+ /** Blur handler */
92
+ onblur?: (event: FocusEvent) => void;
93
+ /** Focus handler */
94
+ onfocus?: (event: FocusEvent) => void;
95
+ /** Test ID for e2e testing (Playwright, Cypress) */
96
+ testId?: string;
97
+ /** Show validation state immediately without waiting for interaction */
98
+ validateOnMount?: boolean;
99
+ }
100
+
101
+ let {
102
+ id = generateId('input'),
103
+ name,
104
+ type = 'text',
105
+ label,
106
+ value = '',
107
+ placeholder = '',
108
+ required = false,
109
+ disabled = false,
110
+ readonly = false,
111
+ error = '',
112
+ hint = '',
113
+ successMessage = '',
114
+ maxLength,
115
+ minLength,
116
+ showCharCount = false,
117
+ pattern,
118
+ inputMode = 'text',
119
+ autocomplete = 'off',
120
+ size = 'md',
121
+ iconLeft,
122
+ iconRight,
123
+ hideLabel = false,
124
+ class: className = '',
125
+ formatFn,
126
+ oninput,
127
+ onblur,
128
+ onfocus,
129
+ testId,
130
+ validateOnMount = false,
131
+ ...restProps
132
+ }: Props = $props();
133
+
134
+ // Internal state
135
+ let isFocused = $state(false);
136
+ let hasInteracted = $state(false);
137
+
138
+ // Derived states
139
+ // Validation shows: after interaction OR if validateOnMount is enabled
140
+ const shouldValidate = $derived(hasInteracted || validateOnMount);
141
+ const showError = $derived(shouldValidate && !!error);
142
+ const showSuccess = $derived(shouldValidate && !error && !!value && !!successMessage);
143
+ const isValid = $derived(shouldValidate && !error && !!value && !!successMessage);
144
+ const errorId = $derived(`${id}-error`);
145
+ const hintId = $derived(`${id}-hint`);
146
+
147
+ function handleInput(e: Event) {
148
+ const target = e.target as HTMLInputElement;
149
+ let newValue = target.value;
150
+
151
+ if (formatFn) {
152
+ newValue = formatFn(newValue);
153
+ target.value = newValue;
154
+ }
155
+
156
+ oninput?.(e);
157
+ }
158
+
159
+ function handleBlur(e: FocusEvent) {
160
+ isFocused = false;
161
+ hasInteracted = true;
162
+ onblur?.(e);
163
+ }
164
+
165
+ function handleFocus(e: FocusEvent) {
166
+ isFocused = true;
167
+ onfocus?.(e);
168
+ }
169
+
170
+ // Build aria-describedby
171
+ const ariaDescribedBy = $derived(
172
+ [error && errorId, hint && hintId].filter(Boolean).join(' ') || undefined
173
+ );
174
+ </script>
175
+
176
+ <div class={cn('input-wrapper', `input-${size}`, disabled && 'input-disabled', className)}>
177
+ <!-- Label -->
178
+ <label for={id} class={cn('input-label', hideLabel && 'sr-only')}>
179
+ {label}
180
+ {#if required}
181
+ <span class="input-required" aria-hidden="true">*</span>
182
+ {/if}
183
+ {#if hint && !hideLabel}
184
+ <span class="input-hint-icon" title={hint}>
185
+ <Info size={14} />
186
+ </span>
187
+ {/if}
188
+ </label>
189
+
190
+ <!-- Input wrapper -->
191
+ <div class="input-field-wrapper">
192
+ {#if iconLeft}
193
+ <span class={cn('input-icon-left', isFocused && 'input-icon-focused')} aria-hidden="true">
194
+ {@render iconLeft()}
195
+ </span>
196
+ {/if}
197
+
198
+ <input
199
+ {id}
200
+ {name}
201
+ {type}
202
+ {value}
203
+ {placeholder}
204
+ {disabled}
205
+ {readonly}
206
+ {required}
207
+ {pattern}
208
+ maxlength={maxLength}
209
+ minlength={minLength}
210
+ inputmode={inputMode}
211
+ {autocomplete}
212
+ aria-invalid={showError}
213
+ aria-describedby={ariaDescribedBy}
214
+ aria-required={required}
215
+ data-testid={testId}
216
+ class={cn(
217
+ 'input-field',
218
+ iconLeft && 'input-has-icon-left',
219
+ (iconRight || showError || isValid) && 'input-has-icon-right',
220
+ showError && 'input-error',
221
+ isValid && 'input-valid'
222
+ )}
223
+ oninput={handleInput}
224
+ onblur={handleBlur}
225
+ onfocus={handleFocus}
226
+ {...restProps}
227
+ />
228
+
229
+ <!-- Status icon or custom right icon -->
230
+ {#if showError}
231
+ <span class="input-icon-right input-icon-error" aria-hidden="true">
232
+ <AlertCircle size={18} />
233
+ </span>
234
+ {:else if isValid}
235
+ <span class="input-icon-right input-icon-success" aria-hidden="true">
236
+ <Check size={18} />
237
+ </span>
238
+ {:else if iconRight}
239
+ <span class="input-icon-right" aria-hidden="true">
240
+ {@render iconRight()}
241
+ </span>
242
+ {/if}
243
+ </div>
244
+
245
+ <!-- Footer messages -->
246
+ <div class="input-footer">
247
+ <div class="input-messages">
248
+ {#if showError}
249
+ <p id={errorId} class="input-message input-message-error" role="alert" aria-live="assertive">
250
+ {error}
251
+ </p>
252
+ {:else if showSuccess}
253
+ <p class="input-message input-message-success">
254
+ {successMessage}
255
+ </p>
256
+ {:else if hint && isFocused}
257
+ <p id={hintId} class="input-message input-message-hint">
258
+ {hint}
259
+ </p>
260
+ {/if}
261
+ </div>
262
+
263
+ {#if showCharCount && maxLength}
264
+ <span class={cn('input-char-count', value.length > maxLength * 0.9 && 'input-char-count-warning')}>
265
+ {value.length}/{maxLength}
266
+ </span>
267
+ {/if}
268
+ </div>
269
+ </div>
270
+
271
+ <style>
272
+ /* ========================================
273
+ BASE WRAPPER
274
+ ======================================== */
275
+ .input-wrapper {
276
+ display: flex;
277
+ flex-direction: column;
278
+ gap: 0.375rem;
279
+ }
280
+
281
+ .input-disabled {
282
+ opacity: 0.6;
283
+ }
284
+
285
+ /* ========================================
286
+ LABEL
287
+ ======================================== */
288
+ .input-label {
289
+ display: flex;
290
+ align-items: center;
291
+ gap: 0.375rem;
292
+ font-weight: 500;
293
+ color: var(--ui-text-primary);
294
+ }
295
+
296
+ .input-sm .input-label {
297
+ font-size: 0.75rem;
298
+ }
299
+
300
+ .input-md .input-label,
301
+ .input-lg .input-label {
302
+ font-size: 0.875rem;
303
+ }
304
+
305
+ .input-required {
306
+ color: rgb(var(--ui-color-error));
307
+ }
308
+
309
+ :global([data-theme='dark']) .input-required {
310
+ color: rgb(var(--ui-color-error-light));
311
+ }
312
+
313
+ @media (prefers-color-scheme: dark) {
314
+ :global(:root:not([data-theme='light'])) .input-required {
315
+ color: rgb(var(--ui-color-error-light));
316
+ }
317
+ }
318
+
319
+ .input-hint-icon {
320
+ color: var(--ui-text-tertiary);
321
+ cursor: help;
322
+ }
323
+
324
+ /* ========================================
325
+ FIELD WRAPPER
326
+ ======================================== */
327
+ .input-field-wrapper {
328
+ position: relative;
329
+ display: flex;
330
+ align-items: center;
331
+ }
332
+
333
+ /* ========================================
334
+ INPUT FIELD
335
+ ======================================== */
336
+ .input-field {
337
+ width: 100%;
338
+ border: 2px solid var(--ui-border-default);
339
+ border-radius: 0.5rem;
340
+ background: var(--ui-bg-secondary);
341
+ color: var(--ui-text-primary);
342
+ transition: all 0.2s ease;
343
+ }
344
+
345
+ .input-field::placeholder {
346
+ color: var(--ui-text-tertiary);
347
+ }
348
+
349
+ .input-field:focus-visible {
350
+ outline: none;
351
+ background: var(--ui-bg-primary);
352
+ border-color: rgb(var(--ui-color-primary));
353
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-primary) / 0.4);
354
+ }
355
+
356
+ :global([data-theme='dark']) .input-field:focus-visible {
357
+ border-color: rgb(var(--ui-color-primary-light));
358
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-primary-light) / 0.5);
359
+ }
360
+
361
+ @media (prefers-color-scheme: dark) {
362
+ :global(:root:not([data-theme='light'])) .input-field:focus-visible {
363
+ border-color: rgb(var(--ui-color-primary-light));
364
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-primary-light) / 0.5);
365
+ }
366
+ }
367
+
368
+ .input-field:disabled {
369
+ cursor: not-allowed;
370
+ background: var(--ui-bg-tertiary);
371
+ }
372
+
373
+ .input-field:read-only {
374
+ background: var(--ui-bg-tertiary);
375
+ cursor: default;
376
+ }
377
+
378
+ /* Size variants */
379
+ .input-sm .input-field {
380
+ height: 2rem;
381
+ padding: 0 0.75rem;
382
+ font-size: 0.875rem;
383
+ }
384
+
385
+ .input-md .input-field {
386
+ height: 2.5rem;
387
+ padding: 0 0.75rem;
388
+ font-size: 0.875rem;
389
+ }
390
+
391
+ .input-lg .input-field {
392
+ height: 3rem;
393
+ padding: 0 1rem;
394
+ font-size: 1rem;
395
+ }
396
+
397
+ /* Icon padding adjustments - use !important to override size-specific padding */
398
+ .input-field.input-has-icon-left {
399
+ padding-left: 2.25rem !important;
400
+ }
401
+
402
+ .input-field.input-has-icon-right {
403
+ padding-right: 2.25rem !important;
404
+ }
405
+
406
+ /* Error state */
407
+ .input-error {
408
+ border-color: rgb(var(--ui-color-error));
409
+ }
410
+
411
+ .input-error:focus-visible {
412
+ border-color: rgb(var(--ui-color-error));
413
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-error) / 0.4);
414
+ }
415
+
416
+ :global([data-theme='dark']) .input-error {
417
+ border-color: rgb(var(--ui-color-error-light));
418
+ }
419
+
420
+ :global([data-theme='dark']) .input-error:focus-visible {
421
+ border-color: rgb(var(--ui-color-error-light));
422
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-error-light) / 0.5);
423
+ }
424
+
425
+ @media (prefers-color-scheme: dark) {
426
+ :global(:root:not([data-theme='light'])) .input-error {
427
+ border-color: rgb(var(--ui-color-error-light));
428
+ }
429
+
430
+ :global(:root:not([data-theme='light'])) .input-error:focus-visible {
431
+ border-color: rgb(var(--ui-color-error-light));
432
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-error-light) / 0.5);
433
+ }
434
+ }
435
+
436
+ /* Valid state */
437
+ .input-valid {
438
+ border-color: rgb(var(--ui-color-success));
439
+ }
440
+
441
+ .input-valid:focus-visible {
442
+ border-color: rgb(var(--ui-color-success));
443
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-success) / 0.4);
444
+ }
445
+
446
+ :global([data-theme='dark']) .input-valid {
447
+ border-color: rgb(var(--ui-color-success-light));
448
+ }
449
+
450
+ :global([data-theme='dark']) .input-valid:focus-visible {
451
+ border-color: rgb(var(--ui-color-success-light));
452
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-success-light) / 0.5);
453
+ }
454
+
455
+ @media (prefers-color-scheme: dark) {
456
+ :global(:root:not([data-theme='light'])) .input-valid {
457
+ border-color: rgb(var(--ui-color-success-light));
458
+ }
459
+
460
+ :global(:root:not([data-theme='light'])) .input-valid:focus-visible {
461
+ border-color: rgb(var(--ui-color-success-light));
462
+ box-shadow: 0 0 0 3px rgb(var(--ui-color-success-light) / 0.5);
463
+ }
464
+ }
465
+
466
+ /* ========================================
467
+ ICONS
468
+ ======================================== */
469
+ .input-icon-left {
470
+ position: absolute;
471
+ left: 0.75rem;
472
+ top: 50%;
473
+ transform: translateY(-50%);
474
+ z-index: 1;
475
+ pointer-events: none;
476
+ color: var(--ui-text-tertiary);
477
+ transition: color 0.2s ease;
478
+ }
479
+
480
+ .input-icon-focused {
481
+ color: rgb(var(--ui-color-primary));
482
+ }
483
+
484
+ :global([data-theme='dark']) .input-icon-focused {
485
+ color: rgb(var(--ui-color-primary-light));
486
+ }
487
+
488
+ @media (prefers-color-scheme: dark) {
489
+ :global(:root:not([data-theme='light'])) .input-icon-focused {
490
+ color: rgb(var(--ui-color-primary-light));
491
+ }
492
+ }
493
+
494
+ .input-icon-right {
495
+ position: absolute;
496
+ right: 0.75rem;
497
+ top: 50%;
498
+ transform: translateY(-50%);
499
+ z-index: 1;
500
+ pointer-events: none;
501
+ color: var(--ui-text-tertiary);
502
+ }
503
+
504
+ .input-icon-error {
505
+ color: rgb(var(--ui-color-error));
506
+ }
507
+
508
+ :global([data-theme='dark']) .input-icon-error {
509
+ color: rgb(var(--ui-color-error-light));
510
+ }
511
+
512
+ @media (prefers-color-scheme: dark) {
513
+ :global(:root:not([data-theme='light'])) .input-icon-error {
514
+ color: rgb(var(--ui-color-error-light));
515
+ }
516
+ }
517
+
518
+ .input-icon-success {
519
+ color: rgb(var(--ui-color-success));
520
+ }
521
+
522
+ :global([data-theme='dark']) .input-icon-success {
523
+ color: rgb(var(--ui-color-success-light));
524
+ }
525
+
526
+ @media (prefers-color-scheme: dark) {
527
+ :global(:root:not([data-theme='light'])) .input-icon-success {
528
+ color: rgb(var(--ui-color-success-light));
529
+ }
530
+ }
531
+
532
+ /* ========================================
533
+ FOOTER & MESSAGES
534
+ ======================================== */
535
+ .input-footer {
536
+ display: flex;
537
+ justify-content: space-between;
538
+ align-items: center;
539
+ min-height: 1.25rem;
540
+ }
541
+
542
+ .input-messages {
543
+ flex: 1;
544
+ }
545
+
546
+ .input-message {
547
+ font-size: 0.75rem;
548
+ animation: slide-in 0.2s ease-out;
549
+ }
550
+
551
+ .input-message-error {
552
+ color: rgb(var(--ui-color-error));
553
+ }
554
+
555
+ :global([data-theme='dark']) .input-message-error {
556
+ color: rgb(var(--ui-color-error-light));
557
+ }
558
+
559
+ @media (prefers-color-scheme: dark) {
560
+ :global(:root:not([data-theme='light'])) .input-message-error {
561
+ color: rgb(var(--ui-color-error-light));
562
+ }
563
+ }
564
+
565
+ .input-message-success {
566
+ color: rgb(var(--ui-color-success));
567
+ }
568
+
569
+ :global([data-theme='dark']) .input-message-success {
570
+ color: rgb(var(--ui-color-success-light));
571
+ }
572
+
573
+ @media (prefers-color-scheme: dark) {
574
+ :global(:root:not([data-theme='light'])) .input-message-success {
575
+ color: rgb(var(--ui-color-success-light));
576
+ }
577
+ }
578
+
579
+ .input-message-hint {
580
+ color: var(--ui-text-tertiary);
581
+ }
582
+
583
+ .input-char-count {
584
+ font-size: 0.75rem;
585
+ margin-left: 0.5rem;
586
+ color: var(--ui-text-tertiary);
587
+ }
588
+
589
+ .input-char-count-warning {
590
+ color: rgb(var(--ui-color-warning));
591
+ }
592
+
593
+ :global([data-theme='dark']) .input-char-count-warning {
594
+ color: rgb(var(--ui-color-warning-light));
595
+ }
596
+
597
+ @media (prefers-color-scheme: dark) {
598
+ :global(:root:not([data-theme='light'])) .input-char-count-warning {
599
+ color: rgb(var(--ui-color-warning-light));
600
+ }
601
+ }
602
+
603
+ /* ========================================
604
+ UTILITIES
605
+ ======================================== */
606
+ .sr-only {
607
+ position: absolute;
608
+ width: 1px;
609
+ height: 1px;
610
+ padding: 0;
611
+ margin: -1px;
612
+ overflow: hidden;
613
+ clip: rect(0, 0, 0, 0);
614
+ white-space: nowrap;
615
+ border: 0;
616
+ }
617
+
618
+ @keyframes slide-in {
619
+ from {
620
+ opacity: 0;
621
+ transform: translateY(-4px);
622
+ }
623
+ to {
624
+ opacity: 1;
625
+ transform: translateY(0);
626
+ }
627
+ }
628
+ </style>
@@ -0,0 +1,97 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLInputAttributes } from 'svelte/elements';
3
+ import type { InputMode, AutoComplete } from '../../types/index.js';
4
+ type InputSize = 'sm' | 'md' | 'lg';
5
+ type InputType = 'text' | 'email' | 'password' | 'tel' | 'url' | 'search' | 'number';
6
+ interface Props extends Omit<HTMLInputAttributes, 'class' | 'size'> {
7
+ /** Unique identifier for the input */
8
+ id?: string;
9
+ /** Form field name */
10
+ name?: string;
11
+ /** Input type */
12
+ type?: InputType;
13
+ /** Label text for the input */
14
+ label: string;
15
+ /** Current value */
16
+ value?: string;
17
+ /** Placeholder text */
18
+ placeholder?: string;
19
+ /** Whether the field is required */
20
+ required?: boolean;
21
+ /** Whether the field is disabled */
22
+ disabled?: boolean;
23
+ /** Whether the field is read-only */
24
+ readonly?: boolean;
25
+ /** Error message to display */
26
+ error?: string;
27
+ /** Hint text to display below the input */
28
+ hint?: string;
29
+ /** Success message when valid */
30
+ successMessage?: string;
31
+ /** Maximum character length */
32
+ maxLength?: number;
33
+ /** Minimum character length */
34
+ minLength?: number;
35
+ /** Whether to show character count */
36
+ showCharCount?: boolean;
37
+ /** Pattern for validation (regex) */
38
+ pattern?: string;
39
+ /** Input mode for mobile keyboards */
40
+ inputMode?: InputMode;
41
+ /** Autocomplete value */
42
+ autocomplete?: AutoComplete;
43
+ /** Size of the input */
44
+ size?: InputSize;
45
+ /** Icon to display on the left */
46
+ iconLeft?: Snippet;
47
+ /** Icon to display on the right (for custom icons) */
48
+ iconRight?: Snippet;
49
+ /** Whether to hide the label visually (still accessible) */
50
+ hideLabel?: boolean;
51
+ /** Additional CSS classes for the wrapper */
52
+ class?: string;
53
+ /** Format function to transform input value */
54
+ formatFn?: (value: string) => string;
55
+ /** Input handler */
56
+ oninput?: (event: Event) => void;
57
+ /** Blur handler */
58
+ onblur?: (event: FocusEvent) => void;
59
+ /** Focus handler */
60
+ onfocus?: (event: FocusEvent) => void;
61
+ /** Test ID for e2e testing (Playwright, Cypress) */
62
+ testId?: string;
63
+ /** Show validation state immediately without waiting for interaction */
64
+ validateOnMount?: boolean;
65
+ }
66
+ /**
67
+ * TextInput
68
+ *
69
+ * A fully accessible text input component with validation states,
70
+ * icons, character counting, and comprehensive ARIA support.
71
+ *
72
+ * Designed for healthcare applications with proper error handling
73
+ * and clear visual feedback.
74
+ *
75
+ * @example
76
+ * <TextInput
77
+ * id="patient-name"
78
+ * label="Patient Name"
79
+ * value={patientName}
80
+ * oninput={(e) => patientName = e.target.value}
81
+ * required
82
+ * />
83
+ *
84
+ * @example
85
+ * <TextInput
86
+ * id="member-id"
87
+ * label="Member ID"
88
+ * value={memberId}
89
+ * error={memberIdError}
90
+ * hint="Enter your insurance member ID"
91
+ * oninput={(e) => handleInput(e)}
92
+ * />
93
+ */
94
+ declare const TextInput: import("svelte").Component<Props, {}, "">;
95
+ type TextInput = ReturnType<typeof TextInput>;
96
+ export default TextInput;
97
+ //# sourceMappingURL=TextInput.svelte.d.ts.map