@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
package/README.md CHANGED
@@ -46,17 +46,24 @@ npm install @aspect-ops/exon-ui
46
46
 
47
47
  ### Form Components
48
48
 
49
- | Component | Description |
50
- | --------------- | ---------------------------------------- |
51
- | `TextInput` | Text input with validation states |
52
- | `Textarea` | Multi-line input with auto-resize |
53
- | `Select` | Dropdown select with keyboard navigation |
54
- | `Checkbox` | Single checkbox with indeterminate state |
55
- | `CheckboxGroup` | Group of checkboxes with shared state |
56
- | `Radio` | Single radio button |
57
- | `RadioGroup` | Radio button group with orientation |
58
- | `Switch` | Toggle switch component |
59
- | `FormField` | Label wrapper with helper/error text |
49
+ | Component | Description |
50
+ | --------------- | ------------------------------------------ |
51
+ | `TextInput` | Text input with validation states |
52
+ | `Textarea` | Multi-line input with auto-resize |
53
+ | `Select` | Dropdown select with keyboard navigation |
54
+ | `Checkbox` | Single checkbox with indeterminate state |
55
+ | `CheckboxGroup` | Group of checkboxes with shared state |
56
+ | `Radio` | Single radio button |
57
+ | `RadioGroup` | Radio button group with orientation |
58
+ | `Switch` | Toggle switch component |
59
+ | `FormField` | Label wrapper with helper/error text |
60
+ | `SearchInput` | Search with autocomplete suggestions |
61
+ | `DatePicker` | Date selection with calendar popup |
62
+ | `TimePicker` | Time selection with hour/minute picker |
63
+ | `FileUpload` | Drag-drop file upload with previews |
64
+ | `OTPInput` | One-time password input |
65
+ | `NumberInput` | Number input with +/- buttons and keyboard |
66
+ | `ToggleGroup` | Single/multi select button group |
60
67
 
61
68
  ### Navigation Components
62
69
 
@@ -68,6 +75,31 @@ npm install @aspect-ops/exon-ui
68
75
  | `BottomNav`, `BottomNavItem` | Mobile bottom navigation |
69
76
  | `Navbar`, `NavItem` | Responsive header with mobile menu |
70
77
  | `Sidebar`, `SidebarItem`, `SidebarGroup` | Collapsible sidebar navigation |
78
+ | `Stepper`, `StepperStep` | Multi-step progress indicator |
79
+ | `Pagination` | Page navigation with ellipsis |
80
+
81
+ ### Data Display Components
82
+
83
+ | Component | Description |
84
+ | ---------------------------- | -------------------------------------------- |
85
+ | `Accordion`, `AccordionItem` | Collapsible content panels |
86
+ | `Slider` | Range slider with drag/keyboard support |
87
+ | `Carousel`, `CarouselSlide` | Image/content carousel with swipe |
88
+ | `Image` | Lazy loading image with placeholder/fallback |
89
+ | `Rating` | Star rating display/input |
90
+ | `Chip`, `ChipGroup` | Tag/filter chips with selection |
91
+
92
+ ### Mobile Components
93
+
94
+ | Component | Description |
95
+ | ----------------------------------------------------- | ------------------------------------------- |
96
+ | `ActionSheet`, `ActionSheetItem` | iOS/Android-style bottom action menu |
97
+ | `BottomSheet`, `BottomSheetHeader`, `BottomSheetBody` | Draggable bottom sheet with snap points |
98
+ | `FAB` | Floating action button with positions |
99
+ | `FABGroup` | Speed dial FAB with expandable actions |
100
+ | `PullToRefresh` | Pull-to-refresh gesture for lists |
101
+ | `SwipeActions` | Swipe-to-reveal actions on list items |
102
+ | `SafeArea` | Safe area inset wrapper for notched devices |
71
103
 
72
104
  ## Usage Examples
73
105
 
@@ -320,73 +352,901 @@ npm install @aspect-ops/exon-ui
320
352
  </Sidebar>
321
353
  ```
322
354
 
323
- ## Theming
355
+ ## Advanced Form Components
324
356
 
325
- ### CSS Custom Properties
357
+ ### SearchInput
326
358
 
327
- Import the default styles and customize with CSS variables:
359
+ Search input with autocomplete suggestions and keyboard navigation.
360
+
361
+ **Props:**
362
+
363
+ | Prop | Type | Default | Description |
364
+ | ---------------- | ----------- | ---------- | --------------------------------- |
365
+ | `value` | `string` | `''` | Bindable search value |
366
+ | `placeholder` | `string` | `'Search'` | Input placeholder text |
367
+ | `suggestions` | `string[]` | `[]` | Array of autocomplete suggestions |
368
+ | `size` | `InputSize` | `'md'` | Input size: sm, md, lg |
369
+ | `disabled` | `boolean` | `false` | Disabled state |
370
+ | `loading` | `boolean` | `false` | Show loading spinner |
371
+ | `maxSuggestions` | `number` | `5` | Maximum suggestions to display |
372
+ | `onselect` | `function` | - | Callback when suggestion selected |
373
+
374
+ **Usage:**
328
375
 
329
376
  ```svelte
330
377
  <script>
331
- import '@aspect-ops/exon-ui/styles';
378
+ import { SearchInput } from '@aspect-ops/exon-ui';
379
+
380
+ let query = $state('');
381
+ const suggestions = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'];
332
382
  </script>
333
383
 
