@aspect-ops/exon-ui 0.1.0 → 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.
- package/README.md +141 -16
- package/dist/components/NumberInput/NumberInput.svelte +293 -0
- package/dist/components/NumberInput/NumberInput.svelte.d.ts +16 -0
- package/dist/components/NumberInput/index.d.ts +1 -0
- package/dist/components/NumberInput/index.js +1 -0
- package/dist/components/Pagination/Pagination.svelte +243 -0
- package/dist/components/Pagination/Pagination.svelte.d.ts +10 -0
- package/dist/components/Pagination/index.d.ts +1 -0
- package/dist/components/Pagination/index.js +1 -0
- package/dist/components/ToggleGroup/ToggleGroup.svelte +91 -0
- package/dist/components/ToggleGroup/ToggleGroup.svelte.d.ts +13 -0
- package/dist/components/ToggleGroup/ToggleGroupItem.svelte +158 -0
- package/dist/components/ToggleGroup/ToggleGroupItem.svelte.d.ts +9 -0
- package/dist/components/ToggleGroup/index.d.ts +3 -0
- package/dist/components/ToggleGroup/index.js +2 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/input.d.ts +15 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,22 +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
|
|
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
|
|
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 |
|
|
65
67
|
|
|
66
68
|
### Navigation Components
|
|
67
69
|
|
|
@@ -74,6 +76,7 @@ npm install @aspect-ops/exon-ui
|
|
|
74
76
|
| `Navbar`, `NavItem` | Responsive header with mobile menu |
|
|
75
77
|
| `Sidebar`, `SidebarItem`, `SidebarGroup` | Collapsible sidebar navigation |
|
|
76
78
|
| `Stepper`, `StepperStep` | Multi-step progress indicator |
|
|
79
|
+
| `Pagination` | Page navigation with ellipsis |
|
|
77
80
|
|
|
78
81
|
### Data Display Components
|
|
79
82
|
|
|
@@ -492,6 +495,128 @@ One-time password input with auto-focus and paste support.
|
|
|
492
495
|
<OTPInput bind:value={otp} length={6} oncomplete={(code) => console.log('OTP:', code)} />
|
|
493
496
|
```
|
|
494
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
|
+
|
|
495
620
|
## Data Display Components
|
|
496
621
|
|
|
497
622
|
### Accordion
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
value?: number | null;
|
|
4
|
+
min?: number;
|
|
5
|
+
max?: number;
|
|
6
|
+
step?: number;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
id?: string;
|
|
11
|
+
error?: boolean;
|
|
12
|
+
class?: string;
|
|
13
|
+
onValueChange?: (value: number | null) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
value = $bindable(null),
|
|
18
|
+
min,
|
|
19
|
+
max,
|
|
20
|
+
step = 1,
|
|
21
|
+
disabled = false,
|
|
22
|
+
placeholder,
|
|
23
|
+
name,
|
|
24
|
+
id,
|
|
25
|
+
error = false,
|
|
26
|
+
class: className = '',
|
|
27
|
+
onValueChange
|
|
28
|
+
}: Props = $props();
|
|
29
|
+
|
|
30
|
+
let inputValue = $state(value?.toString() ?? '');
|
|
31
|
+
|
|
32
|
+
// Sync internal state with external value prop
|
|
33
|
+
$effect(() => {
|
|
34
|
+
inputValue = value?.toString() ?? '';
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const isDecrementDisabled = $derived(
|
|
38
|
+
disabled || (min !== undefined && value !== null && value <= min)
|
|
39
|
+
);
|
|
40
|
+
const isIncrementDisabled = $derived(
|
|
41
|
+
disabled || (max !== undefined && value !== null && value >= max)
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
function handleIncrement() {
|
|
45
|
+
if (isIncrementDisabled) return;
|
|
46
|
+
|
|
47
|
+
const currentVal = value ?? 0;
|
|
48
|
+
const newValue = currentVal + step;
|
|
49
|
+
const clampedValue = max !== undefined ? Math.min(newValue, max) : newValue;
|
|
50
|
+
|
|
51
|
+
value = clampedValue;
|
|
52
|
+
onValueChange?.(clampedValue);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function handleDecrement() {
|
|
56
|
+
if (isDecrementDisabled) return;
|
|
57
|
+
|
|
58
|
+
const currentVal = value ?? 0;
|
|
59
|
+
const newValue = currentVal - step;
|
|
60
|
+
const clampedValue = min !== undefined ? Math.max(newValue, min) : newValue;
|
|
61
|
+
|
|
62
|
+
value = clampedValue;
|
|
63
|
+
onValueChange?.(clampedValue);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function handleInput(e: Event & { currentTarget: HTMLInputElement }) {
|
|
67
|
+
inputValue = e.currentTarget.value;
|
|
68
|
+
|
|
69
|
+
if (inputValue === '') {
|
|
70
|
+
value = null;
|
|
71
|
+
onValueChange?.(null);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const parsed = parseFloat(inputValue);
|
|
76
|
+
if (!isNaN(parsed)) {
|
|
77
|
+
value = parsed;
|
|
78
|
+
onValueChange?.(parsed);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function handleBlur() {
|
|
83
|
+
if (value === null) {
|
|
84
|
+
inputValue = '';
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Clamp value to min/max on blur
|
|
89
|
+
let clampedValue = value;
|
|
90
|
+
if (min !== undefined && clampedValue < min) {
|
|
91
|
+
clampedValue = min;
|
|
92
|
+
}
|
|
93
|
+
if (max !== undefined && clampedValue > max) {
|
|
94
|
+
clampedValue = max;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (clampedValue !== value) {
|
|
98
|
+
value = clampedValue;
|
|
99
|
+
onValueChange?.(clampedValue);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
inputValue = clampedValue.toString();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
106
|
+
if (e.key === 'ArrowUp') {
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
handleIncrement();
|
|
109
|
+
} else if (e.key === 'ArrowDown') {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
handleDecrement();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
</script>
|
|
115
|
+
|
|
116
|
+
<div
|
|
117
|
+
class="number-input {error ? 'number-input--error' : ''} {disabled
|
|
118
|
+
? 'number-input--disabled'
|
|
119
|
+
: ''} {className}"
|
|
120
|
+
>
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
class="number-input__button number-input__button--decrement"
|
|
124
|
+
disabled={isDecrementDisabled}
|
|
125
|
+
onclick={handleDecrement}
|
|
126
|
+
aria-label="Decrement value"
|
|
127
|
+
>
|
|
128
|
+
<svg
|
|
129
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
130
|
+
width="16"
|
|
131
|
+
height="16"
|
|
132
|
+
viewBox="0 0 24 24"
|
|
133
|
+
fill="none"
|
|
134
|
+
stroke="currentColor"
|
|
135
|
+
stroke-width="2"
|
|
136
|
+
stroke-linecap="round"
|
|
137
|
+
stroke-linejoin="round"
|
|
138
|
+
>
|
|
139
|
+
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
140
|
+
</svg>
|
|
141
|
+
</button>
|
|
142
|
+
|
|
143
|
+
<input
|
|
144
|
+
type="text"
|
|
145
|
+
inputmode="decimal"
|
|
146
|
+
class="number-input__field"
|
|
147
|
+
role="spinbutton"
|
|
148
|
+
aria-valuemin={min}
|
|
149
|
+
aria-valuemax={max}
|
|
150
|
+
aria-valuenow={value ?? undefined}
|
|
151
|
+
{name}
|
|
152
|
+
{id}
|
|
153
|
+
{placeholder}
|
|
154
|
+
{disabled}
|
|
155
|
+
aria-invalid={error}
|
|
156
|
+
value={inputValue}
|
|
157
|
+
oninput={handleInput}
|
|
158
|
+
onblur={handleBlur}
|
|
159
|
+
onkeydown={handleKeyDown}
|
|
160
|
+
/>
|
|
161
|
+
|
|
162
|
+
<button
|
|
163
|
+
type="button"
|
|
164
|
+
class="number-input__button number-input__button--increment"
|
|
165
|
+
disabled={isIncrementDisabled}
|
|
166
|
+
onclick={handleIncrement}
|
|
167
|
+
aria-label="Increment value"
|
|
168
|
+
>
|
|
169
|
+
<svg
|
|
170
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
171
|
+
width="16"
|
|
172
|
+
height="16"
|
|
173
|
+
viewBox="0 0 24 24"
|
|
174
|
+
fill="none"
|
|
175
|
+
stroke="currentColor"
|
|
176
|
+
stroke-width="2"
|
|
177
|
+
stroke-linecap="round"
|
|
178
|
+
stroke-linejoin="round"
|
|
179
|
+
>
|
|
180
|
+
<line x1="12" y1="5" x2="12" y2="19"></line>
|
|
181
|
+
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
182
|
+
</svg>
|
|
183
|
+
</button>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<style>
|
|
187
|
+
.number-input {
|
|
188
|
+
display: flex;
|
|
189
|
+
align-items: stretch;
|
|
190
|
+
width: 100%;
|
|
191
|
+
gap: 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.number-input--disabled {
|
|
195
|
+
opacity: 0.5;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.number-input__button {
|
|
199
|
+
display: flex;
|
|
200
|
+
align-items: center;
|
|
201
|
+
justify-content: center;
|
|
202
|
+
min-width: var(--touch-target-min, 44px);
|
|
203
|
+
min-height: var(--touch-target-min, 44px);
|
|
204
|
+
padding: 0;
|
|
205
|
+
border: 1px solid var(--color-border, #e5e7eb);
|
|
206
|
+
background: var(--color-bg, #ffffff);
|
|
207
|
+
color: var(--color-text, #1f2937);
|
|
208
|
+
font-family: inherit;
|
|
209
|
+
cursor: pointer;
|
|
210
|
+
transition: all var(--transition-fast, 150ms ease);
|
|
211
|
+
-webkit-tap-highlight-color: transparent;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.number-input__button:hover:not(:disabled) {
|
|
215
|
+
background: var(--color-bg-muted, #f3f4f6);
|
|
216
|
+
border-color: var(--color-border-hover, #d1d5db);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.number-input__button:active:not(:disabled) {
|
|
220
|
+
background: var(--color-border, #e5e7eb);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.number-input__button:focus-visible {
|
|
224
|
+
outline: 2px solid var(--color-primary, #3b82f6);
|
|
225
|
+
outline-offset: 2px;
|
|
226
|
+
z-index: 1;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.number-input__button:disabled {
|
|
230
|
+
opacity: 0.3;
|
|
231
|
+
cursor: not-allowed;
|
|
232
|
+
pointer-events: none;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.number-input__button--decrement {
|
|
236
|
+
border-top-left-radius: var(--radius-md, 0.375rem);
|
|
237
|
+
border-bottom-left-radius: var(--radius-md, 0.375rem);
|
|
238
|
+
border-right: none;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.number-input__button--increment {
|
|
242
|
+
border-top-right-radius: var(--radius-md, 0.375rem);
|
|
243
|
+
border-bottom-right-radius: var(--radius-md, 0.375rem);
|
|
244
|
+
border-left: none;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.number-input__field {
|
|
248
|
+
flex: 1;
|
|
249
|
+
min-width: 0;
|
|
250
|
+
min-height: var(--touch-target-min, 44px);
|
|
251
|
+
padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
|
|
252
|
+
border: 1px solid var(--color-border, #e5e7eb);
|
|
253
|
+
border-left: none;
|
|
254
|
+
border-right: none;
|
|
255
|
+
background: var(--color-bg, #ffffff);
|
|
256
|
+
color: var(--color-text, #1f2937);
|
|
257
|
+
font-family: inherit;
|
|
258
|
+
font-size: var(--text-sm, 0.875rem);
|
|
259
|
+
line-height: 1.5;
|
|
260
|
+
text-align: center;
|
|
261
|
+
transition:
|
|
262
|
+
border-color var(--transition-fast, 150ms ease),
|
|
263
|
+
box-shadow var(--transition-fast, 150ms ease);
|
|
264
|
+
-webkit-tap-highlight-color: transparent;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.number-input__field::placeholder {
|
|
268
|
+
color: var(--color-text-muted, #9ca3af);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.number-input__field:focus {
|
|
272
|
+
outline: none;
|
|
273
|
+
border-color: var(--color-primary, #3b82f6);
|
|
274
|
+
box-shadow: 0 0 0 3px var(--color-primary-alpha, rgba(59, 130, 246, 0.1));
|
|
275
|
+
z-index: 1;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.number-input__field:disabled {
|
|
279
|
+
background: var(--color-bg-muted, #f3f4f6);
|
|
280
|
+
cursor: not-allowed;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* Error state */
|
|
284
|
+
.number-input--error .number-input__field,
|
|
285
|
+
.number-input--error .number-input__button {
|
|
286
|
+
border-color: var(--color-error, #ef4444);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.number-input--error .number-input__field:focus {
|
|
290
|
+
border-color: var(--color-error, #ef4444);
|
|
291
|
+
box-shadow: 0 0 0 3px var(--color-error-alpha, rgba(239, 68, 68, 0.1));
|
|
292
|
+
}
|
|
293
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
value?: number | null;
|
|
3
|
+
min?: number;
|
|
4
|
+
max?: number;
|
|
5
|
+
step?: number;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
id?: string;
|
|
10
|
+
error?: boolean;
|
|
11
|
+
class?: string;
|
|
12
|
+
onValueChange?: (value: number | null) => void;
|
|
13
|
+
}
|
|
14
|
+
declare const NumberInput: import("svelte").Component<Props, {}, "value">;
|
|
15
|
+
type NumberInput = ReturnType<typeof NumberInput>;
|
|
16
|
+
export default NumberInput;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as NumberInput } from './NumberInput.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as NumberInput } from './NumberInput.svelte';
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
currentPage: number;
|
|
4
|
+
totalPages: number;
|
|
5
|
+
siblingCount?: number;
|
|
6
|
+
onPageChange?: (page: number) => void;
|
|
7
|
+
class?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
currentPage,
|
|
12
|
+
totalPages,
|
|
13
|
+
siblingCount = 1,
|
|
14
|
+
onPageChange,
|
|
15
|
+
class: className = ''
|
|
16
|
+
}: Props = $props();
|
|
17
|
+
|
|
18
|
+
const canGoPrevious = $derived(currentPage > 1);
|
|
19
|
+
const canGoNext = $derived(currentPage < totalPages);
|
|
20
|
+
|
|
21
|
+
// Generate page numbers with ellipsis logic
|
|
22
|
+
const pageNumbers = $derived(() => {
|
|
23
|
+
const pages: (number | 'ellipsis-start' | 'ellipsis-end')[] = [];
|
|
24
|
+
|
|
25
|
+
// Always show first page
|
|
26
|
+
pages.push(1);
|
|
27
|
+
|
|
28
|
+
// Calculate the range around current page
|
|
29
|
+
const leftSibling = Math.max(currentPage - siblingCount, 2);
|
|
30
|
+
const rightSibling = Math.min(currentPage + siblingCount, totalPages - 1);
|
|
31
|
+
|
|
32
|
+
// Add ellipsis after first page if needed
|
|
33
|
+
if (leftSibling > 2) {
|
|
34
|
+
pages.push('ellipsis-start');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Add sibling pages
|
|
38
|
+
for (let i = leftSibling; i <= rightSibling; i++) {
|
|
39
|
+
pages.push(i);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Add ellipsis before last page if needed
|
|
43
|
+
if (rightSibling < totalPages - 1) {
|
|
44
|
+
pages.push('ellipsis-end');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Always show last page (if more than 1 page)
|
|
48
|
+
if (totalPages > 1) {
|
|
49
|
+
pages.push(totalPages);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return pages;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function handlePageClick(page: number) {
|
|
56
|
+
if (page !== currentPage && page >= 1 && page <= totalPages) {
|
|
57
|
+
onPageChange?.(page);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function handlePrevious() {
|
|
62
|
+
if (canGoPrevious) {
|
|
63
|
+
onPageChange?.(currentPage - 1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function handleNext() {
|
|
68
|
+
if (canGoNext) {
|
|
69
|
+
onPageChange?.(currentPage + 1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
74
|
+
switch (event.key) {
|
|
75
|
+
case 'ArrowLeft':
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
handlePrevious();
|
|
78
|
+
break;
|
|
79
|
+
case 'ArrowRight':
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
handleNext();
|
|
82
|
+
break;
|
|
83
|
+
case 'Home':
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
handlePageClick(1);
|
|
86
|
+
break;
|
|
87
|
+
case 'End':
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
handlePageClick(totalPages);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
96
|
+
<nav class="pagination {className}" aria-label="Pagination">
|
|
97
|
+
<div class="pagination-wrapper" role="group" onkeydown={handleKeyDown}>
|
|
98
|
+
<button
|
|
99
|
+
class="pagination-button pagination-button--previous"
|
|
100
|
+
disabled={!canGoPrevious}
|
|
101
|
+
onclick={handlePrevious}
|
|
102
|
+
aria-label="Previous page"
|
|
103
|
+
>
|
|
104
|
+
Previous
|
|
105
|
+
</button>
|
|
106
|
+
|
|
107
|
+
<div class="pagination-pages">
|
|
108
|
+
{#each pageNumbers() as item}
|
|
109
|
+
{#if typeof item === 'number'}
|
|
110
|
+
<button
|
|
111
|
+
class="pagination-button pagination-button--page"
|
|
112
|
+
class:pagination-button--current={item === currentPage}
|
|
113
|
+
onclick={() => handlePageClick(item)}
|
|
114
|
+
aria-label={item === currentPage ? `Current page, page ${item}` : `Go to page ${item}`}
|
|
115
|
+
aria-current={item === currentPage ? 'page' : undefined}
|
|
116
|
+
>
|
|
117
|
+
{item}
|
|
118
|
+
</button>
|
|
119
|
+
{:else}
|
|
120
|
+
<span class="pagination-ellipsis" aria-hidden="true">...</span>
|
|
121
|
+
{/if}
|
|
122
|
+
{/each}
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<button
|
|
126
|
+
class="pagination-button pagination-button--next"
|
|
127
|
+
disabled={!canGoNext}
|
|
128
|
+
onclick={handleNext}
|
|
129
|
+
aria-label="Next page"
|
|
130
|
+
>
|
|
131
|
+
Next
|
|
132
|
+
</button>
|
|
133
|
+
</div>
|
|
134
|
+
</nav>
|
|
135
|
+
|
|
136
|
+
<style>
|
|
137
|
+
.pagination {
|
|
138
|
+
font-family: inherit;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.pagination-wrapper {
|
|
142
|
+
display: flex;
|
|
143
|
+
align-items: center;
|
|
144
|
+
gap: 0.5rem;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.pagination-pages {
|
|
148
|
+
display: flex;
|
|
149
|
+
align-items: center;
|
|
150
|
+
gap: 0.25rem;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.pagination-button {
|
|
154
|
+
min-width: 2.75rem;
|
|
155
|
+
min-height: 2.75rem; /* 44px minimum touch target */
|
|
156
|
+
padding: 0.5rem 0.75rem;
|
|
157
|
+
border: 1px solid var(--pagination-border-color, #d1d5db);
|
|
158
|
+
border-radius: var(--pagination-border-radius, 0.375rem);
|
|
159
|
+
background-color: var(--pagination-bg-color, #ffffff);
|
|
160
|
+
color: var(--pagination-text-color, #374151);
|
|
161
|
+
font-family: inherit;
|
|
162
|
+
font-size: 0.875rem;
|
|
163
|
+
font-weight: 500;
|
|
164
|
+
cursor: pointer;
|
|
165
|
+
transition: all 0.2s ease;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.pagination-button:hover:not(:disabled) {
|
|
169
|
+
background-color: var(--pagination-hover-bg-color, #f3f4f6);
|
|
170
|
+
border-color: var(--pagination-hover-border-color, #9ca3af);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.pagination-button:focus-visible {
|
|
174
|
+
outline: 2px solid var(--pagination-focus-color, #3b82f6);
|
|
175
|
+
outline-offset: 2px;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.pagination-button:disabled {
|
|
179
|
+
opacity: 0.5;
|
|
180
|
+
cursor: not-allowed;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.pagination-button--page {
|
|
184
|
+
min-width: 2.75rem;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.pagination-button--current {
|
|
188
|
+
background-color: var(--pagination-current-bg-color, #3b82f6);
|
|
189
|
+
color: var(--pagination-current-text-color, #ffffff);
|
|
190
|
+
border-color: var(--pagination-current-border-color, #3b82f6);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.pagination-button--current:hover {
|
|
194
|
+
background-color: var(--pagination-current-hover-bg-color, #2563eb);
|
|
195
|
+
border-color: var(--pagination-current-hover-border-color, #2563eb);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.pagination-button--previous,
|
|
199
|
+
.pagination-button--next {
|
|
200
|
+
padding-left: 1rem;
|
|
201
|
+
padding-right: 1rem;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.pagination-ellipsis {
|
|
205
|
+
display: inline-flex;
|
|
206
|
+
align-items: center;
|
|
207
|
+
justify-content: center;
|
|
208
|
+
min-width: 2.75rem;
|
|
209
|
+
min-height: 2.75rem;
|
|
210
|
+
padding: 0.5rem;
|
|
211
|
+
color: var(--pagination-ellipsis-color, #6b7280);
|
|
212
|
+
font-size: 0.875rem;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* Mobile responsive: hide some siblings on small screens */
|
|
216
|
+
@media (max-width: 640px) {
|
|
217
|
+
.pagination-wrapper {
|
|
218
|
+
gap: 0.25rem;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.pagination-pages {
|
|
222
|
+
gap: 0.125rem;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.pagination-button--previous,
|
|
226
|
+
.pagination-button--next {
|
|
227
|
+
padding-left: 0.75rem;
|
|
228
|
+
padding-right: 0.75rem;
|
|
229
|
+
font-size: 0.75rem;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.pagination-button--page {
|
|
233
|
+
min-width: 2.75rem;
|
|
234
|
+
padding: 0.5rem;
|
|
235
|
+
font-size: 0.75rem;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.pagination-ellipsis {
|
|
239
|
+
min-width: 2rem;
|
|
240
|
+
padding: 0.25rem;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
currentPage: number;
|
|
3
|
+
totalPages: number;
|
|
4
|
+
siblingCount?: number;
|
|
5
|
+
onPageChange?: (page: number) => void;
|
|
6
|
+
class?: string;
|
|
7
|
+
}
|
|
8
|
+
declare const Pagination: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type Pagination = ReturnType<typeof Pagination>;
|
|
10
|
+
export default Pagination;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Pagination } from './Pagination.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Pagination } from './Pagination.svelte';
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { setContext } from 'svelte';
|
|
3
|
+
import type { ToggleGroupType, ToggleGroupOrientation } from '../../types/index.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
type?: ToggleGroupType;
|
|
7
|
+
value?: string | string[];
|
|
8
|
+
onValueChange?: (value: string | string[]) => void;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
orientation?: ToggleGroupOrientation;
|
|
11
|
+
class?: string;
|
|
12
|
+
children?: import('svelte').Snippet;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
type = 'single',
|
|
17
|
+
value = $bindable(type === 'single' ? '' : []),
|
|
18
|
+
onValueChange,
|
|
19
|
+
disabled = false,
|
|
20
|
+
orientation = 'horizontal',
|
|
21
|
+
class: className = '',
|
|
22
|
+
children
|
|
23
|
+
}: Props = $props();
|
|
24
|
+
|
|
25
|
+
const handleToggle = (itemValue: string) => {
|
|
26
|
+
if (disabled) return;
|
|
27
|
+
|
|
28
|
+
let newValue: string | string[];
|
|
29
|
+
|
|
30
|
+
if (type === 'single') {
|
|
31
|
+
// Single mode: toggle or select new value
|
|
32
|
+
newValue = (value as string) === itemValue ? '' : itemValue;
|
|
33
|
+
} else {
|
|
34
|
+
// Multiple mode: toggle item in array
|
|
35
|
+
const currentArray = (value as string[]) || [];
|
|
36
|
+
if (currentArray.includes(itemValue)) {
|
|
37
|
+
newValue = currentArray.filter((v) => v !== itemValue);
|
|
38
|
+
} else {
|
|
39
|
+
newValue = [...currentArray, itemValue];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
value = newValue;
|
|
44
|
+
onValueChange?.(newValue);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const isSelected = (itemValue: string): boolean => {
|
|
48
|
+
if (type === 'single') {
|
|
49
|
+
return (value as string) === itemValue;
|
|
50
|
+
} else {
|
|
51
|
+
return ((value as string[]) || []).includes(itemValue);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Set context for child items
|
|
56
|
+
setContext('toggleGroup', {
|
|
57
|
+
get type() {
|
|
58
|
+
return type;
|
|
59
|
+
},
|
|
60
|
+
get disabled() {
|
|
61
|
+
return disabled;
|
|
62
|
+
},
|
|
63
|
+
onToggle: handleToggle,
|
|
64
|
+
isSelected
|
|
65
|
+
});
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<div
|
|
69
|
+
class="toggle-group toggle-group--{orientation} {className}"
|
|
70
|
+
role={type === 'single' ? 'radiogroup' : 'group'}
|
|
71
|
+
aria-disabled={disabled}
|
|
72
|
+
>
|
|
73
|
+
{#if children}
|
|
74
|
+
{@render children()}
|
|
75
|
+
{/if}
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<style>
|
|
79
|
+
.toggle-group {
|
|
80
|
+
display: inline-flex;
|
|
81
|
+
font-family: inherit;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.toggle-group--horizontal {
|
|
85
|
+
flex-direction: row;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.toggle-group--vertical {
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
}
|
|
91
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ToggleGroupType, ToggleGroupOrientation } from '../../types/index.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
type?: ToggleGroupType;
|
|
4
|
+
value?: string | string[];
|
|
5
|
+
onValueChange?: (value: string | string[]) => void;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
orientation?: ToggleGroupOrientation;
|
|
8
|
+
class?: string;
|
|
9
|
+
children?: import('svelte').Snippet;
|
|
10
|
+
}
|
|
11
|
+
declare const ToggleGroup: import("svelte").Component<Props, {}, "value">;
|
|
12
|
+
type ToggleGroup = ReturnType<typeof ToggleGroup>;
|
|
13
|
+
export default ToggleGroup;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getContext } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
value: string;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
class?: string;
|
|
8
|
+
children?: import('svelte').Snippet;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { value, disabled = false, class: className = '', children }: Props = $props();
|
|
12
|
+
|
|
13
|
+
const context = getContext<{
|
|
14
|
+
type: 'single' | 'multiple';
|
|
15
|
+
disabled: boolean;
|
|
16
|
+
onToggle: (value: string) => void;
|
|
17
|
+
isSelected: (value: string) => boolean;
|
|
18
|
+
}>('toggleGroup');
|
|
19
|
+
|
|
20
|
+
const isGroupDisabled = context?.disabled ?? false;
|
|
21
|
+
const isDisabled = $derived(disabled || isGroupDisabled);
|
|
22
|
+
const isPressed = $derived(context?.isSelected(value) ?? false);
|
|
23
|
+
|
|
24
|
+
const handleClick = () => {
|
|
25
|
+
if (isDisabled) return;
|
|
26
|
+
context?.onToggle(value);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const handleKeydown = (e: KeyboardEvent) => {
|
|
30
|
+
const target = e.currentTarget as HTMLElement;
|
|
31
|
+
const group = target.parentElement;
|
|
32
|
+
if (!group) return;
|
|
33
|
+
|
|
34
|
+
const items = Array.from(group.querySelectorAll('[role="radio"], [role="button"]'));
|
|
35
|
+
const currentIndex = items.indexOf(target);
|
|
36
|
+
|
|
37
|
+
let nextIndex = currentIndex;
|
|
38
|
+
|
|
39
|
+
switch (e.key) {
|
|
40
|
+
case 'ArrowRight':
|
|
41
|
+
case 'ArrowDown':
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
nextIndex = (currentIndex + 1) % items.length;
|
|
44
|
+
break;
|
|
45
|
+
case 'ArrowLeft':
|
|
46
|
+
case 'ArrowUp':
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
nextIndex = currentIndex - 1;
|
|
49
|
+
if (nextIndex < 0) nextIndex = items.length - 1;
|
|
50
|
+
break;
|
|
51
|
+
case ' ':
|
|
52
|
+
case 'Enter':
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
handleClick();
|
|
55
|
+
return;
|
|
56
|
+
default:
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
(items[nextIndex] as HTMLElement)?.focus();
|
|
61
|
+
};
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<button
|
|
65
|
+
class="toggle-group-item {isPressed ? 'toggle-group-item--pressed' : ''} {className}"
|
|
66
|
+
role={context?.type === 'single' ? 'radio' : 'button'}
|
|
67
|
+
aria-pressed={isPressed}
|
|
68
|
+
aria-checked={context?.type === 'single' ? isPressed : undefined}
|
|
69
|
+
aria-disabled={isDisabled}
|
|
70
|
+
disabled={isDisabled}
|
|
71
|
+
tabindex={isDisabled ? -1 : 0}
|
|
72
|
+
onclick={handleClick}
|
|
73
|
+
onkeydown={handleKeydown}
|
|
74
|
+
>
|
|
75
|
+
{#if children}
|
|
76
|
+
{@render children()}
|
|
77
|
+
{/if}
|
|
78
|
+
</button>
|
|
79
|
+
|
|
80
|
+
<style>
|
|
81
|
+
.toggle-group-item {
|
|
82
|
+
position: relative;
|
|
83
|
+
display: inline-flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
justify-content: center;
|
|
86
|
+
min-height: var(--touch-target-min, 44px);
|
|
87
|
+
min-width: var(--touch-target-min, 44px);
|
|
88
|
+
padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
|
|
89
|
+
background: var(--color-bg, #ffffff);
|
|
90
|
+
color: var(--color-text, #1f2937);
|
|
91
|
+
border: 1px solid var(--color-border, #e5e7eb);
|
|
92
|
+
font-family: inherit;
|
|
93
|
+
font-size: var(--text-sm, 0.875rem);
|
|
94
|
+
font-weight: 500;
|
|
95
|
+
cursor: pointer;
|
|
96
|
+
transition: all var(--transition-fast, 150ms ease);
|
|
97
|
+
-webkit-tap-highlight-color: transparent;
|
|
98
|
+
margin-left: -1px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.toggle-group-item:first-child {
|
|
102
|
+
margin-left: 0;
|
|
103
|
+
border-top-left-radius: var(--radius-md, 0.375rem);
|
|
104
|
+
border-bottom-left-radius: var(--radius-md, 0.375rem);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.toggle-group-item:last-child {
|
|
108
|
+
border-top-right-radius: var(--radius-md, 0.375rem);
|
|
109
|
+
border-bottom-right-radius: var(--radius-md, 0.375rem);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
:global(.toggle-group--vertical) .toggle-group-item {
|
|
113
|
+
margin-left: 0;
|
|
114
|
+
margin-top: -1px;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
:global(.toggle-group--vertical) .toggle-group-item:first-child {
|
|
118
|
+
margin-top: 0;
|
|
119
|
+
border-top-left-radius: var(--radius-md, 0.375rem);
|
|
120
|
+
border-top-right-radius: var(--radius-md, 0.375rem);
|
|
121
|
+
border-bottom-left-radius: 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
:global(.toggle-group--vertical) .toggle-group-item:last-child {
|
|
125
|
+
border-top-right-radius: 0;
|
|
126
|
+
border-bottom-left-radius: var(--radius-md, 0.375rem);
|
|
127
|
+
border-bottom-right-radius: var(--radius-md, 0.375rem);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.toggle-group-item:hover:not(:disabled) {
|
|
131
|
+
background: var(--color-bg-muted, #f3f4f6);
|
|
132
|
+
z-index: 1;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.toggle-group-item:focus-visible {
|
|
136
|
+
outline: 2px solid var(--color-primary, #3b82f6);
|
|
137
|
+
outline-offset: 2px;
|
|
138
|
+
z-index: 2;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.toggle-group-item--pressed {
|
|
142
|
+
background: var(--color-primary, #3b82f6);
|
|
143
|
+
color: var(--color-text-inverse, #ffffff);
|
|
144
|
+
border-color: var(--color-primary, #3b82f6);
|
|
145
|
+
z-index: 1;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.toggle-group-item--pressed:hover:not(:disabled) {
|
|
149
|
+
background: var(--color-primary-hover, #2563eb);
|
|
150
|
+
border-color: var(--color-primary-hover, #2563eb);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.toggle-group-item:disabled {
|
|
154
|
+
opacity: 0.5;
|
|
155
|
+
cursor: not-allowed;
|
|
156
|
+
pointer-events: none;
|
|
157
|
+
}
|
|
158
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
value: string;
|
|
3
|
+
disabled?: boolean;
|
|
4
|
+
class?: string;
|
|
5
|
+
children?: import('svelte').Snippet;
|
|
6
|
+
}
|
|
7
|
+
declare const ToggleGroupItem: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type ToggleGroupItem = ReturnType<typeof ToggleGroupItem>;
|
|
9
|
+
export default ToggleGroupItem;
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,9 @@ export { SearchInput } from './components/SearchInput/index.js';
|
|
|
16
16
|
export { DatePicker } from './components/DatePicker/index.js';
|
|
17
17
|
export { FileUpload } from './components/FileUpload/index.js';
|
|
18
18
|
export { OTPInput } from './components/OTPInput/index.js';
|
|
19
|
+
export { TimePicker } from './components/TimePicker/index.js';
|
|
20
|
+
export { Rating } from './components/Rating/index.js';
|
|
21
|
+
export { ToggleGroup, ToggleGroupItem } from './components/ToggleGroup/index.js';
|
|
19
22
|
export { default as Tabs } from './components/Tabs/Tabs.svelte';
|
|
20
23
|
export { default as TabList } from './components/Tabs/TabList.svelte';
|
|
21
24
|
export { default as TabTrigger } from './components/Tabs/TabTrigger.svelte';
|
|
@@ -71,4 +74,4 @@ export { ActionSheet, ActionSheetItem } from './components/ActionSheet/index.js'
|
|
|
71
74
|
export { PullToRefresh } from './components/PullToRefresh/index.js';
|
|
72
75
|
export { SwipeActions, SwipeAction } from './components/SwipeActions/index.js';
|
|
73
76
|
export { FAB, FABGroup } from './components/FAB/index.js';
|
|
74
|
-
export type { ButtonProps, ButtonVariant, ButtonSize, TypographyProps, TypographyVariant, IconProps, IconSize, BadgeProps, BadgeVariant, LinkProps, InputSize, TextInputType, FormFieldProps, TextInputProps, TextareaProps, SelectOption, SelectProps, CheckboxProps, RadioProps, RadioGroupProps, SwitchProps, SearchInputProps, DatePickerProps, FileUploadProps, OTPInputProps, TimeFormat, TimePickerProps, RatingProps, TabsOrientation, TabsProps, TabListProps, TabTriggerProps, TabContentProps, MenuProps, MenuTriggerProps, MenuContentProps, MenuItemProps, MenuSeparatorProps, MenuSubProps, MenuSubTriggerProps, MenuSubContentProps, BreadcrumbItemData, BreadcrumbsProps, BreadcrumbItemProps, BottomNavItemData, BottomNavProps, BottomNavItemProps, NavItemProps, NavbarProps, SidebarItemData, SidebarProps, SidebarItemProps, SidebarGroupProps, StepperOrientation, StepperProps, StepperStepProps, AlertVariant, AlertProps, ToastVariant, ToastPosition, ToastData, ToastProps, ToastContainerProps, ModalSize, ModalProps, ModalHeaderProps, ModalBodyProps, ModalFooterProps, ProgressSize, ProgressBarProps, ProgressCircleProps, SpinnerProps, TooltipSide, TooltipProps, PopoverSide, PopoverProps, PopoverTriggerProps, PopoverContentProps, SkeletonVariant, SkeletonProps, CardVariant, CardProps, CardHeaderProps, CardBodyProps, CardFooterProps, AvatarSize, AvatarShape, AvatarProps, AvatarGroupProps, TagVariant, TagSize, TagProps, ChipVariant, ChipColor, ChipSize, ChipProps, ChipGroupProps, EmptyStateSize, EmptyStateProps, ListProps, ListItemProps, TableProps, TableHeadProps, TableHeaderAlign, TableHeaderSortDirection, TableHeaderProps, TableBodyProps, TableRowProps, TableCellAlign, TableCellProps, AccordionProps, AccordionItemProps, SliderProps, CarouselProps, CarouselSlideProps, ImageObjectFit, ImageRounded, ImageProps, ContainerSize, ContainerProps, GridAlign, GridJustify, SpacingToken, GridProps, GridItemProps, StackDirection, StackProps, DividerOrientation, DividerProps, SpacerSize, SpacerProps, AspectRatioPreset, AspectRatioProps, CenterProps, BoxProps, SafeAreaEdge, SafeAreaProps, BottomSheetSnapPoint, BottomSheetProps, BottomSheetHeaderProps, BottomSheetBodyProps, ActionSheetAction, ActionSheetProps, ActionSheetItemProps, PullToRefreshProps, SwipeActionSide, SwipeActionData, SwipeActionsProps, SwipeActionProps, FABSize, FABPosition, FABProps, FABAction, FABGroupProps } from './types/index.js';
|
|
77
|
+
export type { ButtonProps, ButtonVariant, ButtonSize, TypographyProps, TypographyVariant, IconProps, IconSize, BadgeProps, BadgeVariant, LinkProps, InputSize, TextInputType, FormFieldProps, TextInputProps, TextareaProps, SelectOption, SelectProps, CheckboxProps, RadioProps, RadioGroupProps, SwitchProps, SearchInputProps, DatePickerProps, FileUploadProps, OTPInputProps, TimeFormat, TimePickerProps, RatingProps, ToggleGroupType, ToggleGroupOrientation, ToggleGroupProps, ToggleGroupItemProps, TabsOrientation, TabsProps, TabListProps, TabTriggerProps, TabContentProps, MenuProps, MenuTriggerProps, MenuContentProps, MenuItemProps, MenuSeparatorProps, MenuSubProps, MenuSubTriggerProps, MenuSubContentProps, BreadcrumbItemData, BreadcrumbsProps, BreadcrumbItemProps, BottomNavItemData, BottomNavProps, BottomNavItemProps, NavItemProps, NavbarProps, SidebarItemData, SidebarProps, SidebarItemProps, SidebarGroupProps, StepperOrientation, StepperProps, StepperStepProps, AlertVariant, AlertProps, ToastVariant, ToastPosition, ToastData, ToastProps, ToastContainerProps, ModalSize, ModalProps, ModalHeaderProps, ModalBodyProps, ModalFooterProps, ProgressSize, ProgressBarProps, ProgressCircleProps, SpinnerProps, TooltipSide, TooltipProps, PopoverSide, PopoverProps, PopoverTriggerProps, PopoverContentProps, SkeletonVariant, SkeletonProps, CardVariant, CardProps, CardHeaderProps, CardBodyProps, CardFooterProps, AvatarSize, AvatarShape, AvatarProps, AvatarGroupProps, TagVariant, TagSize, TagProps, ChipVariant, ChipColor, ChipSize, ChipProps, ChipGroupProps, EmptyStateSize, EmptyStateProps, ListProps, ListItemProps, TableProps, TableHeadProps, TableHeaderAlign, TableHeaderSortDirection, TableHeaderProps, TableBodyProps, TableRowProps, TableCellAlign, TableCellProps, AccordionProps, AccordionItemProps, SliderProps, CarouselProps, CarouselSlideProps, ImageObjectFit, ImageRounded, ImageProps, ContainerSize, ContainerProps, GridAlign, GridJustify, SpacingToken, GridProps, GridItemProps, StackDirection, StackProps, DividerOrientation, DividerProps, SpacerSize, SpacerProps, AspectRatioPreset, AspectRatioProps, CenterProps, BoxProps, SafeAreaEdge, SafeAreaProps, BottomSheetSnapPoint, BottomSheetProps, BottomSheetHeaderProps, BottomSheetBodyProps, ActionSheetAction, ActionSheetProps, ActionSheetItemProps, PullToRefreshProps, SwipeActionSide, SwipeActionData, SwipeActionsProps, SwipeActionProps, FABSize, FABPosition, FABProps, FABAction, FABGroupProps } from './types/index.js';
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,9 @@ export { SearchInput } from './components/SearchInput/index.js';
|
|
|
20
20
|
export { DatePicker } from './components/DatePicker/index.js';
|
|
21
21
|
export { FileUpload } from './components/FileUpload/index.js';
|
|
22
22
|
export { OTPInput } from './components/OTPInput/index.js';
|
|
23
|
+
export { TimePicker } from './components/TimePicker/index.js';
|
|
24
|
+
export { Rating } from './components/Rating/index.js';
|
|
25
|
+
export { ToggleGroup, ToggleGroupItem } from './components/ToggleGroup/index.js';
|
|
23
26
|
// Navigation Components
|
|
24
27
|
export { default as Tabs } from './components/Tabs/Tabs.svelte';
|
|
25
28
|
export { default as TabList } from './components/Tabs/TabList.svelte';
|
package/dist/types/index.d.ts
CHANGED
|
@@ -131,4 +131,4 @@ export type { AlertVariant, AlertProps, ToastVariant, ToastPosition, ToastData,
|
|
|
131
131
|
export type { CardVariant, CardProps, CardHeaderProps, CardBodyProps, CardFooterProps, AvatarSize, AvatarShape, AvatarProps, AvatarGroupProps, TagVariant, TagSize, TagProps, ChipVariant, ChipColor, ChipSize, ChipProps, ChipGroupProps, EmptyStateSize, EmptyStateProps, ListProps, ListItemProps, TableProps, TableHeadProps, TableHeaderAlign, TableHeaderSortDirection, TableHeaderProps, TableBodyProps, TableRowProps, TableCellAlign, TableCellProps, AccordionProps, AccordionItemProps, SliderProps, CarouselProps, CarouselSlideProps, ImageObjectFit, ImageRounded, ImageProps } from './data-display.js';
|
|
132
132
|
export type { ContainerSize, ContainerProps, GridAlign, GridJustify, SpacingToken, GridProps, GridItemProps, StackDirection, StackProps, DividerOrientation, DividerProps, SpacerSize, SpacerProps, AspectRatioPreset, AspectRatioProps, CenterProps, BoxProps } from './layout.js';
|
|
133
133
|
export type { SafeAreaEdge, SafeAreaProps, BottomSheetSnapPoint, BottomSheetProps, BottomSheetHeaderProps, BottomSheetBodyProps, ActionSheetAction, ActionSheetProps, ActionSheetItemProps, PullToRefreshProps, SwipeActionSide, SwipeActionData, SwipeActionsProps, SwipeActionProps, FABSize, FABPosition, FABProps, FABAction, FABGroupProps } from './mobile.js';
|
|
134
|
-
export type { SearchInputProps, DatePickerProps, FileUploadProps, OTPInputProps, TimeFormat, TimePickerProps, RatingProps } from './input.js';
|
|
134
|
+
export type { SearchInputProps, DatePickerProps, FileUploadProps, OTPInputProps, TimeFormat, TimePickerProps, RatingProps, ToggleGroupType, ToggleGroupOrientation, ToggleGroupProps, ToggleGroupItemProps } from './input.js';
|
package/dist/types/input.d.ts
CHANGED
|
@@ -65,3 +65,18 @@ export interface RatingProps {
|
|
|
65
65
|
showValue?: boolean;
|
|
66
66
|
class?: string;
|
|
67
67
|
}
|
|
68
|
+
export type ToggleGroupType = 'single' | 'multiple';
|
|
69
|
+
export type ToggleGroupOrientation = 'horizontal' | 'vertical';
|
|
70
|
+
export interface ToggleGroupProps {
|
|
71
|
+
type?: ToggleGroupType;
|
|
72
|
+
value?: string | string[];
|
|
73
|
+
onValueChange?: (value: string | string[]) => void;
|
|
74
|
+
disabled?: boolean;
|
|
75
|
+
orientation?: ToggleGroupOrientation;
|
|
76
|
+
class?: string;
|
|
77
|
+
}
|
|
78
|
+
export interface ToggleGroupItemProps {
|
|
79
|
+
value: string;
|
|
80
|
+
disabled?: boolean;
|
|
81
|
+
class?: string;
|
|
82
|
+
}
|