@salmexio/ui 0.1.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 (82) hide show
  1. package/README.md +7 -0
  2. package/dist/app.html +11 -0
  3. package/dist/feedback/Alert/Alert.svelte +538 -0
  4. package/dist/feedback/Alert/Alert.svelte.d.ts +30 -0
  5. package/dist/feedback/Alert/Alert.svelte.d.ts.map +1 -0
  6. package/dist/feedback/Alert/index.d.ts +2 -0
  7. package/dist/feedback/Alert/index.d.ts.map +1 -0
  8. package/dist/feedback/Alert/index.js +1 -0
  9. package/dist/feedback/Spinner/Spinner.svelte +421 -0
  10. package/dist/feedback/Spinner/Spinner.svelte.d.ts +25 -0
  11. package/dist/feedback/Spinner/Spinner.svelte.d.ts.map +1 -0
  12. package/dist/feedback/Spinner/index.d.ts +2 -0
  13. package/dist/feedback/Spinner/index.d.ts.map +1 -0
  14. package/dist/feedback/Spinner/index.js +1 -0
  15. package/dist/feedback/index.d.ts +3 -0
  16. package/dist/feedback/index.d.ts.map +1 -0
  17. package/dist/feedback/index.js +2 -0
  18. package/dist/forms/TextInput/TextInput.svelte +576 -0
  19. package/dist/forms/TextInput/TextInput.svelte.d.ts +50 -0
  20. package/dist/forms/TextInput/TextInput.svelte.d.ts.map +1 -0
  21. package/dist/forms/TextInput/index.d.ts +2 -0
  22. package/dist/forms/TextInput/index.d.ts.map +1 -0
  23. package/dist/forms/TextInput/index.js +1 -0
  24. package/dist/forms/index.d.ts +2 -0
  25. package/dist/forms/index.d.ts.map +1 -0
  26. package/dist/forms/index.js +1 -0
  27. package/dist/index.d.ts +7 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +6 -0
  30. package/dist/layout/Card/Card.svelte +283 -0
  31. package/dist/layout/Card/Card.svelte.d.ts +35 -0
  32. package/dist/layout/Card/Card.svelte.d.ts.map +1 -0
  33. package/dist/layout/Card/index.d.ts +2 -0
  34. package/dist/layout/Card/index.d.ts.map +1 -0
  35. package/dist/layout/Card/index.js +1 -0
  36. package/dist/layout/Container/Container.svelte +202 -0
  37. package/dist/layout/Container/Container.svelte.d.ts +25 -0
  38. package/dist/layout/Container/Container.svelte.d.ts.map +1 -0
  39. package/dist/layout/Container/index.d.ts +2 -0
  40. package/dist/layout/Container/index.d.ts.map +1 -0
  41. package/dist/layout/Container/index.js +1 -0
  42. package/dist/layout/index.d.ts +3 -0
  43. package/dist/layout/index.d.ts.map +1 -0
  44. package/dist/layout/index.js +2 -0
  45. package/dist/navigation/Tabs/Tabs.svelte +448 -0
  46. package/dist/navigation/Tabs/Tabs.svelte.d.ts +59 -0
  47. package/dist/navigation/Tabs/Tabs.svelte.d.ts.map +1 -0
  48. package/dist/navigation/Tabs/index.d.ts +3 -0
  49. package/dist/navigation/Tabs/index.d.ts.map +1 -0
  50. package/dist/navigation/Tabs/index.js +1 -0
  51. package/dist/navigation/index.d.ts +3 -0
  52. package/dist/navigation/index.d.ts.map +1 -0
  53. package/dist/navigation/index.js +1 -0
  54. package/dist/primitives/Badge/Badge.svelte +351 -0
  55. package/dist/primitives/Badge/Badge.svelte.d.ts +35 -0
  56. package/dist/primitives/Badge/Badge.svelte.d.ts.map +1 -0
  57. package/dist/primitives/Badge/index.d.ts +2 -0
  58. package/dist/primitives/Badge/index.d.ts.map +1 -0
  59. package/dist/primitives/Badge/index.js +1 -0
  60. package/dist/primitives/Button/Button.svelte +383 -0
  61. package/dist/primitives/Button/Button.svelte.d.ts +47 -0
  62. package/dist/primitives/Button/Button.svelte.d.ts.map +1 -0
  63. package/dist/primitives/Button/index.d.ts +2 -0
  64. package/dist/primitives/Button/index.d.ts.map +1 -0
  65. package/dist/primitives/Button/index.js +1 -0
  66. package/dist/primitives/index.d.ts +3 -0
  67. package/dist/primitives/index.d.ts.map +1 -0
  68. package/dist/primitives/index.js +2 -0
  69. package/dist/routes/+layout.svelte +5 -0
  70. package/dist/routes/+layout.svelte.d.ts +6 -0
  71. package/dist/routes/+layout.svelte.d.ts.map +1 -0
  72. package/dist/routes/+page.svelte +6 -0
  73. package/dist/routes/+page.svelte.d.ts +27 -0
  74. package/dist/routes/+page.svelte.d.ts.map +1 -0
  75. package/dist/styles/tokens.css +340 -0
  76. package/dist/utils/cn.d.ts +9 -0
  77. package/dist/utils/cn.d.ts.map +1 -0
  78. package/dist/utils/cn.js +29 -0
  79. package/dist/utils/index.d.ts +2 -0
  80. package/dist/utils/index.d.ts.map +1 -0
  81. package/dist/utils/index.js +1 -0
  82. package/package.json +84 -0
