@aspect-ops/exon-ui 0.0.3 → 0.2.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 (79) hide show
  1. package/README.md +929 -54
  2. package/dist/components/Accordion/Accordion.svelte +79 -0
  3. package/dist/components/Accordion/Accordion.svelte.d.ts +10 -0
  4. package/dist/components/Accordion/AccordionItem.svelte +198 -0
  5. package/dist/components/Accordion/AccordionItem.svelte.d.ts +10 -0
  6. package/dist/components/Accordion/index.d.ts +2 -0
  7. package/dist/components/Accordion/index.js +2 -0
  8. package/dist/components/Carousel/Carousel.svelte +454 -0
  9. package/dist/components/Carousel/Carousel.svelte.d.ts +14 -0
  10. package/dist/components/Carousel/CarouselSlide.svelte +22 -0
  11. package/dist/components/Carousel/CarouselSlide.svelte.d.ts +7 -0
  12. package/dist/components/Carousel/index.d.ts +2 -0
  13. package/dist/components/Carousel/index.js +2 -0
  14. package/dist/components/Chip/Chip.svelte +461 -0
  15. package/dist/components/Chip/Chip.svelte.d.ts +17 -0
  16. package/dist/components/Chip/ChipGroup.svelte +76 -0
  17. package/dist/components/Chip/ChipGroup.svelte.d.ts +9 -0
  18. package/dist/components/Chip/index.d.ts +2 -0
  19. package/dist/components/Chip/index.js +2 -0
  20. package/dist/components/DatePicker/DatePicker.svelte +746 -0
  21. package/dist/components/DatePicker/DatePicker.svelte.d.ts +19 -0
  22. package/dist/components/DatePicker/index.d.ts +1 -0
  23. package/dist/components/DatePicker/index.js +1 -0
  24. package/dist/components/FileUpload/FileUpload.svelte +484 -0
  25. package/dist/components/FileUpload/FileUpload.svelte.d.ts +16 -0
  26. package/dist/components/FileUpload/index.d.ts +1 -0
  27. package/dist/components/FileUpload/index.js +1 -0
  28. package/dist/components/Image/Image.svelte +223 -0
  29. package/dist/components/Image/Image.svelte.d.ts +19 -0
  30. package/dist/components/Image/index.d.ts +1 -0
  31. package/dist/components/Image/index.js +1 -0
  32. package/dist/components/NumberInput/NumberInput.svelte +293 -0
  33. package/dist/components/NumberInput/NumberInput.svelte.d.ts +16 -0
  34. package/dist/components/NumberInput/index.d.ts +1 -0
  35. package/dist/components/NumberInput/index.js +1 -0
  36. package/dist/components/OTPInput/OTPInput.svelte +312 -0
  37. package/dist/components/OTPInput/OTPInput.svelte.d.ts +57 -0
  38. package/dist/components/OTPInput/index.d.ts +1 -0
  39. package/dist/components/OTPInput/index.js +1 -0
  40. package/dist/components/Pagination/Pagination.svelte +243 -0
  41. package/dist/components/Pagination/Pagination.svelte.d.ts +10 -0
  42. package/dist/components/Pagination/index.d.ts +1 -0
  43. package/dist/components/Pagination/index.js +1 -0
  44. package/dist/components/Rating/Rating.svelte +316 -0
  45. package/dist/components/Rating/Rating.svelte.d.ts +16 -0
  46. package/dist/components/Rating/index.d.ts +1 -0
  47. package/dist/components/Rating/index.js +1 -0
  48. package/dist/components/SearchInput/SearchInput.svelte +480 -0
  49. package/dist/components/SearchInput/SearchInput.svelte.d.ts +22 -0
  50. package/dist/components/SearchInput/index.d.ts +1 -0
  51. package/dist/components/SearchInput/index.js +1 -0
  52. package/dist/components/Slider/Slider.svelte +324 -0
  53. package/dist/components/Slider/Slider.svelte.d.ts +14 -0
  54. package/dist/components/Slider/index.d.ts +1 -0
  55. package/dist/components/Slider/index.js +1 -0
  56. package/dist/components/Stepper/Stepper.svelte +100 -0
  57. package/dist/components/Stepper/Stepper.svelte.d.ts +11 -0
  58. package/dist/components/Stepper/StepperStep.svelte +391 -0
  59. package/dist/components/Stepper/StepperStep.svelte.d.ts +13 -0
  60. package/dist/components/Stepper/index.d.ts +2 -0
  61. package/dist/components/Stepper/index.js +2 -0
  62. package/dist/components/TimePicker/TimePicker.svelte +803 -0
  63. package/dist/components/TimePicker/TimePicker.svelte.d.ts +17 -0
  64. package/dist/components/TimePicker/index.d.ts +1 -0
  65. package/dist/components/TimePicker/index.js +1 -0
  66. package/dist/components/ToggleGroup/ToggleGroup.svelte +91 -0
  67. package/dist/components/ToggleGroup/ToggleGroup.svelte.d.ts +13 -0
  68. package/dist/components/ToggleGroup/ToggleGroupItem.svelte +158 -0
  69. package/dist/components/ToggleGroup/ToggleGroupItem.svelte.d.ts +9 -0
  70. package/dist/components/ToggleGroup/index.d.ts +3 -0
  71. package/dist/components/ToggleGroup/index.js +2 -0
  72. package/dist/index.d.ts +13 -1
  73. package/dist/index.js +12 -0
  74. package/dist/types/data-display.d.ts +68 -0
  75. package/dist/types/index.d.ts +3 -2
  76. package/dist/types/input.d.ts +82 -0
  77. package/dist/types/input.js +2 -0
  78. package/dist/types/navigation.d.ts +15 -0
  79. package/package.json +1 -1
