@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,19 @@
1
+ import type { InputSize } from '../../types/index.js';
2
+ interface Props {
3
+ value?: Date | null;
4
+ placeholder?: string;
5
+ min?: Date | null;
6
+ max?: Date | null;
7
+ disabled?: boolean;
8
+ required?: boolean;
9
+ error?: boolean;
10
+ size?: InputSize;
11
+ format?: string;
12
+ locale?: string;
13
+ class?: string;
14
+ onchange?: (date: Date | null) => void;
15
+ onclear?: () => void;
16
+ }
17
+ declare const DatePicker: import("svelte").Component<Props, {}, "value">;
18
+ type DatePicker = ReturnType<typeof DatePicker>;
19
+ export default DatePicker;
@@ -0,0 +1 @@
1
+ export { default as DatePicker } from './DatePicker.svelte';
@@ -0,0 +1 @@
1
+ export { default as DatePicker } from './DatePicker.svelte';
@@ -0,0 +1,484 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ files?: File[];
4
+ accept?: string;
5
+ multiple?: boolean;
6
+ maxSize?: number;
7
+ maxFiles?: number;
8
+ disabled?: boolean;
9
+ showPreview?: boolean;
10
+ class?: string;
11
+ onchange?: (files: File[]) => void;
12
+ onerror?: (error: string) => void;
13
+ onremove?: (file: File) => void;
14
+ }
15
+
16
+ let {
17
+ files = $bindable([]),
18
+ accept = '',
19
+ multiple = false,
20
+ maxSize = 10 * 1024 * 1024, // 10MB
21
+ maxFiles = 10,
22
+ disabled = false,
23
+ showPreview = true,
24
+ class: className = '',
25
+ onchange,
26
+ onerror,
27
+ onremove
28
+ }: Props = $props();
29
+
30
+ let isDragging = $state(false);
31
+ let fileInput: HTMLInputElement;
32
+ let previewUrls = $state<Map<File, string>>(new Map());
33
+
34
+ const hasFiles = $derived(files.length > 0);
35
+
36
+ function formatFileSize(bytes: number): string {
37
+ if (bytes < 1024) return bytes + ' B';
38
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
39
+ return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
40
+ }
41
+
42
+ function isImageFile(file: File): boolean {
43
+ return file.type.startsWith('image/');
44
+ }
45
+
46
+ function validateFile(file: File): string | null {
47
+ if (file.size > maxSize) {
48
+ return `File "${file.name}" exceeds maximum size of ${formatFileSize(maxSize)}`;
49
+ }
50
+
51
+ if (accept) {
52
+ const acceptedTypes = accept.split(',').map((t) => t.trim());
53
+ const isAccepted = acceptedTypes.some((type) => {
54
+ if (type.startsWith('.')) {
55
+ return file.name.toLowerCase().endsWith(type.toLowerCase());
56
+ }
57
+ if (type.endsWith('/*')) {
58
+ return file.type.startsWith(type.slice(0, -2));
59
+ }
60
+ return file.type === type;
61
+ });
62
+
63
+ if (!isAccepted) {
64
+ return `File "${file.name}" is not an accepted file type`;
65
+ }
66
+ }
67
+
68
+ return null;
69
+ }
70
+
71
+ function createPreview(file: File) {
72
+ if (isImageFile(file) && showPreview) {
73
+ const url = URL.createObjectURL(file);
74
+ previewUrls.set(file, url);
75
+ }
76
+ }
77
+
78
+ function revokePreview(file: File) {
79
+ const url = previewUrls.get(file);
80
+ if (url) {
81
+ URL.revokeObjectURL(url);
82
+ previewUrls.delete(file);
83
+ }
84
+ }
85
+
86
+ function handleFiles(newFiles: FileList | File[]) {
87
+ if (disabled) return;
88
+
89
+ const fileArray = Array.from(newFiles);
90
+ const remainingSlots = maxFiles - files.length;
91
+
92
+ if (fileArray.length > remainingSlots) {
93
+ onerror?.(`Can only add ${remainingSlots} more file(s). Maximum is ${maxFiles} files.`);
94
+ return;
95
+ }
96
+
97
+ const validFiles: File[] = [];
98
+ let hasErrors = false;
99
+
100
+ for (const file of fileArray) {
101
+ const error = validateFile(file);
102
+ if (error) {
103
+ onerror?.(error);
104
+ hasErrors = true;
105
+ } else {
106
+ validFiles.push(file);
107
+ createPreview(file);
108
+ }
109
+ }
110
+
111
+ if (validFiles.length > 0) {
112
+ files = [...files, ...validFiles];
113
+ onchange?.(files);
114
+ }
115
+ }
116
+
117
+ function handleDragEnter(e: DragEvent) {
118
+ e.preventDefault();
119
+ if (disabled) return;
120
+ isDragging = true;
121
+ }
122
+
123
+ function handleDragOver(e: DragEvent) {
124
+ e.preventDefault();
125
+ if (disabled) return;
126
+ isDragging = true;
127
+ }
128
+
129
+ function handleDragLeave(e: DragEvent) {
130
+ e.preventDefault();
131
+ if (disabled) return;
132
+ // Only set isDragging to false if we're leaving the drop zone itself
133
+ const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
134
+ const x = e.clientX;
135
+ const y = e.clientY;
136
+ if (x <= rect.left || x >= rect.right || y <= rect.top || y >= rect.bottom) {
137
+ isDragging = false;
138
+ }
139
+ }
140
+
141
+ function handleDrop(e: DragEvent) {
142
+ e.preventDefault();
143
+ isDragging = false;
144
+ if (disabled) return;
145
+
146
+ const droppedFiles = e.dataTransfer?.files;
147
+ if (droppedFiles && droppedFiles.length > 0) {
148
+ if (!multiple && droppedFiles.length > 1) {
149
+ onerror?.('Only one file can be selected');
150
+ return;
151
+ }
152
+ handleFiles(droppedFiles);
153
+ }
154
+ }
155
+
156
+ function handleInputChange(e: Event) {
157
+ const input = e.target as HTMLInputElement;
158
+ if (input.files && input.files.length > 0) {
159
+ handleFiles(input.files);
160
+ }
161
+ // Reset input value to allow selecting the same file again
162
+ input.value = '';
163
+ }
164
+
165
+ function handleClick() {
166
+ if (!disabled) {
167
+ fileInput?.click();
168
+ }
169
+ }
170
+
171
+ function handleKeydown(e: KeyboardEvent) {
172
+ if (disabled) return;
173
+ if (e.key === 'Enter' || e.key === ' ') {
174
+ e.preventDefault();
175
+ handleClick();
176
+ }
177
+ }
178
+
179
+ function removeFile(file: File) {
180
+ if (disabled) return;
181
+ revokePreview(file);
182
+ files = files.filter((f) => f !== file);
183
+ onremove?.(file);
184
+ onchange?.(files);
185
+ }
186
+
187
+ $effect(() => {
188
+ // Cleanup previews when component unmounts
189
+ return () => {
190
+ previewUrls.forEach((url) => URL.revokeObjectURL(url));
191
+ };
192
+ });
193
+ </script>
194
+
195
+ <div class="file-upload {className}" class:disabled>
196
+ <div
197
+ class="drop-zone"
198
+ class:dragging={isDragging}
199
+ class:has-files={hasFiles}
200
+ role="button"
201
+ tabindex={disabled ? -1 : 0}
202
+ aria-label="File upload area"
203
+ aria-describedby="upload-instructions"
204
+ ondragenter={handleDragEnter}
205
+ ondragover={handleDragOver}
206
+ ondragleave={handleDragLeave}
207
+ ondrop={handleDrop}
208
+ onclick={handleClick}
209
+ onkeydown={handleKeydown}
210
+ >
211
+ <input
212
+ bind:this={fileInput}
213
+ type="file"
214
+ {accept}
215
+ {multiple}
216
+ {disabled}
217
+ onchange={handleInputChange}
218
+ aria-label="File input"
219
+ style="display: none;"
220
+ />
221
+
222
+ {#if !hasFiles}
223
+ <div class="drop-zone-content">
224
+ <svg
225
+ class="upload-icon"
226
+ width="48"
227
+ height="48"
228
+ viewBox="0 0 24 24"
229
+ fill="none"
230
+ stroke="currentColor"
231
+ stroke-width="2"
232
+ stroke-linecap="round"
233
+ stroke-linejoin="round"
234
+ aria-hidden="true"
235
+ >
236
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
237
+ <polyline points="17 8 12 3 7 8"></polyline>
238
+ <line x1="12" y1="3" x2="12" y2="15"></line>
239
+ </svg>
240
+ <p id="upload-instructions" class="instructions">
241
+ {isDragging ? 'Drop files here' : 'Drop files here or click to browse'}
242
+ </p>
243
+ <p class="hint">
244
+ {#if accept}
245
+ Accepted: {accept}
246
+ {#if maxSize}
247
+ • Max size: {formatFileSize(maxSize)}
248
+ {/if}
249
+ {:else if maxSize}
250
+ Max size: {formatFileSize(maxSize)}
251
+ {/if}
252
+ </p>
253
+ </div>
254
+ {/if}
255
+ </div>
256
+
257
+ {#if hasFiles}
258
+ <div class="file-list" role="list">
259
+ {#each files as file (file)}
260
+ <div class="file-item" role="listitem">
261
+ <div class="file-preview">
262
+ {#if isImageFile(file) && showPreview && previewUrls.has(file)}
263
+ <img src={previewUrls.get(file)} alt={file.name} class="preview-image" />
264
+ {:else}
265
+ <svg
266
+ class="file-icon"
267
+ width="24"
268
+ height="24"
269
+ viewBox="0 0 24 24"
270
+ fill="none"
271
+ stroke="currentColor"
272
+ stroke-width="2"
273
+ stroke-linecap="round"
274
+ stroke-linejoin="round"
275
+ aria-hidden="true"
276
+ >
277
+ <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
278
+ <polyline points="13 2 13 9 20 9"></polyline>
279
+ </svg>
280
+ {/if}
281
+ </div>
282
+ <div class="file-info">
283
+ <span class="file-name" title={file.name}>{file.name}</span>
284
+ <span class="file-size">{formatFileSize(file.size)}</span>
285
+ </div>
286
+ <button
287
+ class="remove-btn"
288
+ type="button"
289
+ onclick={() => removeFile(file)}
290
+ {disabled}
291
+ aria-label="Remove {file.name}"
292
+ >
293
+ <svg
294
+ width="20"
295
+ height="20"
296
+ viewBox="0 0 24 24"
297
+ fill="none"
298
+ stroke="currentColor"
299
+ stroke-width="2"
300
+ stroke-linecap="round"
301
+ stroke-linejoin="round"
302
+ aria-hidden="true"
303
+ >
304
+ <line x1="18" y1="6" x2="6" y2="18"></line>
305
+ <line x1="6" y1="6" x2="18" y2="18"></line>
306
+ </svg>
307
+ </button>
308
+ </div>
309
+ {/each}
310
+ </div>
311
+ {/if}
312
+ </div>
313
+
314
+ <style>
315
+ .file-upload {
316
+ width: 100%;
317
+ }
318
+
319
+ .file-upload.disabled {
320
+ opacity: 0.6;
321
+ cursor: not-allowed;
322
+ }
323
+
324
+ .drop-zone {
325
+ border: 2px dashed var(--color-border, #d1d5db);
326
+ border-radius: var(--radius-md, 8px);
327
+ padding: var(--space-6, 1.5rem);
328
+ text-align: center;
329
+ cursor: pointer;
330
+ transition: all 0.2s ease;
331
+ background-color: var(--color-background, #ffffff);
332
+ min-height: var(--touch-target-min, 44px);
333
+ }
334
+
335
+ .drop-zone:hover:not(.disabled) {
336
+ border-color: var(--color-primary, #3b82f6);
337
+ background-color: var(--color-background-hover, #f9fafb);
338
+ }
339
+
340
+ .drop-zone:focus-visible {
341
+ outline: 2px solid var(--color-primary, #3b82f6);
342
+ outline-offset: 2px;
343
+ }
344
+
345
+ .drop-zone.dragging {
346
+ border-color: var(--color-primary, #3b82f6);
347
+ background-color: var(--color-primary-light, #eff6ff);
348
+ }
349
+
350
+ .drop-zone.has-files {
351
+ border-style: solid;
352
+ padding: var(--space-4, 1rem);
353
+ cursor: default;
354
+ }
355
+
356
+ .drop-zone.disabled {
357
+ cursor: not-allowed;
358
+ }
359
+
360
+ .drop-zone-content {
361
+ display: flex;
362
+ flex-direction: column;
363
+ align-items: center;
364
+ gap: var(--space-3, 0.75rem);
365
+ }
366
+
367
+ .upload-icon {
368
+ color: var(--color-text-secondary, #6b7280);
369
+ }
370
+
371
+ .instructions {
372
+ margin: 0;
373
+ font-size: var(--text-base, 1rem);
374
+ color: var(--color-text, #1f2937);
375
+ font-weight: 500;
376
+ }
377
+
378
+ .hint {
379
+ margin: 0;
380
+ font-size: var(--text-sm, 0.875rem);
381
+ color: var(--color-text-secondary, #6b7280);
382
+ }
383
+
384
+ .file-list {
385
+ margin-top: var(--space-4, 1rem);
386
+ display: grid;
387
+ gap: var(--space-3, 0.75rem);
388
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
389
+ }
390
+
391
+ .file-item {
392
+ display: flex;
393
+ align-items: center;
394
+ gap: var(--space-3, 0.75rem);
395
+ padding: var(--space-3, 0.75rem);
396
+ border: 1px solid var(--color-border, #e5e7eb);
397
+ border-radius: var(--radius-md, 8px);
398
+ background-color: var(--color-background, #ffffff);
399
+ }
400
+
401
+ .file-preview {
402
+ flex-shrink: 0;
403
+ width: 48px;
404
+ height: 48px;
405
+ display: flex;
406
+ align-items: center;
407
+ justify-content: center;
408
+ border-radius: var(--radius-sm, 4px);
409
+ background-color: var(--color-background-secondary, #f3f4f6);
410
+ overflow: hidden;
411
+ }
412
+
413
+ .preview-image {
414
+ width: 100%;
415
+ height: 100%;
416
+ object-fit: cover;
417
+ }
418
+
419
+ .file-icon {
420
+ color: var(--color-text-secondary, #6b7280);
421
+ }
422
+
423
+ .file-info {
424
+ flex: 1;
425
+ min-width: 0;
426
+ display: flex;
427
+ flex-direction: column;
428
+ gap: var(--space-1, 0.25rem);
429
+ }
430
+
431
+ .file-name {
432
+ font-size: var(--text-sm, 0.875rem);
433
+ color: var(--color-text, #1f2937);
434
+ font-weight: 500;
435
+ overflow: hidden;
436
+ text-overflow: ellipsis;
437
+ white-space: nowrap;
438
+ }
439
+
440
+ .file-size {
441
+ font-size: var(--text-xs, 0.75rem);
442
+ color: var(--color-text-secondary, #6b7280);
443
+ }
444
+
445
+ .remove-btn {
446
+ flex-shrink: 0;
447
+ min-width: var(--touch-target-min, 44px);
448
+ min-height: var(--touch-target-min, 44px);
449
+ display: flex;
450
+ align-items: center;
451
+ justify-content: center;
452
+ border: none;
453
+ background: transparent;
454
+ color: var(--color-text-secondary, #6b7280);
455
+ cursor: pointer;
456
+ border-radius: var(--radius-sm, 4px);
457
+ transition: all 0.2s ease;
458
+ }
459
+
460
+ .remove-btn:hover:not(:disabled) {
461
+ background-color: var(--color-error-light, #fef2f2);
462
+ color: var(--color-error, #ef4444);
463
+ }
464
+
465
+ .remove-btn:focus-visible {
466
+ outline: 2px solid var(--color-primary, #3b82f6);
467
+ outline-offset: 2px;
468
+ }
469
+
470
+ .remove-btn:disabled {
471
+ cursor: not-allowed;
472
+ opacity: 0.5;
473
+ }
474
+
475
+ @media (max-width: 640px) {
476
+ .file-list {
477
+ grid-template-columns: 1fr;
478
+ }
479
+
480
+ .drop-zone {
481
+ padding: var(--space-4, 1rem);
482
+ }
483
+ }
484
+ </style>
@@ -0,0 +1,16 @@
1
+ interface Props {
2
+ files?: File[];
3
+ accept?: string;
4
+ multiple?: boolean;
5
+ maxSize?: number;
6
+ maxFiles?: number;
7
+ disabled?: boolean;
8
+ showPreview?: boolean;
9
+ class?: string;
10
+ onchange?: (files: File[]) => void;
11
+ onerror?: (error: string) => void;
12
+ onremove?: (file: File) => void;
13
+ }
14
+ declare const FileUpload: import("svelte").Component<Props, {}, "files">;
15
+ type FileUpload = ReturnType<typeof FileUpload>;
16
+ export default FileUpload;
@@ -0,0 +1 @@
1
+ export { default as FileUpload } from './FileUpload.svelte';
@@ -0,0 +1 @@
1
+ export { default as FileUpload } from './FileUpload.svelte';