@@ -0,0 +1,576 @@
1
+ <!--
2
+ @component TextInput
3
+
4
+ Win2K × Basquiat — Canvas-style text field with bold borders.
5
+ Label, error, hint, prefix/suffix, clearable, password toggle. Full a11y.
6
+ -->
7
+ <script lang="ts">
8
+ import type { Snippet } from 'svelte';
9
+ import type { HTMLInputAttributes } from 'svelte/elements';
10
+ import { cn } from '../../utils/cn.js';
11
+
12
+ type InputSize = 'sm' | 'md' | 'lg';
13
+ type InputMode = 'none' | 'text' | 'search' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal';
14
+
15
+ interface Props extends Omit<HTMLInputAttributes, 'class' | 'size' | 'inputmode' | 'autocomplete'> {
16
+ id?: string;
17
+ name?: string;
18
+ type?: 'text' | 'email' | 'password' | 'tel' | 'url' | 'search' | 'number';
19
+ label: string;
20
+ value?: string;
21
+ placeholder?: string;
22
+ required?: boolean;
23
+ disabled?: boolean;
24
+ readonly?: boolean;
25
+ error?: string;
26
+ hint?: string;
27
+ successMessage?: string;
28
+ maxLength?: number;
29
+ minLength?: number;
30
+ showCharCount?: boolean;
31
+ pattern?: string;
32
+ inputMode?: InputMode;
33
+ autocomplete?: string;
34
+ size?: InputSize;
35
+ iconLeft?: Snippet;
36
+ iconRight?: Snippet;
37
+ hideLabel?: boolean;
38
+ class?: string;
39
+ prefix?: string;
40
+ suffix?: string;
41
+ clearable?: boolean;
42
+ showPasswordToggle?: boolean;
43
+ loading?: boolean;
44
+ oninput?: (event: Event) => void;
45
+ onblur?: (event: FocusEvent) => void;
46
+ onfocus?: (event: FocusEvent) => void;
47
+ onclear?: () => void;
48
+ testId?: string;
49
+ validateOnMount?: boolean;
50
+ }
51
+
52
+ let {
53
+ id = `input-${Math.random().toString(36).slice(2, 9)}`,
54
+ name,
55
+ type = 'text',
56
+ label,
57
+ value = $bindable(''),
58
+ placeholder = '',
59
+ required = false,
60
+ disabled = false,
61
+ readonly = false,
62
+ error = '',
63
+ hint = '',
64
+ successMessage = '',
65
+ maxLength,
66
+ minLength,
67
+ showCharCount = false,
68
+ pattern,
69
+ inputMode = 'text',
70
+ autocomplete = 'off',
71
+ size = 'md',
72
+ iconLeft,
73
+ iconRight,
74
+ hideLabel = false,
75
+ class: className = '',
76
+ prefix,
77
+ suffix,
78
+ clearable = false,
79
+ showPasswordToggle = false,
80
+ loading = false,
81
+ oninput,
82
+ onblur,
83
+ onfocus,
84
+ onclear,
85
+ testId,
86
+ validateOnMount = false,
87
+ ...restProps
88
+ }: Props = $props();
89
+
90
+ let inputRef = $state<HTMLInputElement | undefined>();
91
+ let isFocused = $state(false);
92
+ let hasInteracted = $state(false);
93
+ let passwordVisible = $state(false);
94
+
95
+ const shouldValidate = $derived(hasInteracted || validateOnMount);
96
+ const showError = $derived(shouldValidate && !!error);
97
+ const showSuccess = $derived(shouldValidate && !error && !!value && !!successMessage);
98
+ const effectiveType = $derived(type === 'password' && passwordVisible ? 'text' : type);
99
+ const errorId = $derived(`${id}-error`);
100
+ const hintId = $derived(`${id}-hint`);
101
+ const hasRightContent = $derived(
102
+ loading ||
103
+ showError ||
104
+ showSuccess ||
105
+ (clearable && value && !disabled && !readonly) ||
106
+ (showPasswordToggle && type === 'password') ||
107
+ !!iconRight
108
+ );
109
+ const ariaDescribedBy = $derived(
110
+ [error && errorId, hint && hintId].filter(Boolean).join(' ') || undefined
111
+ );
112
+
113
+ function handleInput(e: Event) {
114
+ const target = e.target as HTMLInputElement;
115
+ value = target.value;
116
+ oninput?.(e);
117
+ }
118
+
119
+ function handleBlur(e: FocusEvent) {
120
+ isFocused = false;
121
+ hasInteracted = true;
122
+ onblur?.(e);
123
+ }
124
+
125
+ function handleFocus(e: FocusEvent) {
126
+ isFocused = true;
127
+ onfocus?.(e);
128
+ }
129
+
130
+ function handleClear() {
131
+ value = '';
132
+ if (inputRef) {
133
+ inputRef.value = '';
134
+ inputRef.focus();
135
+ }
136
+ onclear?.();
137
+ }
138
+
139
+ function togglePassword() {
140
+ passwordVisible = !passwordVisible;
141
+ inputRef?.focus();
142
+ }
143
+
144
+ function handleKeyDown(e: KeyboardEvent) {
145
+ if (e.key === 'Escape' && clearable && value && !disabled && !readonly) {
146
+ e.preventDefault();
147
+ handleClear();
148
+ }
149
+ }
150
+ </script>
151
+
152
+ <div
153
+ class={cn(
154
+ 'salmex-input-wrapper',
155
+ `salmex-input-${size}`,
156
+ disabled && 'salmex-input-disabled',
157
+ className
158
+ )}
159
+ >
160
+ <label for={id} class={cn('salmex-input-label', hideLabel && 'salmex-sr-only')}>
161
+ {label}
162
+ {#if required}
163
+ <span class="salmex-input-required" aria-hidden="true">*</span>
164
+ {/if}
165
+ </label>
166
+
167
+ <div
168
+ class={cn(
169
+ 'salmex-input-field-wrapper',
170
+ isFocused && 'salmex-input-focused',
171
+ showError && 'salmex-input-error',
172
+ showSuccess && 'salmex-input-valid',
173
+ disabled && 'salmex-input-disabled-wrap'
174
+ )}
175
+ >
176
+ {#if prefix}
177
+ <span class="salmex-input-prefix" aria-hidden="true">{prefix}</span>
178
+ {/if}
179
+ {#if iconLeft}
180
+ <span class="salmex-input-icon-left" aria-hidden="true">
181
+ {@render iconLeft()}
182
+ </span>
183
+ {/if}
184
+
185
+ <input
186
+ bind:this={inputRef}
187
+ {id}
188
+ {name}
189
+ type={effectiveType}
190
+ bind:value
191
+ {placeholder}
192
+ {disabled}
193
+ {readonly}
194
+ {required}
195
+ {pattern}
196
+ maxlength={maxLength}
197
+ minlength={minLength}
198
+ inputmode={inputMode}
199
+ autocomplete={autocomplete as import('svelte/elements').HTMLInputAttributes['autocomplete']}
200
+ aria-invalid={showError}
201
+ aria-describedby={ariaDescribedBy}
202
+ aria-required={required}
203
+ data-testid={testId}
204
+ class={cn(
205
+ 'salmex-input-field',
206
+ prefix && 'salmex-input-has-prefix',
207
+ suffix && 'salmex-input-has-suffix',
208
+ iconLeft && 'salmex-input-has-icon-left',
209
+ hasRightContent && 'salmex-input-has-right'
210
+ )}
211
+ oninput={handleInput}
212
+ onblur={handleBlur}
213
+ onfocus={handleFocus}
214
+ onkeydown={handleKeyDown}
215
+ {...restProps}
216
+ />
217
+
218
+ <div class="salmex-input-right">
219
+ {#if loading}
220
+ <span class="salmex-input-loading" aria-hidden="true">
221
+ <svg class="salmex-input-spinner" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
222
+ <circle cx="12" cy="12" r="10" stroke-opacity="0.25"/>
223
+ <path d="M12 2a10 10 0 0 1 10 10" stroke-linecap="round"/>
224
+ </svg>
225
+ </span>
226
+ {:else if showError}
227
+ <span class="salmex-input-icon-error" aria-hidden="true">
228
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>
229
+ </span>
230
+ {:else if showSuccess}
231
+ <span class="salmex-input-icon-success" aria-hidden="true">
232
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
233
+ </span>
234
+ {:else if iconRight}
235
+ <span class="salmex-input-icon-right" aria-hidden="true">
236
+ {@render iconRight()}
237
+ </span>
238
+ {/if}
239
+
240
+ {#if clearable && value && !disabled && !readonly}
241
+ <button
242
+ type="button"
243
+ class="salmex-input-clear"
244
+ onclick={handleClear}
245
+ aria-label="Clear input"
246
+ tabindex={-1}
247
+ >
248
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
249
+ </button>
250
+ {/if}
251
+
252
+ {#if showPasswordToggle && type === 'password'}
253
+ <button
254
+ type="button"
255
+ class="salmex-input-password-toggle"
256
+ onclick={togglePassword}
257
+ aria-label={passwordVisible ? 'Hide password' : 'Show password'}
258
+ aria-pressed={passwordVisible}
259
+ tabindex={-1}
260
+ >
261
+ {#if passwordVisible}
262
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>
263
+ {:else}
264
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
265
+ {/if}
266
+ </button>
267
+ {/if}
268
+ </div>
269
+
270
+ {#if suffix}
271
+ <span class="salmex-input-suffix" aria-hidden="true">{suffix}</span>
272
+ {/if}
273
+ </div>
274
+
275
+ <div class="salmex-input-footer">
276
+ <div class="salmex-input-messages">
277
+ {#if showError}
278
+ <p id={errorId} class="salmex-input-message salmex-input-message-error" role="alert" aria-live="assertive">
279
+ {error}
280
+ </p>
281
+ {:else if showSuccess}
282
+ <p class="salmex-input-message salmex-input-message-success">{successMessage}</p>
283
+ {:else if hint}
284
+ <p id={hintId} class="salmex-input-message salmex-input-message-hint">{hint}</p>
285
+ {/if}
286
+ </div>
287
+ {#if showCharCount && maxLength !== undefined}
288
+ <span class="salmex-input-charcount" class:salmex-input-charcount-warn={value.length > maxLength * 0.9}>
289
+ {value.length}/{maxLength}
290
+ </span>
291
+ {/if}
292
+ </div>
293
+ </div>
294
+
295
+ <style>
296
+ .salmex-input-wrapper {
297
+ display: flex;
298
+ flex-direction: column;
299
+ gap: var(--salmex-space-1);
300
+ }
301
+
302
+ .salmex-input-disabled {
303
+ opacity: 0.6;
304
+ }
305
+
306
+ .salmex-input-label {
307
+ font-family: var(--salmex-font-system);
308
+ font-weight: 700;
309
+ font-size: var(--salmex-font-size-sm);
310
+ text-transform: uppercase;
311
+ letter-spacing: 0.3px;
312
+ color: rgb(var(--salmex-text-primary));
313
+ }
314
+
315
+ .salmex-input-required {
316
+ color: rgb(var(--salmex-street-red));
317
+ }
318
+
319
+ .salmex-sr-only {
320
+ position: absolute;
321
+ width: 1px;
322
+ height: 1px;
323
+ padding: 0;
324
+ margin: -1px;
325
+ overflow: hidden;
326
+ clip: rect(0, 0, 0, 0);
327
+ white-space: nowrap;
328
+ border: 0;
329
+ }
330
+
331
+ .salmex-input-field-wrapper {
332
+ position: relative;
333
+ display: flex;
334
+ align-items: center;
335
+ background: rgb(var(--salmex-button-face));
336
+ border: 2px solid rgb(var(--salmex-border-dark));
337
+ box-shadow:
338
+ inset 2px 2px 0 rgb(var(--salmex-button-highlight)),
339
+ inset -2px -2px 0 rgb(var(--salmex-button-shadow));
340
+ transition: box-shadow var(--salmex-transition-fast), border-color var(--salmex-transition-fast);
341
+ }
342
+
343
+ /* Focus - yellow ring with dark edge in light theme for visibility */
344
+ .salmex-input-focused {
345
+ border-color: rgb(var(--salmex-midnight-black));
346
+ box-shadow:
347
+ inset 2px 2px 0 rgb(var(--salmex-button-highlight)),
348
+ inset -2px -2px 0 rgb(var(--salmex-button-shadow)),
349
+ 0 0 0 2px rgb(var(--salmex-midnight-black)),
350
+ 0 0 0 5px rgb(var(--salmex-crown-yellow));
351
+ }
352
+
353
+ :global([data-theme='dark']) .salmex-input-focused {
354
+ border-color: rgb(var(--salmex-border-light));
355
+ box-shadow:
356
+ inset 2px 2px 0 rgb(var(--salmex-button-highlight)),
357
+ inset -2px -2px 0 rgb(var(--salmex-button-shadow)),
358
+ 0 0 0 3px rgb(var(--salmex-crown-yellow));
359
+ }
360
+
361
+ .salmex-input-error {
362
+ border-color: rgb(var(--salmex-street-red));
363
+ }
364
+
365
+ .salmex-input-valid {
366
+ border-color: rgb(var(--salmex-urban-green));
367
+ }
368
+
369
+ .salmex-input-disabled-wrap {
370
+ opacity: 0.7;
371
+ cursor: not-allowed;
372
+ background: rgb(var(--salmex-bg-tertiary));
373
+ }
374
+
375
+ .salmex-input-prefix,
376
+ .salmex-input-suffix {
377
+ display: flex;
378
+ align-items: center;
379
+ flex-shrink: 0;
380
+ padding: 0 var(--salmex-space-2);
381
+ font-family: var(--salmex-font-mono);
382
+ font-size: var(--salmex-font-size-sm);
383
+ font-weight: 600;
384
+ color: rgb(var(--salmex-text-secondary));
385
+ border-right: 2px solid rgb(var(--salmex-border-default));
386
+ }
387
+
388
+ .salmex-input-suffix {
389
+ border-right: none;
390
+ border-left: 2px solid rgb(var(--salmex-border-default));
391
+ margin-left: 0;
392
+ }
393
+
394
+ .salmex-input-icon-left {
395
+ display: flex;
396
+ align-items: center;
397
+ padding-left: var(--salmex-space-2);
398
+ color: rgb(var(--salmex-text-secondary));
399
+ }
400
+
401
+ .salmex-input-field {
402
+ flex: 1;
403
+ min-width: 0;
404
+ border: none;
405
+ background: transparent;
406
+ color: rgb(var(--salmex-text-primary));
407
+ font-family: var(--salmex-font-system);
408
+ font-size: var(--salmex-font-size-base);
409
+ outline: none;
410
+ padding: var(--salmex-space-2) var(--salmex-space-3);
411
+ }
412
+
413
+ /* Only the wrapper shows the focus ring; avoid double ring from global :focus-visible on the input */
414
+ .salmex-input-field:focus-visible {
415
+ box-shadow: none;
416
+ }
417
+
418
+ .salmex-input-field::placeholder {
419
+ color: rgb(var(--salmex-text-disabled));
420
+ }
421
+
422
+ .salmex-input-field:disabled {
423
+ cursor: not-allowed;
424
+ }
425
+
426
+ .salmex-input-has-prefix {
427
+ padding-left: var(--salmex-space-1);
428
+ }
429
+
430
+ .salmex-input-has-suffix {
431
+ padding-right: var(--salmex-space-1);
432
+ }
433
+
434
+ .salmex-input-has-icon-left {
435
+ padding-left: var(--salmex-space-1);
436
+ }
437
+
438
+ .salmex-input-has-right {
439
+ padding-right: var(--salmex-space-1);
440
+ }
441
+
442
+ .salmex-input-sm .salmex-input-field-wrapper {
443
+ min-height: 32px;
444
+ }
445
+
446
+ .salmex-input-sm .salmex-input-field {
447
+ font-size: var(--salmex-font-size-sm);
448
+ padding: var(--salmex-space-1) var(--salmex-space-2);
449
+ }
450
+
451
+ .salmex-input-md .salmex-input-field-wrapper {
452
+ min-height: 40px;
453
+ }
454
+
455
+ .salmex-input-lg .salmex-input-field-wrapper {
456
+ min-height: 48px;
457
+ }
458
+
459
+ .salmex-input-lg .salmex-input-field {
460
+ font-size: var(--salmex-font-size-md);
461
+ padding: var(--salmex-space-3) var(--salmex-space-4);
462
+ }
463
+
464
+ .salmex-input-right {
465
+ display: flex;
466
+ align-items: center;
467
+ gap: var(--salmex-space-1);
468
+ flex-shrink: 0;
469
+ padding-right: var(--salmex-space-2);
470
+ }
471
+
472
+ .salmex-input-icon-right,
473
+ .salmex-input-icon-error,
474
+ .salmex-input-icon-success {
475
+ display: flex;
476
+ align-items: center;
477
+ color: rgb(var(--salmex-text-secondary));
478
+ }
479
+
480
+ .salmex-input-icon-error {
481
+ color: rgb(var(--salmex-street-red));
482
+ }
483
+
484
+ .salmex-input-icon-success {
485
+ color: rgb(var(--salmex-urban-green));
486
+ }
487
+
488
+ .salmex-input-loading {
489
+ display: flex;
490
+ align-items: center;
491
+ color: rgb(var(--salmex-electric-blue));
492
+ }
493
+
494
+ .salmex-input-spinner {
495
+ animation: salmex-spin-input 0.8s linear infinite;
496
+ }
497
+
498
+ @keyframes salmex-spin-input {
499
+ to {
500
+ transform: rotate(360deg);
501
+ }
502
+ }
503
+
504
+ .salmex-input-clear,
505
+ .salmex-input-password-toggle {
506
+ display: flex;
507
+ align-items: center;
508
+ justify-content: center;
509
+ width: 28px;
510
+ height: 28px;
511
+ padding: 0;
512
+ border: none;
513
+ background: transparent;
514
+ color: rgb(var(--salmex-text-secondary));
515
+ cursor: pointer;
516
+ transition: background var(--salmex-transition-fast), color var(--salmex-transition-fast);
517
+ }
518
+
519
+ .salmex-input-clear:hover,
520
+ .salmex-input-password-toggle:hover {
521
+ background: rgb(var(--salmex-bg-tertiary));
522
+ color: rgb(var(--salmex-text-primary));
523
+ }
524
+
525
+ .salmex-input-clear:focus-visible,
526
+ .salmex-input-password-toggle:focus-visible {
527
+ outline: none;
528
+ box-shadow: 0 0 0 2px rgb(var(--salmex-midnight-black)), 0 0 0 5px rgb(var(--salmex-crown-yellow));
529
+ }
530
+
531
+ :global([data-theme='dark']) .salmex-input-clear:focus-visible,
532
+ :global([data-theme='dark']) .salmex-input-password-toggle:focus-visible {
533
+ box-shadow: 0 0 0 3px rgb(var(--salmex-crown-yellow));
534
+ }
535
+
536
+ .salmex-input-footer {
537
+ display: flex;
538
+ justify-content: space-between;
539
+ align-items: center;
540
+ min-height: 1.25rem;
541
+ }
542
+
543
+ .salmex-input-message {
544
+ font-size: var(--salmex-font-size-xs);
545
+ margin: 0;
546
+ }
547
+
548
+ .salmex-input-message-error {
549
+ color: rgb(var(--salmex-street-red));
550
+ font-weight: 600;
551
+ }
552
+
553
+ .salmex-input-message-success {
554
+ color: rgb(var(--salmex-urban-green));
555
+ }
556
+
557
+ .salmex-input-message-hint {
558
+ color: rgb(var(--salmex-text-secondary));
559
+ }
560
+
561
+ .salmex-input-charcount {
562
+ font-size: var(--salmex-font-size-xs);
563
+ font-family: var(--salmex-font-mono);
564
+ color: rgb(var(--salmex-text-secondary));
565
+ }
566
+
567
+ .salmex-input-charcount-warn {
568
+ color: rgb(var(--salmex-spray-orange));
569
+ }
570
+
571
+ @media (prefers-reduced-motion: reduce) {
572
+ .salmex-input-spinner {
573
+ animation: none;
574
+ }
575
+ }
576
+ </style>
@@ -0,0 +1,50 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLInputAttributes } from 'svelte/elements';
3
+ type InputSize = 'sm' | 'md' | 'lg';
4
+ type InputMode = 'none' | 'text' | 'search' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal';
5
+ interface Props extends Omit<HTMLInputAttributes, 'class' | 'size' | 'inputmode' | 'autocomplete'> {
6
+ id?: string;
7
+ name?: string;
8
+ type?: 'text' | 'email' | 'password' | 'tel' | 'url' | 'search' | 'number';
9
+ label: string;
10
+ value?: string;
11
+ placeholder?: string;
12
+ required?: boolean;
13
+ disabled?: boolean;
14
+ readonly?: boolean;
15
+ error?: string;
16
+ hint?: string;
17
+ successMessage?: string;
18
+ maxLength?: number;
19
+ minLength?: number;
20
+ showCharCount?: boolean;
21
+ pattern?: string;
22
+ inputMode?: InputMode;
23
+ autocomplete?: string;
24
+ size?: InputSize;
25
+ iconLeft?: Snippet;
26
+ iconRight?: Snippet;
27
+ hideLabel?: boolean;
28
+ class?: string;
29
+ prefix?: string;
30
+ suffix?: string;
31
+ clearable?: boolean;
32
+ showPasswordToggle?: boolean;
33
+ loading?: boolean;
34
+ oninput?: (event: Event) => void;
35
+ onblur?: (event: FocusEvent) => void;
36
+ onfocus?: (event: FocusEvent) => void;
37
+ onclear?: () => void;
38
+ testId?: string;
39
+ validateOnMount?: boolean;
40
+ }
41
+ /**
42
+ * TextInput
43
+ *
44
+ * Win2K × Basquiat — Canvas-style text field with bold borders.
45
+ * Label, error, hint, prefix/suffix, clearable, password toggle. Full a11y.
46
+ */
47
+ declare const TextInput: import("svelte").Component<Props, {}, "value">;
48
+ type TextInput = ReturnType<typeof TextInput>;
49
+ export default TextInput;
50
+ //# sourceMappingURL=TextInput.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TextInput.svelte.d.ts","sourceRoot":"","sources":["../../../src/forms/TextInput/TextInput.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAI1D,KAAK,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACpC,KAAK,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AAE9F,UAAU,KAAM,SAAQ,IAAI,CAAC,mBAAmB,EAAE,OAAO,GAAG,MAAM,GAAG,WAAW,GAAG,cAAc,CAAC;IACjG,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B;AAuNF;;;;;GAKG;AACH,QAAA,MAAM,SAAS,gDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { default as TextInput } from './TextInput.svelte';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/forms/TextInput/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1 @@
1
+ export { default as TextInput } from './TextInput.svelte';
@@ -0,0 +1,2 @@
1
+ export { TextInput } from './TextInput/index.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/forms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1 @@
1
+ export { TextInput } from './TextInput/index.js';
@@ -0,0 +1,7 @@
1
+ export * from './primitives/index.js';
2
+ export * from './navigation/index.js';
3
+ export * from './layout/index.js';
4
+ export * from './feedback/index.js';
5
+ export * from './forms/index.js';
6
+ export * from './utils/index.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from './primitives/index.js';
2
+ export * from './navigation/index.js';
3
+ export * from './layout/index.js';
4
+ export * from './feedback/index.js';
5
+ export * from './forms/index.js';
6
+ export * from './utils/index.js';