@@ -0,0 +1,480 @@
1
+ <script lang="ts">
2
+ import type { InputSize } from '../../types/index.js';
3
+
4
+ interface Props {
5
+ value?: string;
6
+ placeholder?: string;
7
+ suggestions?: string[];
8
+ size?: InputSize;
9
+ disabled?: boolean;
10
+ loading?: boolean;
11
+ maxSuggestions?: number;
12
+ class?: string;
13
+ oninput?: (e: Event & { currentTarget: HTMLInputElement }) => void;
14
+ onchange?: (e: Event & { currentTarget: HTMLInputElement }) => void;
15
+ onselect?: (value: string) => void;
16
+ onclear?: () => void;
17
+ }
18
+
19
+ let {
20
+ value = $bindable(''),
21
+ placeholder = 'Search...',
22
+ suggestions = [],
23
+ size = 'md',
24
+ disabled = false,
25
+ loading = false,
26
+ maxSuggestions = 5,
27
+ class: className = '',
28
+ oninput,
29
+ onchange,
30
+ onselect,
31
+ onclear
32
+ }: Props = $props();
33
+
34
+ let isOpen = $state(false);
35
+ let highlightedIndex = $state(-1);
36
+ let inputRef: HTMLInputElement;
37
+ let dropdownRef = $state<HTMLDivElement>();
38
+
39
+ // Filter and limit suggestions
40
+ const filteredSuggestions = $derived(() => {
41
+ if (!value.trim() || !suggestions.length) return [];
42
+ const filtered = suggestions.filter((suggestion) =>
43
+ suggestion.toLowerCase().includes(value.toLowerCase())
44
+ );
45
+ return filtered.slice(0, maxSuggestions);
46
+ });
47
+
48
+ const hasSuggestions = $derived(filteredSuggestions().length > 0);
49
+ const showClearButton = $derived(value.length > 0 && !disabled);
50
+ const showDropdown = $derived(isOpen && hasSuggestions && !disabled);
51
+
52
+ // Handle input changes
53
+ function handleInput(e: Event & { currentTarget: HTMLInputElement }) {
54
+ value = e.currentTarget.value;
55
+ isOpen = true;
56
+ highlightedIndex = -1;
57
+ oninput?.(e);
58
+ }
59
+
60
+ // Handle clear button click
61
+ function handleClear() {
62
+ value = '';
63
+ isOpen = false;
64
+ highlightedIndex = -1;
65
+ inputRef?.focus();
66
+ onclear?.();
67
+ }
68
+
69
+ // Handle suggestion selection
70
+ function selectSuggestion(suggestion: string) {
71
+ value = suggestion;
72
+ isOpen = false;
73
+ highlightedIndex = -1;
74
+ onselect?.(suggestion);
75
+ }
76
+
77
+ // Handle keyboard navigation
78
+ function handleKeyDown(e: KeyboardEvent) {
79
+ const suggestions = filteredSuggestions();
80
+
81
+ if (!suggestions.length) return;
82
+
83
+ switch (e.key) {
84
+ case 'ArrowDown':
85
+ e.preventDefault();
86
+ highlightedIndex = Math.min(highlightedIndex + 1, suggestions.length - 1);
87
+ isOpen = true;
88
+ break;
89
+ case 'ArrowUp':
90
+ e.preventDefault();
91
+ highlightedIndex = Math.max(highlightedIndex - 1, -1);
92
+ break;
93
+ case 'Enter':
94
+ e.preventDefault();
95
+ if (highlightedIndex >= 0 && highlightedIndex < suggestions.length) {
96
+ selectSuggestion(suggestions[highlightedIndex]);
97
+ }
98
+ break;
99
+ case 'Escape':
100
+ e.preventDefault();
101
+ isOpen = false;
102
+ highlightedIndex = -1;
103
+ inputRef?.blur();
104
+ break;
105
+ }
106
+ }
107
+
108
+ // Handle click outside to close dropdown
109
+ function handleClickOutside(e: MouseEvent) {
110
+ const target = e.target as Node;
111
+ if (inputRef && !inputRef.contains(target) && dropdownRef && !dropdownRef.contains(target)) {
112
+ isOpen = false;
113
+ highlightedIndex = -1;
114
+ }
115
+ }
116
+
117
+ // Highlight matching text in suggestions
118
+ function highlightMatch(text: string, query: string): string {
119
+ if (!query) return text;
120
+ const regex = new RegExp(`(${query})`, 'gi');
121
+ return text.replace(regex, '<mark>$1</mark>');
122
+ }
123
+
124
+ $effect(() => {
125
+ if (showDropdown) {
126
+ document.addEventListener('click', handleClickOutside);
127
+ return () => {
128
+ document.removeEventListener('click', handleClickOutside);
129
+ };
130
+ }
131
+ });
132
+ </script>
133
+
134
+ <div
135
+ class="search-input search-input--{size} {disabled ? 'search-input--disabled' : ''} {className}"
136
+ >
137
+ <div class="search-input__wrapper">
138
+ <!-- Search Icon -->
139
+ <span class="search-input__icon search-input__icon--search" aria-hidden="true">
140
+ <svg
141
+ width="20"
142
+ height="20"
143
+ viewBox="0 0 20 20"
144
+ fill="none"
145
+ xmlns="http://www.w3.org/2000/svg"
146
+ >
147
+ <path
148
+ d="M9 17A8 8 0 1 0 9 1a8 8 0 0 0 0 16zM18 18l-4.35-4.35"
149
+ stroke="currentColor"
150
+ stroke-width="2"
151
+ stroke-linecap="round"
152
+ stroke-linejoin="round"
153
+ />
154
+ </svg>
155
+ </span>
156
+
157
+ <!-- Input Field -->
158
+ <input
159
+ bind:this={inputRef}
160
+ type="search"
161
+ class="search-input__field"
162
+ bind:value
163
+ {placeholder}
164
+ {disabled}
165
+ role="combobox"
166
+ aria-autocomplete="list"
167
+ aria-expanded={showDropdown}
168
+ aria-controls="search-suggestions"
169
+ aria-activedescendant={highlightedIndex >= 0 ? `suggestion-${highlightedIndex}` : undefined}
170
+ oninput={handleInput}
171
+ {onchange}
172
+ onkeydown={handleKeyDown}
173
+ onfocus={() => {
174
+ if (value && filteredSuggestions().length > 0) {
175
+ isOpen = true;
176
+ }
177
+ }}
178
+ />
179
+
180
+ <!-- Loading Spinner -->
181
+ {#if loading}
182
+ <span class="search-input__icon search-input__icon--trailing" aria-hidden="true">
183
+ <svg
184
+ class="search-input__spinner"
185
+ width="20"
186
+ height="20"
187
+ viewBox="0 0 20 20"
188
+ fill="none"
189
+ xmlns="http://www.w3.org/2000/svg"
190
+ >
191
+ <circle
192
+ cx="10"
193
+ cy="10"
194
+ r="8"
195
+ stroke="currentColor"
196
+ stroke-width="2"
197
+ stroke-linecap="round"
198
+ stroke-dasharray="40 40"
199
+ stroke-dashoffset="0"
200
+ />
201
+ </svg>
202
+ </span>
203
+ {/if}
204
+
205
+ <!-- Clear Button -->
206
+ {#if showClearButton && !loading}
207
+ <button
208
+ type="button"
209
+ class="search-input__clear"
210
+ aria-label="Clear search"
211
+ onclick={handleClear}
212
+ >
213
+ <svg
214
+ width="20"
215
+ height="20"
216
+ viewBox="0 0 20 20"
217
+ fill="none"
218
+ xmlns="http://www.w3.org/2000/svg"
219
+ >
220
+ <path
221
+ d="M15 5L5 15M5 5l10 10"
222
+ stroke="currentColor"
223
+ stroke-width="2"
224
+ stroke-linecap="round"
225
+ stroke-linejoin="round"
226
+ />
227
+ </svg>
228
+ </button>
229
+ {/if}
230
+ </div>
231
+
232
+ <!-- Suggestions Dropdown -->
233
+ {#if showDropdown}
234
+ <div
235
+ bind:this={dropdownRef}
236
+ class="search-input__suggestions"
237
+ id="search-suggestions"
238
+ role="listbox"
239
+ >
240
+ {#each filteredSuggestions() as suggestion, index}
241
+ <button
242
+ type="button"
243
+ class="search-input__suggestion"
244
+ class:search-input__suggestion--highlighted={index === highlightedIndex}
245
+ id="suggestion-{index}"
246
+ role="option"
247
+ aria-selected={index === highlightedIndex}
248
+ onclick={() => selectSuggestion(suggestion)}
249
+ onmouseenter={() => {
250
+ highlightedIndex = index;
251
+ }}
252
+ >
253
+ {@html highlightMatch(suggestion, value)}
254
+ </button>
255
+ {/each}
256
+ </div>
257
+ {/if}
258
+ </div>
259
+
260
+ <style>
261
+ .search-input {
262
+ position: relative;
263
+ display: flex;
264
+ flex-direction: column;
265
+ width: 100%;
266
+ }
267
+
268
+ .search-input--disabled {
269
+ opacity: 0.5;
270
+ }
271
+
272
+ .search-input__wrapper {
273
+ position: relative;
274
+ display: flex;
275
+ align-items: center;
276
+ width: 100%;
277
+ }
278
+
279
+ .search-input__field {
280
+ width: 100%;
281
+ min-height: var(--touch-target-min, 44px);
282
+ padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
283
+ padding-left: calc(var(--space-md, 1rem) * 2 + 1.25rem);
284
+ padding-right: calc(var(--space-md, 1rem) * 2 + 1.25rem);
285
+ border: 1px solid var(--color-border, #e5e7eb);
286
+ border-radius: var(--radius-md, 0.375rem);
287
+ background: var(--color-bg, #ffffff);
288
+ color: var(--color-text, #1f2937);
289
+ font-family: inherit;
290
+ font-size: var(--text-sm, 0.875rem);
291
+ line-height: 1.5;
292
+ transition:
293
+ border-color var(--transition-fast, 150ms ease),
294
+ box-shadow var(--transition-fast, 150ms ease);
295
+ -webkit-tap-highlight-color: transparent;
296
+ }
297
+
298
+ .search-input__field::placeholder {
299
+ color: var(--color-text-muted, #9ca3af);
300
+ }
301
+
302
+ .search-input__field:focus {
303
+ outline: none;
304
+ border-color: var(--color-primary, #3b82f6);
305
+ box-shadow: 0 0 0 3px var(--color-primary-alpha, rgba(59, 130, 246, 0.1));
306
+ }
307
+
308
+ .search-input__field:disabled {
309
+ background: var(--color-bg-muted, #f3f4f6);
310
+ cursor: not-allowed;
311
+ }
312
+
313
+ /* Remove default search input clear button */
314
+ .search-input__field::-webkit-search-cancel-button {
315
+ display: none;
316
+ }
317
+
318
+ /* Sizes */
319
+ .search-input--sm .search-input__field {
320
+ min-height: var(--touch-target-min, 44px);
321
+ padding: var(--space-xs, 0.25rem) var(--space-sm, 0.5rem);
322
+ padding-left: calc(var(--space-sm, 0.5rem) * 2 + 1.25rem);
323
+ padding-right: calc(var(--space-sm, 0.5rem) * 2 + 1.25rem);
324
+ font-size: var(--text-xs, 0.75rem);
325
+ }
326
+
327
+ .search-input--md .search-input__field {
328
+ min-height: var(--touch-target-min, 44px);
329
+ padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
330
+ padding-left: calc(var(--space-md, 1rem) * 2 + 1.25rem);
331
+ padding-right: calc(var(--space-md, 1rem) * 2 + 1.25rem);
332
+ font-size: var(--text-sm, 0.875rem);
333
+ }
334
+
335
+ .search-input--lg .search-input__field {
336
+ min-height: 3.25rem;
337
+ padding: var(--space-md, 1rem) var(--space-lg, 1.5rem);
338
+ padding-left: calc(var(--space-lg, 1.5rem) * 2 + 1.5rem);
339
+ padding-right: calc(var(--space-lg, 1.5rem) * 2 + 1.5rem);
340
+ font-size: var(--text-base, 1rem);
341
+ }
342
+
343
+ /* Icons */
344
+ .search-input__icon {
345
+ position: absolute;
346
+ display: flex;
347
+ align-items: center;
348
+ justify-content: center;
349
+ color: var(--color-text-muted, #6b7280);
350
+ pointer-events: none;
351
+ }
352
+
353
+ .search-input__icon--search {
354
+ left: var(--space-md, 1rem);
355
+ }
356
+
357
+ .search-input__icon--trailing {
358
+ right: var(--space-md, 1rem);
359
+ }
360
+
361
+ .search-input--sm .search-input__icon--search {
362
+ left: var(--space-sm, 0.5rem);
363
+ }
364
+
365
+ .search-input--sm .search-input__icon--trailing {
366
+ right: var(--space-sm, 0.5rem);
367
+ }
368
+
369
+ .search-input--lg .search-input__icon--search {
370
+ left: var(--space-lg, 1.5rem);
371
+ }
372
+
373
+ .search-input--lg .search-input__icon--trailing {
374
+ right: var(--space-lg, 1.5rem);
375
+ }
376
+
377
+ /* Loading Spinner */
378
+ .search-input__spinner {
379
+ animation: spin 1s linear infinite;
380
+ }
381
+
382
+ @keyframes spin {
383
+ from {
384
+ transform: rotate(0deg);
385
+ }
386
+ to {
387
+ transform: rotate(360deg);
388
+ }
389
+ }
390
+
391
+ /* Clear Button */
392
+ .search-input__clear {
393
+ position: absolute;
394
+ right: var(--space-md, 1rem);
395
+ display: flex;
396
+ align-items: center;
397
+ justify-content: center;
398
+ min-width: var(--touch-target-min, 44px);
399
+ min-height: var(--touch-target-min, 44px);
400
+ padding: 0;
401
+ border: none;
402
+ background: transparent;
403
+ color: var(--color-text-muted, #6b7280);
404
+ cursor: pointer;
405
+ transition: color var(--transition-fast, 150ms ease);
406
+ -webkit-tap-highlight-color: transparent;
407
+ }
408
+
409
+ .search-input__clear:hover {
410
+ color: var(--color-text, #1f2937);
411
+ }
412
+
413
+ .search-input__clear:focus {
414
+ outline: none;
415
+ color: var(--color-primary, #3b82f6);
416
+ }
417
+
418
+ .search-input--sm .search-input__clear {
419
+ right: var(--space-sm, 0.5rem);
420
+ }
421
+
422
+ .search-input--lg .search-input__clear {
423
+ right: var(--space-lg, 1.5rem);
424
+ }
425
+
426
+ /* Suggestions Dropdown */
427
+ .search-input__suggestions {
428
+ position: absolute;
429
+ top: calc(100% + var(--space-xs, 0.25rem));
430
+ left: 0;
431
+ right: 0;
432
+ z-index: var(--z-dropdown, 100);
433
+ max-height: 300px;
434
+ overflow-y: auto;
435
+ border: 1px solid var(--color-border, #e5e7eb);
436
+ border-radius: var(--radius-md, 0.375rem);
437
+ background: var(--color-bg-elevated, #ffffff);
438
+ box-shadow: var(--shadow-lg, 0 10px 15px -3px rgb(0 0 0 / 0.1));
439
+ }
440
+
441
+ .search-input__suggestion {
442
+ display: block;
443
+ width: 100%;
444
+ min-height: var(--touch-target-min, 44px);
445
+ padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
446
+ border: none;
447
+ background: transparent;
448
+ color: var(--color-text, #1f2937);
449
+ font-family: inherit;
450
+ font-size: var(--text-sm, 0.875rem);
451
+ line-height: 1.5;
452
+ text-align: left;
453
+ cursor: pointer;
454
+ transition: background-color var(--transition-fast, 150ms ease);
455
+ -webkit-tap-highlight-color: transparent;
456
+ }
457
+
458
+ .search-input__suggestion:hover,
459
+ .search-input__suggestion--highlighted {
460
+ background: var(--color-bg-hover, #f3f4f6);
461
+ }
462
+
463
+ .search-input__suggestion:active {
464
+ background: var(--color-bg-active, #e5e7eb);
465
+ }
466
+
467
+ /* Highlight matched text */
468
+ .search-input__suggestion :global(mark) {
469
+ background: var(--color-primary-bg, #e6f4fe);
470
+ color: var(--color-primary, #3b82f6);
471
+ font-weight: var(--font-medium, 500);
472
+ }
473
+
474
+ /* Mobile optimizations */
475
+ @media (max-width: 640px) {
476
+ .search-input__suggestions {
477
+ max-height: 200px;
478
+ }
479
+ }
480
+ </style>
@@ -0,0 +1,22 @@
1
+ import type { InputSize } from '../../types/index.js';
2
+ interface Props {
3
+ value?: string;
4
+ placeholder?: string;
5
+ suggestions?: string[];
6
+ size?: InputSize;
7
+ disabled?: boolean;
8
+ loading?: boolean;
9
+ maxSuggestions?: number;
10
+ class?: string;
11
+ oninput?: (e: Event & {
12
+ currentTarget: HTMLInputElement;
13
+ }) => void;
14
+ onchange?: (e: Event & {
15
+ currentTarget: HTMLInputElement;
16
+ }) => void;
17
+ onselect?: (value: string) => void;
18
+ onclear?: () => void;
19
+ }
20
+ declare const SearchInput: import("svelte").Component<Props, {}, "value">;
21
+ type SearchInput = ReturnType<typeof SearchInput>;
22
+ export default SearchInput;
@@ -0,0 +1 @@
1
+ export { default as SearchInput } from './SearchInput.svelte';
@@ -0,0 +1 @@
1
+ export { default as SearchInput } from './SearchInput.svelte';