@makolabs/ripple 3.0.0 → 3.0.2
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/dist/elements/accordion/Accordion.svelte +1 -1
- package/dist/elements/combobox/ComboBox.svelte +59 -29
- package/dist/elements/dropdown/Select.svelte +98 -62
- package/dist/elements/dropdown/select.d.ts +3 -108
- package/dist/elements/dropdown/select.js +37 -46
- package/dist/elements/popover/Popover.svelte +59 -36
- package/dist/filters/CompactFilters.svelte +1 -1
- package/dist/forms/Checkbox.svelte +24 -9
- package/dist/forms/DateRange.svelte +236 -204
- package/dist/forms/Input.svelte +18 -18
- package/dist/forms/MarketSelector.svelte +1 -1
- package/dist/forms/NumberInput.svelte +160 -55
- package/dist/forms/RadioGroup.svelte +6 -2
- package/dist/forms/SegmentedControl.svelte +1 -1
- package/dist/forms/Tags.svelte +32 -11
- package/dist/forms/Textarea.svelte +8 -13
- package/dist/forms/Toggle.svelte +22 -14
- package/dist/forms/calendar/Calendar.svelte +107 -8
- package/dist/forms/calendar/calendar-types.d.ts +8 -0
- package/dist/forms/date-picker/DatePicker.svelte +11 -14
- package/dist/forms/form-size.d.ts +37 -0
- package/dist/forms/form-size.js +67 -0
- package/dist/forms/form-types.d.ts +33 -0
- package/dist/forms/month-picker/MonthPicker.svelte +299 -0
- package/dist/forms/month-picker/MonthPicker.svelte.d.ts +4 -0
- package/dist/forms/month-picker/month-picker-types.d.ts +22 -0
- package/dist/forms/month-picker/month-picker-types.js +1 -0
- package/dist/forms/segmented-control.d.ts +2 -2
- package/dist/forms/segmented-control.js +18 -15
- package/dist/forms/slider.js +35 -28
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/layout/activity-list/ActivityList.svelte +1 -1
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { cn } from '../helper/cls.js';
|
|
3
3
|
import { buildTestId } from '../helper/testid.js';
|
|
4
4
|
import { Size } from '../variants.js';
|
|
5
|
+
import { formSizeTokens } from './form-size.js';
|
|
5
6
|
import type { NumberInputProps } from '../index.js';
|
|
6
7
|
|
|
7
8
|
let {
|
|
@@ -12,32 +13,63 @@
|
|
|
12
13
|
placeholder = 'Enter a number',
|
|
13
14
|
size = Size.MD,
|
|
14
15
|
class: className = '',
|
|
16
|
+
icon: LeadingIcon,
|
|
17
|
+
iconPreset,
|
|
15
18
|
units = [],
|
|
16
19
|
errors,
|
|
17
20
|
disabled = false,
|
|
18
21
|
dropdownIcon: DropdownIcon,
|
|
19
22
|
onunitchange: onUnitChange,
|
|
23
|
+
formatThousands = true,
|
|
24
|
+
locale = 'en-US',
|
|
20
25
|
testId,
|
|
21
26
|
...restProps
|
|
22
27
|
}: NumberInputProps = $props();
|
|
23
28
|
|
|
29
|
+
const showPresetIcon = $derived(!LeadingIcon && iconPreset);
|
|
30
|
+
|
|
24
31
|
let showUnitDropdown = $state(false);
|
|
25
32
|
let containerRef = $state<HTMLDivElement | null>(null);
|
|
33
|
+
let inputFocused = $state(false);
|
|
26
34
|
|
|
27
35
|
const selectedOption = $derived(units.find((u) => u.value === unit));
|
|
28
36
|
|
|
37
|
+
// Only render the dropdown trigger when there's something to switch
|
|
38
|
+
// between. A single-unit field looks like a static currency label
|
|
39
|
+
// rather than an editable selector — no chevron, no click target.
|
|
40
|
+
const hasMultipleUnits = $derived(units.length > 1);
|
|
41
|
+
|
|
42
|
+
// While focused, show the raw number so typing stays predictable
|
|
43
|
+
// (typed commas would otherwise fight the formatter). On blur we
|
|
44
|
+
// reformat with locale thousands separators for readability.
|
|
45
|
+
const displayValue = $derived(
|
|
46
|
+
value == null || Number.isNaN(value)
|
|
47
|
+
? ''
|
|
48
|
+
: inputFocused || !formatThousands
|
|
49
|
+
? String(value)
|
|
50
|
+
: value.toLocaleString(locale)
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
function handleInput(e: Event) {
|
|
54
|
+
const raw = (e.currentTarget as HTMLInputElement).value.replace(/[^0-9.-]/g, '');
|
|
55
|
+
if (raw === '' || raw === '-') {
|
|
56
|
+
value = 0;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const num = Number(raw);
|
|
60
|
+
if (!Number.isNaN(num)) value = num;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const tokens = $derived(formSizeTokens[size]);
|
|
64
|
+
|
|
29
65
|
const containerClass = $derived(
|
|
30
66
|
cn(
|
|
31
|
-
'relative flex items-center gap-1
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
'h-8': size === Size.SM,
|
|
38
|
-
'h-10': size === Size.MD,
|
|
39
|
-
'h-12': size === Size.LG
|
|
40
|
-
},
|
|
67
|
+
'relative flex items-center gap-1 border bg-white',
|
|
68
|
+
tokens.radius,
|
|
69
|
+
tokens.shadow,
|
|
70
|
+
tokens.height,
|
|
71
|
+
errors?.length && 'border-danger-300',
|
|
72
|
+
disabled && 'cursor-not-allowed opacity-50',
|
|
41
73
|
'border-default-300 focus-within:border-primary-500 focus-within:ring-2 focus-within:ring-primary-500 focus-within:ring-offset-2',
|
|
42
74
|
className
|
|
43
75
|
)
|
|
@@ -45,12 +77,9 @@
|
|
|
45
77
|
|
|
46
78
|
const inputClass = $derived(
|
|
47
79
|
cn(
|
|
48
|
-
'w-full bg-transparent outline-none disabled:cursor-not-allowed
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
'text-base': size === Size.MD,
|
|
52
|
-
'text-lg': size === Size.LG
|
|
53
|
-
}
|
|
80
|
+
'w-full bg-transparent outline-none disabled:cursor-not-allowed placeholder:text-default-400',
|
|
81
|
+
tokens.padX,
|
|
82
|
+
tokens.text
|
|
54
83
|
)
|
|
55
84
|
);
|
|
56
85
|
|
|
@@ -91,27 +120,27 @@
|
|
|
91
120
|
>
|
|
92
121
|
{/if}
|
|
93
122
|
<div class={containerClass} bind:this={containerRef}>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
stroke-width="2"
|
|
107
|
-
d="M17 9V7a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2m2 4h10a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2H9a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2m7-5a2 2 0 1 1-4 0a2 2 0 0 1 4 0"
|
|
108
|
-
/>
|
|
109
|
-
</svg>
|
|
123
|
+
{#if LeadingIcon}
|
|
124
|
+
<span class={cn('text-default-500 flex shrink-0 items-center', tokens.padX)}>
|
|
125
|
+
<LeadingIcon class={tokens.iconSize} />
|
|
126
|
+
</span>
|
|
127
|
+
{:else if showPresetIcon}
|
|
128
|
+
<span
|
|
129
|
+
class={cn('text-default-500 flex shrink-0 items-center', tokens.padX)}
|
|
130
|
+
aria-hidden="true"
|
|
131
|
+
>
|
|
132
|
+
{@render presetIconSvg(iconPreset)}
|
|
133
|
+
</span>
|
|
134
|
+
{/if}
|
|
110
135
|
<input
|
|
111
136
|
{name}
|
|
112
137
|
id={name}
|
|
113
|
-
|
|
114
|
-
|
|
138
|
+
type="text"
|
|
139
|
+
inputmode="decimal"
|
|
140
|
+
value={displayValue}
|
|
141
|
+
oninput={handleInput}
|
|
142
|
+
onfocus={() => (inputFocused = true)}
|
|
143
|
+
onblur={() => (inputFocused = false)}
|
|
115
144
|
{placeholder}
|
|
116
145
|
{disabled}
|
|
117
146
|
class={inputClass}
|
|
@@ -119,27 +148,41 @@
|
|
|
119
148
|
{...restProps}
|
|
120
149
|
/>
|
|
121
150
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
{
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
151
|
+
{#if hasMultipleUnits}
|
|
152
|
+
<!-- Clickable selector: renders only when there's more than one
|
|
153
|
+
unit to pick from. Otherwise the unit is static text below. -->
|
|
154
|
+
<button
|
|
155
|
+
type="button"
|
|
156
|
+
class="hover:bg-default-100 flex items-center gap-1 rounded px-1"
|
|
157
|
+
onclick={handleUnitToggle}
|
|
158
|
+
{disabled}
|
|
159
|
+
>
|
|
160
|
+
{#if selectedOption?.icon}
|
|
161
|
+
{@const Icon = selectedOption.icon}
|
|
162
|
+
<Icon />
|
|
163
|
+
{/if}
|
|
164
|
+
<span class={cn(tokens.text)}>{unit}</span>
|
|
165
|
+
{#if DropdownIcon}
|
|
166
|
+
<DropdownIcon class={iconClass} />
|
|
167
|
+
{:else}
|
|
168
|
+
<svg class={iconClass} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
169
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
|
|
170
|
+
</svg>
|
|
171
|
+
{/if}
|
|
172
|
+
</button>
|
|
173
|
+
{:else if unit}
|
|
174
|
+
<!-- Static unit label — no chevron, not clickable. Matches the
|
|
175
|
+
field's text size so it sits inline. -->
|
|
176
|
+
<span class={cn('text-default-500 flex items-center gap-1 pr-2', tokens.text)}>
|
|
177
|
+
{#if selectedOption?.icon}
|
|
178
|
+
{@const Icon = selectedOption.icon}
|
|
179
|
+
<Icon />
|
|
180
|
+
{/if}
|
|
181
|
+
{unit}
|
|
182
|
+
</span>
|
|
183
|
+
{/if}
|
|
184
|
+
|
|
185
|
+
{#if showUnitDropdown && hasMultipleUnits}
|
|
143
186
|
<div class={dropdownClass}>
|
|
144
187
|
{#each units as unitOption (unitOption.value)}
|
|
145
188
|
<button
|
|
@@ -167,3 +210,65 @@
|
|
|
167
210
|
{/each}
|
|
168
211
|
{/if}
|
|
169
212
|
</div>
|
|
213
|
+
|
|
214
|
+
{#snippet presetIconSvg(preset: string | undefined)}
|
|
215
|
+
{#if preset === 'currency'}
|
|
216
|
+
<svg
|
|
217
|
+
class={tokens.iconSize}
|
|
218
|
+
viewBox="0 0 24 24"
|
|
219
|
+
fill="none"
|
|
220
|
+
stroke="currentColor"
|
|
221
|
+
stroke-width="2"
|
|
222
|
+
stroke-linecap="round"
|
|
223
|
+
stroke-linejoin="round"
|
|
224
|
+
>
|
|
225
|
+
<path
|
|
226
|
+
d="M17 9V7a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2m2 4h10a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2H9a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2m7-5a2 2 0 1 1-4 0a2 2 0 0 1 4 0"
|
|
227
|
+
/>
|
|
228
|
+
</svg>
|
|
229
|
+
{:else if preset === 'quantity'}
|
|
230
|
+
<span class={cn(tokens.iconSize, 'flex items-center justify-center font-bold')}>#</span>
|
|
231
|
+
{:else if preset === 'percentage'}
|
|
232
|
+
<svg
|
|
233
|
+
class={tokens.iconSize}
|
|
234
|
+
viewBox="0 0 24 24"
|
|
235
|
+
fill="none"
|
|
236
|
+
stroke="currentColor"
|
|
237
|
+
stroke-width="2"
|
|
238
|
+
stroke-linecap="round"
|
|
239
|
+
stroke-linejoin="round"
|
|
240
|
+
>
|
|
241
|
+
<path d="M19 5 5 19" /><circle cx="6.5" cy="6.5" r="2.5" /><circle
|
|
242
|
+
cx="17.5"
|
|
243
|
+
cy="17.5"
|
|
244
|
+
r="2.5"
|
|
245
|
+
/>
|
|
246
|
+
</svg>
|
|
247
|
+
{:else if preset === 'weight'}
|
|
248
|
+
<svg
|
|
249
|
+
class={tokens.iconSize}
|
|
250
|
+
viewBox="0 0 24 24"
|
|
251
|
+
fill="none"
|
|
252
|
+
stroke="currentColor"
|
|
253
|
+
stroke-width="2"
|
|
254
|
+
stroke-linecap="round"
|
|
255
|
+
stroke-linejoin="round"
|
|
256
|
+
>
|
|
257
|
+
<path
|
|
258
|
+
d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"
|
|
259
|
+
/><path d="M3.27 6.96 12 12.01l8.73-5.05M12 22.08V12" />
|
|
260
|
+
</svg>
|
|
261
|
+
{:else if preset === 'temperature'}
|
|
262
|
+
<svg
|
|
263
|
+
class={tokens.iconSize}
|
|
264
|
+
viewBox="0 0 24 24"
|
|
265
|
+
fill="none"
|
|
266
|
+
stroke="currentColor"
|
|
267
|
+
stroke-width="2"
|
|
268
|
+
stroke-linecap="round"
|
|
269
|
+
stroke-linejoin="round"
|
|
270
|
+
>
|
|
271
|
+
<path d="M14 14.76V3.5a2.5 2.5 0 0 0-5 0v11.26a4.5 4.5 0 1 0 5 0z" />
|
|
272
|
+
</svg>
|
|
273
|
+
{/if}
|
|
274
|
+
{/snippet}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { cn } from '../helper/cls.js';
|
|
3
3
|
import { buildTestId } from '../helper/testid.js';
|
|
4
4
|
import { Color, Size } from '../variants.js';
|
|
5
|
+
import { formSizeTokens } from './form-size.js';
|
|
5
6
|
import type { RadioGroupProps } from '../index.js';
|
|
6
7
|
|
|
7
8
|
let {
|
|
@@ -21,7 +22,10 @@
|
|
|
21
22
|
}: RadioGroupProps = $props();
|
|
22
23
|
|
|
23
24
|
const hasErrors = $derived(errors.length > 0);
|
|
25
|
+
const tokens = $derived(formSizeTokens[size]);
|
|
24
26
|
|
|
27
|
+
// Radio circle scales with the form ladder — `size-3` at xs through
|
|
28
|
+
// `size-6` at xl. 2xl aliases xl.
|
|
25
29
|
const dotSize = $derived(
|
|
26
30
|
{
|
|
27
31
|
[Size.XS]: 'size-3',
|
|
@@ -29,7 +33,7 @@
|
|
|
29
33
|
[Size.MD]: 'size-4',
|
|
30
34
|
[Size.LG]: 'size-5',
|
|
31
35
|
[Size.XL]: 'size-6',
|
|
32
|
-
[Size.XXL]: 'size-
|
|
36
|
+
[Size.XXL]: 'size-6'
|
|
33
37
|
}[size]
|
|
34
38
|
);
|
|
35
39
|
|
|
@@ -104,7 +108,7 @@
|
|
|
104
108
|
{/if}
|
|
105
109
|
</span>
|
|
106
110
|
<span class="flex flex-col">
|
|
107
|
-
<span class=
|
|
111
|
+
<span class={cn('text-default-800', tokens.text)}>{option.label}</span>
|
|
108
112
|
{#if option.description}
|
|
109
113
|
<span class="text-default-500 text-xs">{option.description}</span>
|
|
110
114
|
{/if}
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
const rootClass = $derived(
|
|
43
43
|
cn(
|
|
44
|
-
orientation === 'auto' ? '@container w-full' : 'w-fit',
|
|
44
|
+
orientation === 'auto' ? '@container w-full' : 'w-full sm:w-fit',
|
|
45
45
|
labelLayout === 'inline' ? 'flex flex-row items-center gap-2' : 'flex flex-col gap-2',
|
|
46
46
|
orientation === 'auto' &&
|
|
47
47
|
labelLayout === 'inline' &&
|
package/dist/forms/Tags.svelte
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import Badge from '../elements/badge/Badge.svelte';
|
|
3
3
|
import { Size } from '../variants.js';
|
|
4
4
|
import { cn } from '../helper/cls.js';
|
|
5
|
+
import { formSizeTokens } from './form-size.js';
|
|
5
6
|
import { fade } from 'svelte/transition';
|
|
6
7
|
import { flip } from 'svelte/animate';
|
|
7
8
|
import { quintOut } from 'svelte/easing';
|
|
@@ -34,6 +35,25 @@
|
|
|
34
35
|
showSuggestions ? suggestions.filter(isUnselected).filter(matchesInput).slice(0, 5) : []
|
|
35
36
|
);
|
|
36
37
|
|
|
38
|
+
const tokens = $derived(formSizeTokens[size]);
|
|
39
|
+
|
|
40
|
+
// Chip size shifted one tier down from the Tags container so chip
|
|
41
|
+
// text matches the container text (e.g. Tags `md` uses `text-xs`, so
|
|
42
|
+
// Badge `sm` — which is also `text-xs` — lines up, instead of Badge
|
|
43
|
+
// `md` which jumps to `text-sm`).
|
|
44
|
+
const chipSize = $derived(
|
|
45
|
+
(
|
|
46
|
+
{
|
|
47
|
+
[Size.XS]: Size.XS,
|
|
48
|
+
[Size.SM]: Size.XS,
|
|
49
|
+
[Size.MD]: Size.SM,
|
|
50
|
+
[Size.LG]: Size.MD,
|
|
51
|
+
[Size.XL]: Size.LG,
|
|
52
|
+
[Size.XXL]: Size.LG
|
|
53
|
+
} as const
|
|
54
|
+
)[size]
|
|
55
|
+
);
|
|
56
|
+
|
|
37
57
|
function handleKeydown(event: KeyboardEvent) {
|
|
38
58
|
if (event.key === 'Enter') {
|
|
39
59
|
event.preventDefault();
|
|
@@ -108,12 +128,14 @@
|
|
|
108
128
|
|
|
109
129
|
const containerClass = $derived(
|
|
110
130
|
cn(
|
|
111
|
-
'relative flex flex-wrap gap-2
|
|
131
|
+
'relative flex flex-wrap gap-2 border bg-white',
|
|
132
|
+
tokens.radius,
|
|
133
|
+
tokens.shadow,
|
|
134
|
+
tokens.padX,
|
|
135
|
+
tokens.padY,
|
|
112
136
|
'border-default-300 focus-within:border-primary-500 focus-within:ring-2 focus-within:ring-primary-500 focus-within:ring-offset-2',
|
|
113
|
-
|
|
114
|
-
'border-danger-300 focus-within:border-danger-500 focus-within:ring-danger-500'
|
|
115
|
-
errors?.length
|
|
116
|
-
},
|
|
137
|
+
errors?.length &&
|
|
138
|
+
'border-danger-300 focus-within:border-danger-500 focus-within:ring-danger-500',
|
|
117
139
|
className
|
|
118
140
|
)
|
|
119
141
|
);
|
|
@@ -143,7 +165,7 @@
|
|
|
143
165
|
transition:fade={{ duration: 250, easing: quintOut }}
|
|
144
166
|
animate:flip={{ duration: 300, easing: quintOut }}
|
|
145
167
|
>
|
|
146
|
-
<Badge {
|
|
168
|
+
<Badge size={chipSize} color="info" onclose={() => handleTagRemoval(tag)} class="shadow-xs">
|
|
147
169
|
{tag}
|
|
148
170
|
</Badge>
|
|
149
171
|
</div>
|
|
@@ -154,11 +176,10 @@
|
|
|
154
176
|
{name}
|
|
155
177
|
id={name}
|
|
156
178
|
{placeholder}
|
|
157
|
-
class={cn(
|
|
158
|
-
'text-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
})}
|
|
179
|
+
class={cn(
|
|
180
|
+
'placeholder:text-default-400 min-w-[120px] flex-1 bg-transparent outline-none',
|
|
181
|
+
tokens.text
|
|
182
|
+
)}
|
|
162
183
|
type="text"
|
|
163
184
|
autocomplete="off"
|
|
164
185
|
onkeydown={handleKeydown}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { cn } from '../helper/cls.js';
|
|
3
3
|
import { buildTestId } from '../helper/testid.js';
|
|
4
4
|
import { Size } from '../variants.js';
|
|
5
|
+
import { formSizeTokens } from './form-size.js';
|
|
5
6
|
import type { TextareaProps } from '../index.js';
|
|
6
7
|
|
|
7
8
|
let {
|
|
@@ -27,25 +28,19 @@
|
|
|
27
28
|
|
|
28
29
|
let el = $state<HTMLTextAreaElement | undefined>();
|
|
29
30
|
|
|
30
|
-
const
|
|
31
|
-
{
|
|
32
|
-
[Size.XS]: 'text-xs',
|
|
33
|
-
[Size.SM]: 'text-sm',
|
|
34
|
-
[Size.MD]: 'text-sm',
|
|
35
|
-
[Size.LG]: 'text-base',
|
|
36
|
-
[Size.XL]: 'text-lg',
|
|
37
|
-
[Size.XXL]: 'text-lg'
|
|
38
|
-
}[size]
|
|
39
|
-
);
|
|
40
|
-
|
|
31
|
+
const tokens = $derived(formSizeTokens[size]);
|
|
41
32
|
const hasErrors = $derived(errors.length > 0);
|
|
42
33
|
|
|
43
34
|
const textareaClasses = $derived(
|
|
44
35
|
cn(
|
|
45
|
-
'w-full
|
|
36
|
+
'w-full border bg-white transition-colors',
|
|
46
37
|
'placeholder:text-default-400',
|
|
47
38
|
'focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2',
|
|
48
|
-
|
|
39
|
+
tokens.radius,
|
|
40
|
+
tokens.shadow,
|
|
41
|
+
tokens.padX,
|
|
42
|
+
tokens.padY,
|
|
43
|
+
tokens.text,
|
|
49
44
|
hasErrors
|
|
50
45
|
? 'border-danger-300 focus-within:border-danger-500 focus-within:ring-danger-500'
|
|
51
46
|
: 'border-default-300 focus-within:border-primary-500 focus-within:ring-primary-500',
|
package/dist/forms/Toggle.svelte
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { cn } from '../helper/cls.js';
|
|
3
3
|
import { Color, Size } from '../variants.js';
|
|
4
4
|
import { buildTestId } from '../helper/testid.js';
|
|
5
|
+
import { formSizeTokens } from './form-size.js';
|
|
5
6
|
import type { ToggleProps, VariantColors, VariantSizes } from '../index.js';
|
|
6
7
|
|
|
7
8
|
let {
|
|
@@ -35,15 +36,21 @@
|
|
|
35
36
|
)[color]
|
|
36
37
|
);
|
|
37
38
|
|
|
39
|
+
const tokens = $derived(formSizeTokens[size]);
|
|
40
|
+
|
|
41
|
+
// Track + thumb + on-state offset ladder. Chosen to roughly match the
|
|
42
|
+
// height of an Input at the same `size` (20/24/28/36/44px) so a row
|
|
43
|
+
// with a Toggle and an Input reads evenly. 2xl aliases xl — form
|
|
44
|
+
// controls cap at xl.
|
|
38
45
|
const toggleSize = $derived(
|
|
39
46
|
(
|
|
40
47
|
{
|
|
41
|
-
[Size.XS]: 'w-
|
|
48
|
+
[Size.XS]: 'w-7 h-3.5',
|
|
42
49
|
[Size.SM]: 'w-8 h-4',
|
|
43
50
|
[Size.MD]: 'w-10 h-5',
|
|
44
51
|
[Size.LG]: 'w-12 h-6',
|
|
45
|
-
[Size.XL]: 'w-
|
|
46
|
-
[Size.XXL]: 'w-
|
|
52
|
+
[Size.XL]: 'w-14 h-7',
|
|
53
|
+
[Size.XXL]: 'w-14 h-7'
|
|
47
54
|
} satisfies Record<VariantSizes, string>
|
|
48
55
|
)[size]
|
|
49
56
|
);
|
|
@@ -51,12 +58,12 @@
|
|
|
51
58
|
const thumbSize = $derived(
|
|
52
59
|
(
|
|
53
60
|
{
|
|
54
|
-
[Size.XS]: 'h-
|
|
61
|
+
[Size.XS]: 'h-2.5 w-2.5',
|
|
55
62
|
[Size.SM]: 'h-3 w-3',
|
|
56
63
|
[Size.MD]: 'h-4 w-4',
|
|
57
64
|
[Size.LG]: 'h-5 w-5',
|
|
58
|
-
[Size.XL]: 'h-
|
|
59
|
-
[Size.XXL]: 'h-
|
|
65
|
+
[Size.XL]: 'h-6 w-6',
|
|
66
|
+
[Size.XXL]: 'h-6 w-6'
|
|
60
67
|
} satisfies Record<VariantSizes, string>
|
|
61
68
|
)[size]
|
|
62
69
|
);
|
|
@@ -64,12 +71,12 @@
|
|
|
64
71
|
const thumbPosition = $derived(
|
|
65
72
|
(
|
|
66
73
|
{
|
|
67
|
-
[Size.XS]: value ? 'translate-x-
|
|
74
|
+
[Size.XS]: value ? 'translate-x-3.5' : 'translate-x-0.5',
|
|
68
75
|
[Size.SM]: value ? 'translate-x-4' : 'translate-x-0.5',
|
|
69
76
|
[Size.MD]: value ? 'translate-x-5' : 'translate-x-0.5',
|
|
70
77
|
[Size.LG]: value ? 'translate-x-6' : 'translate-x-0.5',
|
|
71
|
-
[Size.XL]: value ? 'translate-x-
|
|
72
|
-
[Size.XXL]: value ? 'translate-x-
|
|
78
|
+
[Size.XL]: value ? 'translate-x-7' : 'translate-x-0.5',
|
|
79
|
+
[Size.XXL]: value ? 'translate-x-7' : 'translate-x-0.5'
|
|
73
80
|
} satisfies Record<VariantSizes, string>
|
|
74
81
|
)[size]
|
|
75
82
|
);
|
|
@@ -112,11 +119,12 @@
|
|
|
112
119
|
);
|
|
113
120
|
|
|
114
121
|
const labelClasses = $derived(
|
|
115
|
-
cn(
|
|
116
|
-
'
|
|
117
|
-
|
|
118
|
-
'
|
|
119
|
-
|
|
122
|
+
cn(
|
|
123
|
+
'font-medium',
|
|
124
|
+
tokens.text,
|
|
125
|
+
errors.length ? 'text-danger-600' : 'text-default-700',
|
|
126
|
+
disabled && 'opacity-50'
|
|
127
|
+
)
|
|
120
128
|
);
|
|
121
129
|
|
|
122
130
|
function handleKeyDown(event: KeyboardEvent) {
|