@classic-homes/theme-svelte 0.1.21 → 0.1.23
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/lib/components/Combobox.svelte +194 -76
- package/dist/lib/components/Combobox.svelte.d.ts +4 -0
- package/dist/lib/components/DateTimePicker.svelte +280 -191
- package/dist/lib/components/DateTimePicker.svelte.d.ts +9 -1
- package/dist/lib/components/Input.svelte +1 -1
- package/dist/lib/components/MultiSelect.svelte +269 -108
- package/dist/lib/components/MultiSelect.svelte.d.ts +4 -0
- package/dist/lib/components/NumberInput.svelte +198 -78
- package/dist/lib/components/NumberInput.svelte.d.ts +4 -0
- package/dist/lib/components/OTPInput.svelte +125 -35
- package/dist/lib/components/OTPInput.svelte.d.ts +8 -0
- package/dist/lib/components/RadioGroup.svelte +31 -11
- package/dist/lib/components/RadioGroup.svelte.d.ts +2 -0
- package/dist/lib/components/Select.svelte +249 -103
- package/dist/lib/components/Select.svelte.d.ts +2 -0
- package/dist/lib/components/Slider.svelte +32 -2
- package/dist/lib/components/Slider.svelte.d.ts +4 -0
- package/dist/lib/composables/useForm.svelte.d.ts +6 -0
- package/dist/lib/composables/useForm.svelte.js +33 -0
- package/dist/lib/composables/usePersistedForm.svelte.js +10 -0
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +4 -0
- package/dist/lib/utils/date.d.ts +64 -0
- package/dist/lib/utils/date.js +106 -0
- package/dist/lib/utils/form.d.ts +22 -0
- package/dist/lib/utils/form.js +31 -0
- package/package.json +1 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Combobox as ComboboxPrimitive } from 'bits-ui';
|
|
3
3
|
import { cn } from '../utils.js';
|
|
4
|
+
import { generateId, computeDescribedBy } from '../utils/form.js';
|
|
4
5
|
import Spinner from './Spinner.svelte';
|
|
6
|
+
import Label from './Label.svelte';
|
|
5
7
|
|
|
6
8
|
export interface MultiSelectOption {
|
|
7
9
|
value: string;
|
|
@@ -28,6 +30,10 @@
|
|
|
28
30
|
name?: string;
|
|
29
31
|
/** Element ID */
|
|
30
32
|
id?: string;
|
|
33
|
+
/** Label text for the multiselect */
|
|
34
|
+
label?: string;
|
|
35
|
+
/** Hint text displayed below the multiselect */
|
|
36
|
+
hint?: string;
|
|
31
37
|
/** Error message to display */
|
|
32
38
|
error?: string;
|
|
33
39
|
/** Maximum number of selections allowed */
|
|
@@ -52,6 +58,8 @@
|
|
|
52
58
|
required = false,
|
|
53
59
|
name,
|
|
54
60
|
id,
|
|
61
|
+
label,
|
|
62
|
+
hint,
|
|
55
63
|
error,
|
|
56
64
|
max,
|
|
57
65
|
loading = false,
|
|
@@ -60,6 +68,15 @@
|
|
|
60
68
|
class: className,
|
|
61
69
|
}: Props = $props();
|
|
62
70
|
|
|
71
|
+
// Generate unique IDs for accessibility
|
|
72
|
+
const randomSuffix = generateId('multiselect').split('-').pop();
|
|
73
|
+
const componentId = $derived(id || `multiselect-${randomSuffix}`);
|
|
74
|
+
const hintId = $derived(`${componentId}-hint`);
|
|
75
|
+
const errorId = $derived(`${componentId}-error`);
|
|
76
|
+
|
|
77
|
+
// Compute aria-describedby based on hint and error
|
|
78
|
+
const describedBy = $derived(computeDescribedBy(hintId, errorId, !!hint, !!error));
|
|
79
|
+
|
|
63
80
|
let searchQuery = $state('');
|
|
64
81
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
65
82
|
let open = $state(false);
|
|
@@ -118,129 +135,273 @@
|
|
|
118
135
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
119
136
|
};
|
|
120
137
|
});
|
|
138
|
+
|
|
139
|
+
// Determine if we need a container wrapper
|
|
140
|
+
const hasWrapper = $derived(!!label || !!hint || !!error);
|
|
121
141
|
</script>
|
|
122
142
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
{
|
|
129
|
-
|
|
130
|
-
<span
|
|
131
|
-
class="inline-flex items-center gap-1 rounded-md bg-secondary px-2 py-1 text-xs font-medium text-secondary-foreground"
|
|
132
|
-
>
|
|
133
|
-
{label}
|
|
134
|
-
<button
|
|
135
|
-
type="button"
|
|
136
|
-
onclick={() => removeValue(selectedValue)}
|
|
137
|
-
class="rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none"
|
|
138
|
-
{disabled}
|
|
139
|
-
aria-label={`Remove ${label}`}
|
|
140
|
-
>
|
|
141
|
-
<svg
|
|
142
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
143
|
-
width="14"
|
|
144
|
-
height="14"
|
|
145
|
-
viewBox="0 0 24 24"
|
|
146
|
-
fill="none"
|
|
147
|
-
stroke="currentColor"
|
|
148
|
-
stroke-width="2"
|
|
149
|
-
stroke-linecap="round"
|
|
150
|
-
stroke-linejoin="round"
|
|
151
|
-
>
|
|
152
|
-
<path d="M18 6 6 18" />
|
|
153
|
-
<path d="m6 6 12 12" />
|
|
154
|
-
</svg>
|
|
155
|
-
</button>
|
|
156
|
-
</span>
|
|
143
|
+
{#if hasWrapper}
|
|
144
|
+
<div class="space-y-2">
|
|
145
|
+
{#if label}
|
|
146
|
+
<Label for={componentId} {disabled}>
|
|
147
|
+
{label}
|
|
148
|
+
{#if required}
|
|
149
|
+
<span class="text-destructive ml-0.5" aria-hidden="true">*</span>
|
|
150
|
+
<span class="sr-only">(required)</span>
|
|
157
151
|
{/if}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
{/if}
|
|
161
|
-
|
|
162
|
-
<ComboboxPrimitive.Root
|
|
163
|
-
type="multiple"
|
|
164
|
-
{disabled}
|
|
165
|
-
{required}
|
|
166
|
-
{name}
|
|
167
|
-
bind:open
|
|
168
|
-
onOpenChange={handleOpenChange}
|
|
169
|
-
onValueChange={handleValueChange}
|
|
170
|
-
{value}
|
|
171
|
-
>
|
|
172
|
-
<div class="relative">
|
|
173
|
-
<ComboboxPrimitive.Input
|
|
174
|
-
{id}
|
|
175
|
-
placeholder={value.length > 0 ? `${value.length} selected` : placeholder}
|
|
176
|
-
oninput={handleSearchInput}
|
|
177
|
-
onfocus={() => (open = true)}
|
|
178
|
-
onclick={() => (open = true)}
|
|
179
|
-
class={cn(
|
|
180
|
-
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
181
|
-
error && 'border-destructive focus:ring-destructive'
|
|
182
|
-
)}
|
|
183
|
-
aria-invalid={error ? 'true' : undefined}
|
|
184
|
-
/>
|
|
185
|
-
</div>
|
|
152
|
+
</Label>
|
|
153
|
+
{/if}
|
|
186
154
|
|
|
187
|
-
<
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
{@const isSelected = value.includes(option.value)}
|
|
206
|
-
{@const isDisabledByMax = maxReached && !isSelected}
|
|
207
|
-
<ComboboxPrimitive.Item
|
|
208
|
-
value={option.value}
|
|
209
|
-
label={option.label}
|
|
210
|
-
disabled={option.disabled || isDisabledByMax}
|
|
211
|
-
class="relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[disabled]:cursor-default"
|
|
212
|
-
>
|
|
213
|
-
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
214
|
-
{#if isSelected}
|
|
155
|
+
<div class={cn('w-full', className)}>
|
|
156
|
+
<!-- Selected tags -->
|
|
157
|
+
{#if selectedLabels.length > 0}
|
|
158
|
+
<div class="flex flex-wrap gap-1 mb-2">
|
|
159
|
+
{#each value as selectedValue}
|
|
160
|
+
{@const labelText = options.find((o) => o.value === selectedValue)?.label}
|
|
161
|
+
{#if labelText}
|
|
162
|
+
<span
|
|
163
|
+
class="inline-flex items-center gap-1 rounded-md bg-secondary px-2 py-1 text-xs font-medium text-secondary-foreground"
|
|
164
|
+
>
|
|
165
|
+
{labelText}
|
|
166
|
+
<button
|
|
167
|
+
type="button"
|
|
168
|
+
onclick={() => removeValue(selectedValue)}
|
|
169
|
+
class="rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none"
|
|
170
|
+
{disabled}
|
|
171
|
+
aria-label={`Remove ${labelText}`}
|
|
172
|
+
>
|
|
215
173
|
<svg
|
|
216
174
|
xmlns="http://www.w3.org/2000/svg"
|
|
217
|
-
width="
|
|
218
|
-
height="
|
|
175
|
+
width="14"
|
|
176
|
+
height="14"
|
|
219
177
|
viewBox="0 0 24 24"
|
|
220
178
|
fill="none"
|
|
221
179
|
stroke="currentColor"
|
|
222
180
|
stroke-width="2"
|
|
223
181
|
stroke-linecap="round"
|
|
224
182
|
stroke-linejoin="round"
|
|
225
|
-
class="h-4 w-4"
|
|
226
183
|
>
|
|
227
|
-
<
|
|
184
|
+
<path d="M18 6 6 18" />
|
|
185
|
+
<path d="m6 6 12 12" />
|
|
228
186
|
</svg>
|
|
229
|
-
|
|
230
|
-
<span class="h-3.5 w-3.5 rounded-sm border border-primary"></span>
|
|
231
|
-
{/if}
|
|
187
|
+
</button>
|
|
232
188
|
</span>
|
|
233
|
-
|
|
234
|
-
</ComboboxPrimitive.Item>
|
|
189
|
+
{/if}
|
|
235
190
|
{/each}
|
|
236
|
-
|
|
191
|
+
</div>
|
|
192
|
+
{/if}
|
|
193
|
+
|
|
194
|
+
<ComboboxPrimitive.Root
|
|
195
|
+
type="multiple"
|
|
196
|
+
{disabled}
|
|
197
|
+
{required}
|
|
198
|
+
{name}
|
|
199
|
+
bind:open
|
|
200
|
+
onOpenChange={handleOpenChange}
|
|
201
|
+
onValueChange={handleValueChange}
|
|
202
|
+
{value}
|
|
203
|
+
>
|
|
204
|
+
<div class="relative">
|
|
205
|
+
<ComboboxPrimitive.Input
|
|
206
|
+
id={componentId}
|
|
207
|
+
placeholder={value.length > 0 ? `${value.length} selected` : placeholder}
|
|
208
|
+
oninput={handleSearchInput}
|
|
209
|
+
onfocus={() => (open = true)}
|
|
210
|
+
onclick={() => (open = true)}
|
|
211
|
+
class={cn(
|
|
212
|
+
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
213
|
+
error && 'border-destructive focus:ring-destructive'
|
|
214
|
+
)}
|
|
215
|
+
aria-invalid={error ? 'true' : undefined}
|
|
216
|
+
aria-describedby={describedBy}
|
|
217
|
+
/>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<ComboboxPrimitive.Content
|
|
221
|
+
class="isolate relative z-50 min-w-[var(--bits-floating-anchor-width)] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:border-t-0 data-[side=bottom]:rounded-t-none data-[side=top]:border-b-0 data-[side=top]:rounded-b-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
|
|
222
|
+
sideOffset={-1}
|
|
223
|
+
collisionPadding={8}
|
|
224
|
+
avoidCollisions={true}
|
|
225
|
+
>
|
|
226
|
+
<div class="p-1 max-h-[300px] overflow-y-auto">
|
|
227
|
+
{#if loading}
|
|
228
|
+
<div class="flex items-center justify-center py-6">
|
|
229
|
+
<Spinner size="sm" />
|
|
230
|
+
<span class="ml-2 text-sm text-muted-foreground">Loading...</span>
|
|
231
|
+
</div>
|
|
232
|
+
{:else if filteredOptions.length === 0}
|
|
233
|
+
<div class="py-6 text-center text-sm text-muted-foreground">
|
|
234
|
+
{emptyMessage}
|
|
235
|
+
</div>
|
|
236
|
+
{:else}
|
|
237
|
+
{#each filteredOptions as option}
|
|
238
|
+
{@const isSelected = value.includes(option.value)}
|
|
239
|
+
{@const isDisabledByMax = maxReached && !isSelected}
|
|
240
|
+
<ComboboxPrimitive.Item
|
|
241
|
+
value={option.value}
|
|
242
|
+
label={option.label}
|
|
243
|
+
disabled={option.disabled || isDisabledByMax}
|
|
244
|
+
class="relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[disabled]:cursor-default"
|
|
245
|
+
>
|
|
246
|
+
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
247
|
+
{#if isSelected}
|
|
248
|
+
<svg
|
|
249
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
250
|
+
width="16"
|
|
251
|
+
height="16"
|
|
252
|
+
viewBox="0 0 24 24"
|
|
253
|
+
fill="none"
|
|
254
|
+
stroke="currentColor"
|
|
255
|
+
stroke-width="2"
|
|
256
|
+
stroke-linecap="round"
|
|
257
|
+
stroke-linejoin="round"
|
|
258
|
+
class="h-4 w-4"
|
|
259
|
+
>
|
|
260
|
+
<polyline points="20 6 9 17 4 12" />
|
|
261
|
+
</svg>
|
|
262
|
+
{:else}
|
|
263
|
+
<span class="h-3.5 w-3.5 rounded-sm border border-primary"></span>
|
|
264
|
+
{/if}
|
|
265
|
+
</span>
|
|
266
|
+
{option.label}
|
|
267
|
+
</ComboboxPrimitive.Item>
|
|
268
|
+
{/each}
|
|
269
|
+
{/if}
|
|
270
|
+
</div>
|
|
271
|
+
</ComboboxPrimitive.Content>
|
|
272
|
+
</ComboboxPrimitive.Root>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
{#if hint && !error}
|
|
276
|
+
<p id={hintId} class="text-sm text-muted-foreground">
|
|
277
|
+
{hint}
|
|
278
|
+
</p>
|
|
279
|
+
{/if}
|
|
280
|
+
|
|
281
|
+
{#if error}
|
|
282
|
+
<p id={errorId} class="text-sm text-destructive" role="alert" aria-live="polite">
|
|
283
|
+
{error}
|
|
284
|
+
</p>
|
|
285
|
+
{/if}
|
|
286
|
+
</div>
|
|
287
|
+
{:else}
|
|
288
|
+
<div class={cn('w-full', className)}>
|
|
289
|
+
<!-- Selected tags -->
|
|
290
|
+
{#if selectedLabels.length > 0}
|
|
291
|
+
<div class="flex flex-wrap gap-1 mb-2">
|
|
292
|
+
{#each value as selectedValue}
|
|
293
|
+
{@const labelText = options.find((o) => o.value === selectedValue)?.label}
|
|
294
|
+
{#if labelText}
|
|
295
|
+
<span
|
|
296
|
+
class="inline-flex items-center gap-1 rounded-md bg-secondary px-2 py-1 text-xs font-medium text-secondary-foreground"
|
|
297
|
+
>
|
|
298
|
+
{labelText}
|
|
299
|
+
<button
|
|
300
|
+
type="button"
|
|
301
|
+
onclick={() => removeValue(selectedValue)}
|
|
302
|
+
class="rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none"
|
|
303
|
+
{disabled}
|
|
304
|
+
aria-label={`Remove ${labelText}`}
|
|
305
|
+
>
|
|
306
|
+
<svg
|
|
307
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
308
|
+
width="14"
|
|
309
|
+
height="14"
|
|
310
|
+
viewBox="0 0 24 24"
|
|
311
|
+
fill="none"
|
|
312
|
+
stroke="currentColor"
|
|
313
|
+
stroke-width="2"
|
|
314
|
+
stroke-linecap="round"
|
|
315
|
+
stroke-linejoin="round"
|
|
316
|
+
>
|
|
317
|
+
<path d="M18 6 6 18" />
|
|
318
|
+
<path d="m6 6 12 12" />
|
|
319
|
+
</svg>
|
|
320
|
+
</button>
|
|
321
|
+
</span>
|
|
322
|
+
{/if}
|
|
323
|
+
{/each}
|
|
324
|
+
</div>
|
|
325
|
+
{/if}
|
|
326
|
+
|
|
327
|
+
<ComboboxPrimitive.Root
|
|
328
|
+
type="multiple"
|
|
329
|
+
{disabled}
|
|
330
|
+
{required}
|
|
331
|
+
{name}
|
|
332
|
+
bind:open
|
|
333
|
+
onOpenChange={handleOpenChange}
|
|
334
|
+
onValueChange={handleValueChange}
|
|
335
|
+
{value}
|
|
336
|
+
>
|
|
337
|
+
<div class="relative">
|
|
338
|
+
<ComboboxPrimitive.Input
|
|
339
|
+
id={componentId}
|
|
340
|
+
placeholder={value.length > 0 ? `${value.length} selected` : placeholder}
|
|
341
|
+
oninput={handleSearchInput}
|
|
342
|
+
onfocus={() => (open = true)}
|
|
343
|
+
onclick={() => (open = true)}
|
|
344
|
+
class={cn(
|
|
345
|
+
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
346
|
+
error && 'border-destructive focus:ring-destructive'
|
|
347
|
+
)}
|
|
348
|
+
aria-invalid={error ? 'true' : undefined}
|
|
349
|
+
aria-describedby={describedBy}
|
|
350
|
+
/>
|
|
237
351
|
</div>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
{
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
352
|
+
|
|
353
|
+
<ComboboxPrimitive.Content
|
|
354
|
+
class="isolate relative z-50 min-w-[var(--bits-floating-anchor-width)] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:border-t-0 data-[side=bottom]:rounded-t-none data-[side=top]:border-b-0 data-[side=top]:rounded-b-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
|
|
355
|
+
sideOffset={-1}
|
|
356
|
+
collisionPadding={8}
|
|
357
|
+
avoidCollisions={true}
|
|
358
|
+
>
|
|
359
|
+
<div class="p-1 max-h-[300px] overflow-y-auto">
|
|
360
|
+
{#if loading}
|
|
361
|
+
<div class="flex items-center justify-center py-6">
|
|
362
|
+
<Spinner size="sm" />
|
|
363
|
+
<span class="ml-2 text-sm text-muted-foreground">Loading...</span>
|
|
364
|
+
</div>
|
|
365
|
+
{:else if filteredOptions.length === 0}
|
|
366
|
+
<div class="py-6 text-center text-sm text-muted-foreground">
|
|
367
|
+
{emptyMessage}
|
|
368
|
+
</div>
|
|
369
|
+
{:else}
|
|
370
|
+
{#each filteredOptions as option}
|
|
371
|
+
{@const isSelected = value.includes(option.value)}
|
|
372
|
+
{@const isDisabledByMax = maxReached && !isSelected}
|
|
373
|
+
<ComboboxPrimitive.Item
|
|
374
|
+
value={option.value}
|
|
375
|
+
label={option.label}
|
|
376
|
+
disabled={option.disabled || isDisabledByMax}
|
|
377
|
+
class="relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[disabled]:cursor-default"
|
|
378
|
+
>
|
|
379
|
+
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
380
|
+
{#if isSelected}
|
|
381
|
+
<svg
|
|
382
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
383
|
+
width="16"
|
|
384
|
+
height="16"
|
|
385
|
+
viewBox="0 0 24 24"
|
|
386
|
+
fill="none"
|
|
387
|
+
stroke="currentColor"
|
|
388
|
+
stroke-width="2"
|
|
389
|
+
stroke-linecap="round"
|
|
390
|
+
stroke-linejoin="round"
|
|
391
|
+
class="h-4 w-4"
|
|
392
|
+
>
|
|
393
|
+
<polyline points="20 6 9 17 4 12" />
|
|
394
|
+
</svg>
|
|
395
|
+
{:else}
|
|
396
|
+
<span class="h-3.5 w-3.5 rounded-sm border border-primary"></span>
|
|
397
|
+
{/if}
|
|
398
|
+
</span>
|
|
399
|
+
{option.label}
|
|
400
|
+
</ComboboxPrimitive.Item>
|
|
401
|
+
{/each}
|
|
402
|
+
{/if}
|
|
403
|
+
</div>
|
|
404
|
+
</ComboboxPrimitive.Content>
|
|
405
|
+
</ComboboxPrimitive.Root>
|
|
406
|
+
</div>
|
|
246
407
|
{/if}
|
|
@@ -22,6 +22,10 @@ interface Props {
|
|
|
22
22
|
name?: string;
|
|
23
23
|
/** Element ID */
|
|
24
24
|
id?: string;
|
|
25
|
+
/** Label text for the multiselect */
|
|
26
|
+
label?: string;
|
|
27
|
+
/** Hint text displayed below the multiselect */
|
|
28
|
+
hint?: string;
|
|
25
29
|
/** Error message to display */
|
|
26
30
|
error?: string;
|
|
27
31
|
/** Maximum number of selections allowed */
|