@aspect-ops/exon-ui 0.0.3 → 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 (65) hide show
  1. package/README.md +793 -43
  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/OTPInput/OTPInput.svelte +312 -0
  33. package/dist/components/OTPInput/OTPInput.svelte.d.ts +57 -0
  34. package/dist/components/OTPInput/index.d.ts +1 -0
  35. package/dist/components/OTPInput/index.js +1 -0
  36. package/dist/components/Rating/Rating.svelte +316 -0
  37. package/dist/components/Rating/Rating.svelte.d.ts +16 -0
  38. package/dist/components/Rating/index.d.ts +1 -0
  39. package/dist/components/Rating/index.js +1 -0
  40. package/dist/components/SearchInput/SearchInput.svelte +480 -0
  41. package/dist/components/SearchInput/SearchInput.svelte.d.ts +22 -0
  42. package/dist/components/SearchInput/index.d.ts +1 -0
  43. package/dist/components/SearchInput/index.js +1 -0
  44. package/dist/components/Slider/Slider.svelte +324 -0
  45. package/dist/components/Slider/Slider.svelte.d.ts +14 -0
  46. package/dist/components/Slider/index.d.ts +1 -0
  47. package/dist/components/Slider/index.js +1 -0
  48. package/dist/components/Stepper/Stepper.svelte +100 -0
  49. package/dist/components/Stepper/Stepper.svelte.d.ts +11 -0
  50. package/dist/components/Stepper/StepperStep.svelte +391 -0
  51. package/dist/components/Stepper/StepperStep.svelte.d.ts +13 -0
  52. package/dist/components/Stepper/index.d.ts +2 -0
  53. package/dist/components/Stepper/index.js +2 -0
  54. package/dist/components/TimePicker/TimePicker.svelte +803 -0
  55. package/dist/components/TimePicker/TimePicker.svelte.d.ts +17 -0
  56. package/dist/components/TimePicker/index.d.ts +1 -0
  57. package/dist/components/TimePicker/index.js +1 -0
  58. package/dist/index.d.ts +10 -1
  59. package/dist/index.js +9 -0
  60. package/dist/types/data-display.d.ts +68 -0
  61. package/dist/types/index.d.ts +3 -2
  62. package/dist/types/input.d.ts +67 -0
  63. package/dist/types/input.js +2 -0
  64. package/dist/types/navigation.d.ts +15 -0
  65. 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';