334
- <style>
335
- :root {
336
- /* Colors */
337
- --color-primary: #3b82f6;
338
- --color-primary-hover: #2563eb;
339
- --color-bg: #ffffff;
340
- --color-bg-muted: #f9fafb;
341
- --color-text: #1f2937;
342
- --color-border: #e5e7eb;
343
-
344
- /* Spacing */
345
- --space-xs: 0.25rem;
346
- --space-sm: 0.5rem;
347
- --space-md: 1rem;
348
- --space-lg: 1.5rem;
349
- --space-xl: 2rem;
350
-
351
- /* Border Radius */
352
- --radius-sm: 0.25rem;
353
- --radius-md: 0.5rem;
354
- --radius-lg: 0.75rem;
355
-
356
- /* Transitions */
357
- --transition-fast: 150ms ease;
358
- --transition-base: 200ms ease;
384
+ <SearchInput bind:value={query} {suggestions} onselect={(val) => console.log('Selected:', val)} />
385
+ ```
386
+
387
+ ### DatePicker
388
+
389
+ Date selection with calendar popup and keyboard navigation.
390
+
391
+ **Props:**
392
+
393
+ | Prop | Type | Default | Description |
394
+ | ------------- | ----------- | --------------- | ----------------------- |
395
+ | `value` | `Date` | `null` | Bindable selected date |
396
+ | `placeholder` | `string` | `'Select date'` | Input placeholder |
397
+ | `min` | `Date` | `null` | Minimum selectable date |
398
+ | `max` | `Date` | `null` | Maximum selectable date |
399
+ | `disabled` | `boolean` | `false` | Disabled state |
400
+ | `size` | `InputSize` | `'md'` | Input size: sm, md, lg |
401
+ | `format` | `string` | `'yyyy-MM-dd'` | Date format string |
402
+
403
+ **Usage:**
404
+
405
+ ```svelte
406
+ <script>
407
+ import { DatePicker } from '@aspect-ops/exon-ui';
408
+
409
+ let selectedDate = $state(null);
410
+ </script>
411
+
412
+ <DatePicker bind:value={selectedDate} placeholder="Choose a date" />
413
+ ```
414
+
415
+ ### TimePicker
416
+
417
+ Time selection with scrollable hour/minute/period picker.
418
+
419
+ **Props:**
420
+
421
+ | Prop | Type | Default | Description |
422
+ | ------------ | ---------------- | ------- | --------------------------- |
423
+ | `value` | `string` | `''` | Bindable time value (HH:mm) |
424
+ | `format` | `'12h' \| '24h'` | `'24h'` | Time format |
425
+ | `minuteStep` | `number` | `1` | Minute increment step |
426
+ | `min` | `string` | - | Minimum selectable time |
427
+ | `max` | `string` | - | Maximum selectable time |
428
+ | `disabled` | `boolean` | `false` | Disabled state |
429
+ | `size` | `InputSize` | `'md'` | Input size: sm, md, lg |
430
+
431
+ **Usage:**
432
+
433
+ ```svelte
434
+ <script>
435
+ import { TimePicker } from '@aspect-ops/exon-ui';
436
+
437
+ let time = $state('');
438
+ </script>
439
+
440
+ <TimePicker bind:value={time} format="12h" minuteStep={15} />
441
+ ```
442
+
443
+ ### FileUpload
444
+
445
+ Drag-and-drop file upload with image previews and validation.
446
+
447
+ **Props:**
448
+
449
+ | Prop | Type | Default | Description |
450
+ | ------------- | --------- | ------- | -------------------------------- |
451
+ | `files` | `File[]` | `[]` | Bindable array of uploaded files |
452
+ | `accept` | `string` | `''` | Accepted file types |
453
+ | `multiple` | `boolean` | `false` | Allow multiple file selection |
454
+ | `maxSize` | `number` | `10MB` | Maximum file size in bytes |
455
+ | `maxFiles` | `number` | `10` | Maximum number of files |
456
+ | `showPreview` | `boolean` | `true` | Show image previews |
457
+
458
+ **Usage:**
459
+
460
+ ```svelte
461
+ <script>
462
+ import { FileUpload } from '@aspect-ops/exon-ui';
463
+
464
+ let files = $state([]);
465
+ </script>
466
+
467
+ <FileUpload bind:files multiple accept="image/*" maxSize={5242880} />
468
+ ```
469
+
470
+ ### OTPInput
471
+
472
+ One-time password input with auto-focus and paste support.
473
+
474
+ **Props:**
475
+
476
+ | Prop | Type | Default | Description |
477
+ | ------------ | ----------------------------- | ----------- | -------------------------------- |
478
+ | `value` | `string` | `''` | Bindable OTP value |
479
+ | `length` | `number` | `6` | Number of OTP digits |
480
+ | `type` | `'numeric' \| 'alphanumeric'` | `'numeric'` | Input validation type |
481
+ | `masked` | `boolean` | `false` | Show dots instead of characters |
482
+ | `disabled` | `boolean` | `false` | Disabled state |
483
+ | `size` | `Size` | `'md'` | Input size: sm, md, lg |
484
+ | `oncomplete` | `function` | - | Callback when all digits entered |
485
+
486
+ **Usage:**
487
+
488
+ ```svelte
489
+ <script>
490
+ import { OTPInput } from '@aspect-ops/exon-ui';
491
+
492
+ let otp = $state('');
493
+ </script>
494
+
495
+ <OTPInput bind:value={otp} length={6} oncomplete={(code) => console.log('OTP:', code)} />
496
+ ```
497
+
498
+ ### NumberInput
499
+
500
+ Number input with increment/decrement buttons and keyboard support.
501
+
502
+ **Props:**
503
+
504
+ | Prop | Type | Default | Description |
505
+ | --------------- | ------------------------- | ------- | --------------------------- |
506
+ | `value` | `number \| null` | `null` | Bindable number value |
507
+ | `min` | `number` | - | Minimum allowed value |
508
+ | `max` | `number` | - | Maximum allowed value |
509
+ | `step` | `number` | `1` | Increment/decrement step |
510
+ | `disabled` | `boolean` | `false` | Disabled state |
511
+ | `placeholder` | `string` | - | Input placeholder |
512
+ | `error` | `boolean` | `false` | Error state styling |
513
+ | `onValueChange` | `(value: number) => void` | - | Callback when value changes |
514
+
515
+ **Usage:**
516
+
517
+ ```svelte
518
+ <script>
519
+ import { NumberInput } from '@aspect-ops/exon-ui';
520
+
521
+ let quantity = $state(1);
522
+ </script>
523
+
524
+ <NumberInput bind:value={quantity} min={1} max={99} step={1} />
525
+
526
+ <!-- With error state -->
527
+ <NumberInput bind:value={quantity} min={0} max={10} error={quantity > 10} />
528
+ ```
529
+
530
+ ### ToggleGroup
531
+
532
+ Button group for single or multiple selection.
533
+
534
+ **Props (ToggleGroup):**
535
+
536
+ | Prop | Type | Default | Description |
537
+ | --------------- | ---------------------------- | -------------- | --------------------------- |
538
+ | `type` | `'single' \| 'multiple'` | `'single'` | Selection mode |
539
+ | `value` | `string \| string[]` | `'' \| []` | Bindable selected value(s) |
540
+ | `onValueChange` | `(value) => void` | - | Callback when value changes |
541
+ | `disabled` | `boolean` | `false` | Disabled state |
542
+ | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout direction |
543
+
544
+ **Props (ToggleGroupItem):**
545
+
546
+ | Prop | Type | Default | Description |
547
+ | ---------- | --------- | ------- | --------------------- |
548
+ | `value` | `string` | - | Item value (required) |
549
+ | `disabled` | `boolean` | `false` | Disabled state |
550
+
551
+ **Usage:**
552
+
553
+ ```svelte
554
+ <script>
555
+ import { ToggleGroup, ToggleGroupItem } from '@aspect-ops/exon-ui';
556
+
557
+ let alignment = $state('left');
558
+ let formats = $state([]);
559
+ </script>
560
+
561
+ <!-- Single selection (radio-like) -->
562
+ <ToggleGroup bind:value={alignment} type="single">
563
+ {#snippet children()}
564
+ <ToggleGroupItem value="left">Left</ToggleGroupItem>
565
+ <ToggleGroupItem value="center">Center</ToggleGroupItem>
566
+ <ToggleGroupItem value="right">Right</ToggleGroupItem>
567
+ {/snippet}
568
+ </ToggleGroup>
569
+
570
+ <!-- Multiple selection (checkbox-like) -->
571
+ <ToggleGroup bind:value={formats} type="multiple">
572
+ {#snippet children()}
573
+ <ToggleGroupItem value="bold">B</ToggleGroupItem>
574
+ <ToggleGroupItem value="italic">I</ToggleGroupItem>
575
+ <ToggleGroupItem value="underline">U</ToggleGroupItem>
576
+ {/snippet}
577
+ </ToggleGroup>
578
+ ```
579
+
580
+ ### Pagination
581
+
582
+ Page navigation with ellipsis for large page ranges.
583
+
584
+ **Props:**
585
+
586
+ | Prop | Type | Default | Description |
587
+ | -------------- | ------------------------ | ------- | --------------------------------- |
588
+ | `currentPage` | `number` | - | Current page number (required) |
589
+ | `totalPages` | `number` | - | Total number of pages (required) |
590
+ | `siblingCount` | `number` | `1` | Pages to show around current page |
591
+ | `onPageChange` | `(page: number) => void` | - | Callback when page changes |
592
+
593
+ **Usage:**
594
+
595
+ ```svelte
596
+ <script>
597
+ import { Pagination } from '@aspect-ops/exon-ui';
598
+
599
+ let currentPage = $state(1);
600
+ const totalPages = 20;
601
+ </script>
602
+
603
+ <Pagination {currentPage} {totalPages} onPageChange={(page) => (currentPage = page)} />
604
+
605
+ <!-- With more visible siblings -->
606
+ <Pagination
607
+ {currentPage}
608
+ {totalPages}
609
+ siblingCount={2}
610
+ onPageChange={(page) => (currentPage = page)}
611
+ />
612
+ ```
613
+
614
+ **Keyboard Navigation:**
615
+
616
+ - `←` / `→`: Previous / Next page
617
+ - `Home`: First page
618
+ - `End`: Last page
619
+
620
+ ## Data Display Components
621
+
622
+ ### Accordion
623
+
624
+ Collapsible content panels with single or multiple expansion.
625
+
626
+ **Props:**
627
+
628
+ | Prop | Type | Default | Description |
629
+ | ---------- | -------------------- | ------- | ------------------------------- |
630
+ | `value` | `string \| string[]` | `[]` | Bindable expanded item value(s) |
631
+ | `multiple` | `boolean` | `false` | Allow multiple panels open |
632
+ | `disabled` | `boolean` | `false` | Disabled state |
633
+
634
+ **Usage:**
635
+
636
+ ```svelte
637
+ <script>
638
+ import { Accordion, AccordionItem } from '@aspect-ops/exon-ui';
639
+
640
+ let expanded = $state([]);
641
+ </script>
642
+
643
+ <Accordion bind:value={expanded} multiple>
644
+ {#snippet children()}
645
+ <AccordionItem value="item1" title="Section 1">Content for section 1</AccordionItem>
646
+ <AccordionItem value="item2" title="Section 2">Content for section 2</AccordionItem>
647
+ {/snippet}
648
+ </Accordion>
649
+ ```
650
+
651
+ ### Slider
652
+
653
+ Range slider with drag, keyboard support, and optional value display.
654
+
655
+ **Props:**
656
+
657
+ | Prop | Type | Default | Description |
658
+ | ----------- | --------- | ------- | --------------------- |
659
+ | `value` | `number` | `0` | Bindable slider value |
660
+ | `min` | `number` | `0` | Minimum value |
661
+ | `max` | `number` | `100` | Maximum value |
662
+ | `step` | `number` | `1` | Value increment step |
663
+ | `disabled` | `boolean` | `false` | Disabled state |
664
+ | `showValue` | `boolean` | `false` | Show value tooltip |
665
+ | `showTicks` | `boolean` | `false` | Show tick marks |
666
+
667
+ **Usage:**
668
+
669
+ ```svelte
670
+ <script>
671
+ import { Slider } from '@aspect-ops/exon-ui';
672
+
673
+ let volume = $state(50);
674
+ </script>
675
+
676
+ <Slider bind:value={volume} min={0} max={100} showValue />
677
+ ```
678
+
679
+ ### Carousel
680
+
681
+ Image/content carousel with swipe gestures and autoplay.
682
+
683
+ **Props:**
684
+
685
+ | Prop | Type | Default | Description |
686
+ | ------------------ | --------- | ------- | --------------------------- |
687
+ | `activeIndex` | `number` | `0` | Bindable active slide index |
688
+ | `autoplay` | `boolean` | `false` | Enable autoplay |
689
+ | `autoplayInterval` | `number` | `5000` | Autoplay interval (ms) |
690
+ | `loop` | `boolean` | `true` | Loop slides |
691
+ | `showIndicators` | `boolean` | `true` | Show dot indicators |
692
+ | `showArrows` | `boolean` | `true` | Show navigation arrows |
693
+
694
+ **Usage:**
695
+
696
+ ```svelte
697
+ <script>
698
+ import { Carousel, CarouselSlide } from '@aspect-ops/exon-ui';
699
+
700
+ let activeSlide = $state(0);
701
+ </script>
702
+
703
+ <Carousel bind:activeIndex={activeSlide} autoplay loop>
704
+ {#snippet children()}
705
+ <CarouselSlide><img src="slide1.jpg" alt="Slide 1" /></CarouselSlide>
706
+ <CarouselSlide><img src="slide2.jpg" alt="Slide 2" /></CarouselSlide>
707
+ <CarouselSlide><img src="slide3.jpg" alt="Slide 3" /></CarouselSlide>
708
+ {/snippet}
709
+ </Carousel>
710
+ ```
711
+
712
+ ### Image
713
+
714
+ Lazy-loading image with placeholder and fallback support.
715
+
716
+ **Props:**
717
+
718
+ | Prop | Type | Default | Description |
719
+ | ------------- | ------------------- | --------- | ------------------------------- |
720
+ | `src` | `string` | - | Image source URL (required) |
721
+ | `alt` | `string` | - | Alt text (required) |
722
+ | `loading` | `'lazy' \| 'eager'` | `'lazy'` | Loading strategy |
723
+ | `objectFit` | `string` | `'cover'` | CSS object-fit value |
724
+ | `placeholder` | `string` | - | Placeholder image or color |
725
+ | `fallback` | `string` | - | Fallback image on error |
726
+ | `rounded` | `boolean \| string` | `false` | Border radius: sm, md, lg, full |
727
+ | `aspectRatio` | `string` | - | CSS aspect ratio |
728
+
729
+ **Usage:**
730
+
731
+ ```svelte
732
+ <script>
733
+ import { Image } from '@aspect-ops/exon-ui';
734
+ </script>
735
+
736
+ <Image
737
+ src="profile.jpg"
738
+ alt="User profile"
739
+ rounded="full"
740
+ aspectRatio="1/1"
741
+ placeholder="#e5e7eb"
742
+ fallback="default-avatar.png"
743
+ />
744
+ ```
745
+
746
+ ### Rating
747
+
748
+ Star rating display and input with half-star support.
749
+
750
+ **Props:**
751
+
752
+ | Prop | Type | Default | Description |
753
+ | ----------- | --------- | ------- | ----------------------- |
754
+ | `value` | `number` | `0` | Bindable rating value |
755
+ | `max` | `number` | `5` | Maximum rating |
756
+ | `allowHalf` | `boolean` | `false` | Allow half-star ratings |
757
+ | `readonly` | `boolean` | `false` | Read-only display mode |
758
+ | `disabled` | `boolean` | `false` | Disabled state |
759
+ | `size` | `Size` | `'md'` | Star size: sm, md, lg |
760
+ | `showValue` | `boolean` | `false` | Show numeric value |
761
+
762
+ **Usage:**
763
+
764
+ ```svelte
765
+ <script>
766
+ import { Rating } from '@aspect-ops/exon-ui';
767
+
768
+ let rating = $state(4.5);
769
+ </script>
770
+
771
+ <Rating bind:value={rating} allowHalf showValue />
772
+ ```
773
+
774
+ ## Navigation & Feedback Components
775
+
776
+ ### Stepper
777
+
778
+ Multi-step progress indicator with linear or non-linear navigation.
779
+
780
+ **Props:**
781
+
782
+ | Prop | Type | Default | Description |
783
+ | ------------- | ---------------------------- | -------------- | ----------------------------- |
784
+ | `activeStep` | `number` | `0` | Bindable active step index |
785
+ | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout orientation |
786
+ | `linear` | `boolean` | `true` | Enforce sequential navigation |
787
+
788
+ **Usage:**
789
+
790
+ ```svelte
791
+ <script>
792
+ import { Stepper, StepperStep } from '@aspect-ops/exon-ui';
793
+
794
+ let currentStep = $state(0);
795
+ </script>
796
+
797
+ <Stepper bind:activeStep={currentStep}>
798
+ {#snippet children()}
799
+ <StepperStep label="Personal Info" description="Enter your details" />
800
+ <StepperStep label="Address" description="Shipping information" />
801
+ <StepperStep label="Review" description="Confirm your order" />
802
+ {/snippet}
803
+ </Stepper>
804
+ ```
805
+
806
+ ### Chip
807
+
808
+ Compact tag/filter chips with selection and removal.
809
+
810
+ **Props:**
811
+
812
+ | Prop | Type | Default | Description |
813
+ | ----------- | ------------- | ----------- | ------------------------------------------------ |
814
+ | `variant` | `ChipVariant` | `'filled'` | Chip style: filled, outlined, soft |
815
+ | `color` | `ChipColor` | `'default'` | Color: default, primary, success, warning, error |
816
+ | `size` | `ChipSize` | `'md'` | Size: sm, md, lg |
817
+ | `removable` | `boolean` | `false` | Show remove button |
818
+ | `selected` | `boolean` | `false` | Selected state |
819
+ | `disabled` | `boolean` | `false` | Disabled state |
820
+
821
+ **Usage:**
822
+
823
+ ```svelte
824
+ <script>
825
+ import { Chip, ChipGroup } from '@aspect-ops/exon-ui';
826
+
827
+ let selected = $state(['tag1']);
828
+ </script>
829
+
830
+ <ChipGroup>
831
+ <Chip onclick={() => console.log('clicked')}>JavaScript</Chip>
832
+ <Chip color="primary" removable onremove={() => console.log('removed')}>Svelte</Chip>
833
+ <Chip variant="outlined" color="success">TypeScript</Chip>
834
+ </ChipGroup>
835
+ ```
836
+
837
+ ## Mobile Components
838
+
839
+ Components optimized for Capacitor mobile apps with gesture support, haptic feedback, and safe area handling.
840
+
841
+ | Component | Description |
842
+ | --------------- | ------------------------------------------- |
843
+ | `ActionSheet` | iOS/Android-style bottom action menu |
844
+ | `BottomSheet` | Draggable bottom sheet with snap points |
845
+ | `FAB` | Floating action button with positions |
846
+ | `FABGroup` | Speed dial FAB with expandable actions |
847
+ | `PullToRefresh` | Pull-to-refresh gesture for lists |
848
+ | `SwipeActions` | Swipe-to-reveal actions on list items |
849
+ | `SafeArea` | Safe area inset wrapper for notched devices |
850
+
851
+ ### ActionSheet
852
+
853
+ ```svelte
854
+ <script>
855
+ import { ActionSheet, ActionSheetItem, Button } from '@aspect-ops/exon-ui';
856
+
857
+ let open = $state(false);
858
+ </script>
859
+
860
+ <Button onclick={() => (open = true)}>Show Actions</Button>
861
+
862
+ <ActionSheet bind:open title="Choose an action" description="Select what you want to do">
863
+ {#snippet actions()}
864
+ <ActionSheetItem onclick={() => console.log('Edit')}>Edit</ActionSheetItem>
865
+ <ActionSheetItem onclick={() => console.log('Share')}>Share</ActionSheetItem>
866
+ <ActionSheetItem destructive onclick={() => console.log('Delete')}>Delete</ActionSheetItem>
867
+ {/snippet}
868
+ </ActionSheet>
869
+ ```
870
+
871
+ **Props:**
872
+
873
+ - `open` - Bindable open state
874
+ - `title` - Optional header title
875
+ - `description` - Optional header description
876
+ - `cancelLabel` - Cancel button text (default: "Cancel")
877
+ - `showCancel` - Show cancel button (default: true)
878
+ - `closeOnSelect` - Close when action selected (default: true)
879
+
880
+ ### BottomSheet
881
+
882
+ ```svelte
883
+ <script>
884
+ import { BottomSheet, BottomSheetHeader, BottomSheetBody, Button } from '@aspect-ops/exon-ui';
885
+
886
+ let open = $state(false);
887
+ </script>
888
+
889
+ <Button onclick={() => (open = true)}>Open Sheet</Button>
890
+
891
+ <BottomSheet bind:open snapPoints={['half', 'full']} defaultSnapPoint="half">
892
+ <BottomSheetHeader>
893
+ <h3>Sheet Title</h3>
894
+ </BottomSheetHeader>
895
+ <BottomSheetBody>
896
+ <p>Drag the handle to resize or swipe down to close.</p>
897
+ </BottomSheetBody>
898
+ </BottomSheet>
899
+ ```
900
+
901
+ **Props:**
902
+
903
+ - `open` - Bindable open state
904
+ - `snapPoints` - Array of snap points: `'min'` | `'half'` | `'full'` | number (px)
905
+ - `defaultSnapPoint` - Initial snap point (default: 'half')
906
+ - `showHandle` - Show drag handle (default: true)
907
+ - `closeOnBackdrop` - Close on backdrop click (default: true)
908
+ - `closeOnEscape` - Close on Escape key (default: true)
909
+
910
+ ### FAB (Floating Action Button)
911
+
912
+ ```svelte
913
+ <script>
914
+ import { FAB } from '@aspect-ops/exon-ui';
915
+ </script>
916
+
917
+ <!-- Basic FAB -->
918
+ <FAB onclick={() => console.log('clicked')}>+</FAB>
919
+
920
+ <!-- Positioned FAB -->
921
+ <FAB position="bottom-left" size="lg">📝</FAB>
922
+
923
+ <!-- Extended FAB with label -->
924
+ <FAB extended position="bottom-right">
925
+ {#snippet children()}➕{/snippet}
926
+ {#snippet label()}Add Item{/snippet}
927
+ </FAB>
928
+ ```
929
+
930
+ **Props:**
931
+
932
+ - `size` - `'sm'` | `'md'` | `'lg'` (44px, 56px, 72px)
933
+ - `position` - `'bottom-right'` | `'bottom-left'` | `'bottom-center'` | `'top-right'` | `'top-left'`
934
+ - `extended` - Extended FAB with label
935
+ - `disabled` - Disable the button
936
+
937
+ ### FABGroup (Speed Dial)
938
+
939
+ ```svelte
940
+ <script>
941
+ import { FABGroup } from '@aspect-ops/exon-ui';
942
+
943
+ const actions = [
944
+ { icon: '📷', label: 'Camera', onAction: () => console.log('Camera') },
945
+ { icon: '🖼️', label: 'Gallery', onAction: () => console.log('Gallery') },
946
+ { icon: '📎', label: 'Attach', onAction: () => console.log('Attach') }
947
+ ];
948
+ </script>
949
+
950
+ <FABGroup {actions} icon="+" closeIcon="×" position="bottom-right" direction="up" />
951
+ ```
952
+
953
+ **Props:**
954
+
955
+ - `actions` - Array of `{ icon, label, onAction }` objects
956
+ - `icon` - Main FAB icon (default: '+')
957
+ - `closeIcon` - Icon when open (default: '×')
958
+ - `position` - Same as FAB positions
959
+ - `direction` - `'up'` | `'down'` | `'left'` | `'right'`
960
+
961
+ ### PullToRefresh
962
+
963
+ ```svelte
964
+ <script>
965
+ import { PullToRefresh } from '@aspect-ops/exon-ui';
966
+
967
+ let refreshing = $state(false);
968
+
969
+ async function handleRefresh() {
970
+ // Fetch new data
971
+ await new Promise((r) => setTimeout(r, 2000));
972
+ refreshing = false;
359
973
  }
360
- </style>
974
+ </script>
975
+
976
+ <PullToRefresh bind:refreshing onrefresh={handleRefresh}>
977
+ <div class="content">
978
+ <p>Pull down to refresh...</p>
979
+ <!-- Your scrollable content -->
980
+ </div>
981
+ </PullToRefresh>
982
+ ```
983
+
984
+ **Props:**
985
+
986
+ - `refreshing` - Bindable loading state
987
+ - `threshold` - Pull distance to trigger (default: 80px)
988
+ - `maxPull` - Maximum pull distance (default: 150px)
989
+ - `disabled` - Disable pull-to-refresh
990
+ - `onrefresh` - Callback when threshold reached
991
+
992
+ ### SwipeActions
993
+
994
+ ```svelte
995
+ <script>
996
+ import { SwipeActions } from '@aspect-ops/exon-ui';
997
+
998
+ const leftActions = [
999
+ { icon: '📌', label: 'Pin', color: '#3b82f6', onAction: () => console.log('Pin') }
1000
+ ];
1001
+
1002
+ const rightActions = [
1003
+ { icon: '🗑️', label: 'Delete', color: '#ef4444', onAction: () => console.log('Delete') }
1004
+ ];
1005
+ </script>
1006
+
1007
+ <SwipeActions {leftActions} {rightActions}>
1008
+ <div class="list-item">
1009
+ <p>Swipe me left or right</p>
1010
+ </div>
1011
+ </SwipeActions>
1012
+ ```
1013
+
1014
+ **Props:**
1015
+
1016
+ - `leftActions` - Actions revealed on swipe right
1017
+ - `rightActions` - Actions revealed on swipe left
1018
+ - `threshold` - Swipe distance to reveal (default: 60px)
1019
+ - `disabled` - Disable swipe gestures
1020
+
1021
+ **Action object:**
1022
+
1023
+ - `icon` - Icon string or emoji
1024
+ - `label` - Action label text
1025
+ - `color` - Background color
1026
+ - `onAction` - Callback when tapped
1027
+
1028
+ ### SafeArea
1029
+
1030
+ ```svelte
1031
+ <script>
1032
+ import { SafeArea } from '@aspect-ops/exon-ui';
1033
+ </script>
1034
+
1035
+ <!-- All edges (default) -->
1036
+ <SafeArea>
1037
+ <main>Content with safe area padding on all sides</main>
1038
+ </SafeArea>
1039
+
1040
+ <!-- Specific edges -->
1041
+ <SafeArea edges={['top', 'bottom']}>
1042
+ <main>Only top and bottom safe area</main>
1043
+ </SafeArea>
1044
+ ```
1045
+
1046
+ **Props:**
1047
+
1048
+ - `edges` - Array of edges: `'top'` | `'right'` | `'bottom'` | `'left'` (default: all)
1049
+
1050
+ ## Theming & Customization
1051
+
1052
+ ### Quick Setup
1053
+
1054
+ ```svelte
1055
+ <script>
1056
+ // In your root layout (+layout.svelte)
1057
+ import '@aspect-ops/exon-ui/styles';
1058
+ </script>
1059
+ ```
1060
+
1061
+ ### Custom Brand Colors
1062
+
1063
+ Override CSS variables in your app's global styles:
1064
+
1065
+ ```css
1066
+ /* src/app.css or global styles */
1067
+ :root {
1068
+ /* Primary brand color */
1069
+ --color-primary: #8b5cf6; /* Purple */
1070
+ --color-primary-hover: #7c3aed;
1071
+ --color-primary-active: #6d28d9;
1072
+ --color-primary-bg: #ede9fe;
1073
+
1074
+ /* Secondary */
1075
+ --color-secondary: #64748b;
1076
+ --color-secondary-hover: #475569;
1077
+
1078
+ /* Destructive/Error */
1079
+ --color-destructive: #dc2626;
1080
+ --color-error: #dc2626;
1081
+
1082
+ /* Success */
1083
+ --color-success: #16a34a;
1084
+
1085
+ /* Warning */
1086
+ --color-warning: #d97706;
1087
+ }
1088
+ ```
1089
+
1090
+ ### Full Design Token Reference
1091
+
1092
+ ```css
1093
+ :root {
1094
+ /* ===== COLORS ===== */
1095
+
1096
+ /* Backgrounds */
1097
+ --color-bg: #fcfcfc; /* Main background */
1098
+ --color-bg-muted: #f9f9f9; /* Subtle background */
1099
+ --color-bg-subtle: #f0f0f0; /* More contrast */
1100
+ --color-bg-hover: #e8e8e8; /* Hover state */
1101
+ --color-bg-active: #e0e0e0; /* Active/pressed */
1102
+ --color-bg-elevated: #ffffff; /* Cards, modals */
1103
+ --color-bg-card: #ffffff; /* Card backgrounds */
1104
+
1105
+ /* Text */
1106
+ --color-text: #202020; /* Primary text */
1107
+ --color-text-muted: #646464; /* Secondary text */
1108
+ --color-text-subtle: #838383; /* Tertiary text */
1109
+ --color-text-inverse: #ffffff; /* Text on dark backgrounds */
1110
+
1111
+ /* Borders */
1112
+ --color-border: #d9d9d9;
1113
+ --color-border-hover: #cecece;
1114
+ --color-border-active: #bbbbbb;
1115
+
1116
+ /* ===== TYPOGRAPHY ===== */
1117
+
1118
+ --font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
1119
+ --font-family-mono: ui-monospace, 'SF Mono', Menlo, Monaco, monospace;
1120
+
1121
+ /* Fluid type scale (responsive) */
1122
+ --text-xs: clamp(0.69rem, 0.66rem + 0.14vw, 0.78rem); /* ~11-12px */
1123
+ --text-sm: clamp(0.83rem, 0.79rem + 0.21vw, 0.97rem); /* ~13-16px */
1124
+ --text-base: clamp(1rem, 0.93rem + 0.33vw, 1.125rem); /* 16-18px */
1125
+ --text-lg: clamp(1.13rem, 1.03rem + 0.47vw, 1.41rem); /* ~18-23px */
1126
+ --text-xl: clamp(1.27rem, 1.14rem + 0.65vw, 1.76rem); /* ~20-28px */
1127
+ --text-2xl: clamp(1.42rem, 1.25rem + 0.89vw, 2.2rem); /* ~23-35px */
1128
+ --text-3xl: clamp(1.6rem, 1.37rem + 1.19vw, 2.75rem); /* ~26-44px */
1129
+ --text-4xl: clamp(1.8rem, 1.49rem + 1.57vw, 3.43rem); /* ~29-55px */
1130
+
1131
+ /* Font weights */
1132
+ --font-normal: 400;
1133
+ --font-medium: 500;
1134
+ --font-semibold: 600;
1135
+ --font-bold: 700;
1136
+
1137
+ /* ===== SPACING ===== */
1138
+
1139
+ --space-xs: clamp(0.25rem, 0.23rem + 0.11vw, 0.31rem); /* 4-5px */
1140
+ --space-sm: clamp(0.5rem, 0.46rem + 0.22vw, 0.63rem); /* 8-10px */
1141
+ --space-md: clamp(1rem, 0.93rem + 0.33vw, 1.125rem); /* 16-18px */
1142
+ --space-lg: clamp(1.5rem, 1.39rem + 0.54vw, 1.75rem); /* 24-28px */
1143
+ --space-xl: clamp(2rem, 1.85rem + 0.76vw, 2.5rem); /* 32-40px */
1144
+ --space-2xl: clamp(3rem, 2.78rem + 1.09vw, 3.75rem); /* 48-60px */
1145
+
1146
+ /* ===== BORDER RADIUS ===== */
1147
+
1148
+ --radius-sm: 0.25rem; /* 4px */
1149
+ --radius-md: 0.375rem; /* 6px */
1150
+ --radius-lg: 0.5rem; /* 8px */
1151
+ --radius-xl: 0.75rem; /* 12px */
1152
+ --radius-2xl: 1rem; /* 16px */
1153
+ --radius-full: 9999px; /* Pill/circle */
1154
+
1155
+ /* ===== SHADOWS ===== */
1156
+
1157
+ --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1);
1158
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
1159
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
1160
+ --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1);
1161
+
1162
+ /* ===== TRANSITIONS ===== */
1163
+
1164
+ --transition-fast: 150ms ease;
1165
+ --transition-base: 200ms ease;
1166
+ --transition-slow: 300ms ease;
1167
+
1168
+ /* ===== MOBILE ===== */
1169
+
1170
+ --touch-target-min: 44px;
1171
+ --safe-area-top: env(safe-area-inset-top, 0px);
1172
+ --safe-area-bottom: env(safe-area-inset-bottom, 0px);
1173
+ }
361
1174
  ```
362
1175
 
363
1176
  ### Dark Mode
364
1177
 
365
- Toggle dark mode by setting `data-theme="dark"` on the html element:
1178
+ The library supports dark mode via `data-theme` attribute or system preference:
366
1179
 
367
1180
  ```svelte
368
1181
  <script>
1182
+ let isDark = $state(false);
1183
+
369
1184
  function toggleTheme() {
370
- const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
371
- document.documentElement.setAttribute('data-theme', isDark ? 'light' : 'dark');
1185
+ isDark = !isDark;
1186
+ document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
372
1187
  }
1188
+
1189
+ // Respect system preference on load
1190
+ import { onMount } from 'svelte';
1191
+ onMount(() => {
1192
+ if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
1193
+ isDark = true;
1194
+ document.documentElement.setAttribute('data-theme', 'dark');
1195
+ }
1196
+ });
373
1197
  </script>
374
1198
 
375
- <button onclick={toggleTheme}>Toggle Theme</button>
1199
+ <Switch bind:checked={isDark} onchange={toggleTheme}>
1200
+ {#snippet children()}Dark Mode{/snippet}
1201
+ </Switch>
376
1202
  ```
377
1203
 
378
- Dark theme variables are automatically applied:
1204
+ Dark theme automatically adjusts all colors:
379
1205
 
380
1206
  ```css
381
1207
  [data-theme='dark'] {
382
- --color-bg: #111827;
383
- --color-bg-muted: #1f2937;
384
- --color-bg-elevated: #1f2937;
385
- --color-text: #f9fafb;
386
- --color-border: #374151;
1208
+ --color-bg: #111111;
1209
+ --color-bg-muted: #191919;
1210
+ --color-bg-elevated: #222222;
1211
+ --color-text: #eeeeee;
1212
+ --color-text-muted: #b4b4b4;
1213
+ --color-border: #3a3a3a;
1214
+ /* All other colors auto-adapt */
387
1215
  }
388
1216
  ```
389
1217
 
1218
+ ### Component-Level Customization
1219
+
1220
+ Override styles for specific components using CSS classes:
1221
+
1222
+ ```svelte
1223
+ <Button class="my-custom-button" variant="primary">Custom Button</Button>
1224
+
1225
+ <style>
1226
+ :global(.my-custom-button) {
1227
+ --color-primary: #10b981;
1228
+ border-radius: var(--radius-full);
1229
+ text-transform: uppercase;
1230
+ }
1231
+ </style>
1232
+ ```
1233
+
1234
+ ### Platform-Specific Styling
1235
+
1236
+ Components adapt to iOS/Android automatically. Set platform manually:
1237
+
1238
+ ```svelte
1239
+ <script>
1240
+ import { onMount } from 'svelte';
1241
+
1242
+ onMount(() => {
1243
+ // Auto-detect or set manually
1244
+ const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
1245
+ document.documentElement.setAttribute('data-platform', isIOS ? 'ios' : 'android');
1246
+ });
1247
+ </script>
1248
+ ```
1249
+
390
1250
  ## Mobile / Capacitor Support
391
1251
 
392
1252
  Components are designed with mobile-first principles:
@@ -395,12 +1255,27 @@ Components are designed with mobile-first principles:
395
1255
  - **Safe area inset handling** for notched devices (iPhone, Android gesture bar)
396
1256
  - **Responsive breakpoints** for adaptive layouts
397
1257
  - **Hardware-accelerated animations** for smooth 60fps performance
1258
+ - **Haptic feedback** integration (requires Capacitor Haptics plugin)
1259
+ - **Platform-adaptive styling** for iOS and Android
1260
+
1261
+ ### Capacitor Integration
398
1262
 
399
- ### Safe Area Example
1263
+ ```bash
1264
+ # Install Capacitor plugins for full mobile support
1265
+ npm install @capacitor/haptics @capacitor/status-bar
1266
+ ```
400
1267
 
401
1268
  ```svelte
402
- <BottomNav {items} />
403
- <!-- Automatically includes padding-bottom: env(safe-area-inset-bottom) -->
1269
+ <script>
1270
+ import { SafeArea, BottomNav, PullToRefresh } from '@aspect-ops/exon-ui';
1271
+ </script>
1272
+
1273
+ <SafeArea edges={['top', 'bottom']}>
1274
+ <PullToRefresh onrefresh={handleRefresh}>
1275
+ <main>Your content here</main>
1276
+ </PullToRefresh>
1277
+ <BottomNav {items} />
1278
+ </SafeArea>
404
1279
  ```
405
1280
 
406
1281
  ## TypeScript