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