@makolabs/ripple 2.1.0 → 2.3.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/dist/charts/Chart.svelte +307 -53
- package/dist/charts/chart-types.d.ts +12 -0
- package/dist/elements/accordion/Accordion.svelte +27 -10
- package/dist/elements/accordion/accordion-types.d.ts +6 -0
- package/dist/elements/dropdown/Select.svelte +191 -63
- package/dist/elements/dropdown/dropdown-types.d.ts +13 -1
- package/dist/elements/dropdown/select.d.ts +15 -0
- package/dist/elements/dropdown/select.js +14 -8
- package/dist/forms/DateRange.svelte +16 -3
- package/dist/forms/Input.svelte +6 -4
- package/dist/forms/MarketSelector.svelte +7 -27
- package/dist/forms/NumberInput.svelte +9 -6
- package/dist/forms/SegmentedControl.svelte +5 -18
- package/dist/forms/Tags.svelte +1 -1
- package/dist/forms/form-types.d.ts +2 -31
- package/dist/forms/market/market-selector-types.d.ts +1 -21
- package/dist/forms/segmented-control.d.ts +4 -34
- package/dist/forms/segmented-control.js +19 -59
- package/dist/index.d.ts +3 -11
- package/dist/index.js +0 -8
- package/dist/layout/activity-list/ActivityList.svelte +55 -1
- package/dist/variants.js +6 -6
- package/package.json +1 -1
- package/dist/elements/collapsible/Collapsible.svelte +0 -79
- package/dist/elements/collapsible/Collapsible.svelte.d.ts +0 -4
- package/dist/elements/collapsible/CollapsibleTestWrapper.svelte +0 -23
- package/dist/elements/collapsible/CollapsibleTestWrapper.svelte.d.ts +0 -8
- package/dist/elements/collapsible/collapsible-types.d.ts +0 -16
- package/dist/elements/collapsible/collapsible-types.js +0 -1
- package/dist/elements/combobox/Combobox.svelte +0 -274
- package/dist/elements/combobox/Combobox.svelte.d.ts +0 -25
- package/dist/elements/combobox/ComboboxTestWrapper.svelte +0 -38
- package/dist/elements/combobox/ComboboxTestWrapper.svelte.d.ts +0 -4
- package/dist/elements/combobox/combobox-types.d.ts +0 -39
- package/dist/elements/combobox/combobox-types.js +0 -1
- package/dist/elements/timeline/Timeline.svelte +0 -95
- package/dist/elements/timeline/Timeline.svelte.d.ts +0 -7
- package/dist/elements/timeline/timeline-types.d.ts +0 -11
- package/dist/elements/timeline/timeline-types.js +0 -1
- package/dist/forms/RadioInputs.svelte +0 -73
- package/dist/forms/RadioInputs.svelte.d.ts +0 -4
- package/dist/forms/RadioPill.svelte +0 -66
- package/dist/forms/RadioPill.svelte.d.ts +0 -4
|
@@ -23,10 +23,17 @@
|
|
|
23
23
|
searchInputClass = '',
|
|
24
24
|
icon: Icon,
|
|
25
25
|
iconClass = '',
|
|
26
|
-
triggerClass = '',
|
|
26
|
+
triggerClass = '',
|
|
27
|
+
errors = [],
|
|
27
28
|
onselect = () => {},
|
|
28
29
|
onopen = () => {},
|
|
29
30
|
onclose = () => {},
|
|
31
|
+
onsearch,
|
|
32
|
+
debounceMs = 200,
|
|
33
|
+
minSearchLength = 0,
|
|
34
|
+
loadingText = 'Loading...',
|
|
35
|
+
emptyText = 'No items found',
|
|
36
|
+
itemSnippet,
|
|
30
37
|
testId
|
|
31
38
|
}: SelectProps = $props();
|
|
32
39
|
|
|
@@ -35,10 +42,17 @@
|
|
|
35
42
|
let labelRef = $state<HTMLLabelElement | null>(null);
|
|
36
43
|
let searchInputRef = $state<HTMLInputElement | null>(null);
|
|
37
44
|
let highlightedIndex = $state(-1);
|
|
45
|
+
let asyncResults = $state<SelectItem[]>([]);
|
|
46
|
+
let asyncLoading = $state(false);
|
|
47
|
+
let hasSearched = $state(false);
|
|
48
|
+
let debounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
49
|
+
let searchSeq = 0;
|
|
50
|
+
|
|
51
|
+
const isAsync = $derived(!!onsearch);
|
|
52
|
+
const isSearchable = $derived(searchable || isAsync);
|
|
38
53
|
|
|
39
54
|
const selectId = crypto.randomUUID().slice(0, 8);
|
|
40
55
|
|
|
41
|
-
// Convert value to array for internal processing if multiple is true
|
|
42
56
|
const valueArray = $derived.by(() => {
|
|
43
57
|
if (multiple) {
|
|
44
58
|
return Array.isArray(value) ? value : value ? [value] : [];
|
|
@@ -50,7 +64,8 @@
|
|
|
50
64
|
selectTV({
|
|
51
65
|
size,
|
|
52
66
|
disabled,
|
|
53
|
-
multiple
|
|
67
|
+
multiple,
|
|
68
|
+
error: !!errors?.length
|
|
54
69
|
})
|
|
55
70
|
);
|
|
56
71
|
|
|
@@ -63,25 +78,86 @@
|
|
|
63
78
|
const itemClass_ = $derived(cn(item(), itemClass));
|
|
64
79
|
const emptyMessageClass = $derived(cn(emptyMessage()));
|
|
65
80
|
|
|
66
|
-
const selectedItem = $derived(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const filteredItems = $derived(
|
|
70
|
-
searchable && searchQuery
|
|
71
|
-
? items.filter((item) => item.label.toLowerCase().includes(searchQuery.toLowerCase()))
|
|
72
|
-
: items
|
|
81
|
+
const selectedItem = $derived(
|
|
82
|
+
(isAsync ? asyncResults : items).find((i) => i.value === value) ??
|
|
83
|
+
items.find((i) => i.value === value)
|
|
73
84
|
);
|
|
85
|
+
const selectedItems = $derived(items.filter((i) => valueArray.includes(i.value)));
|
|
86
|
+
|
|
87
|
+
const filteredItems = $derived.by<SelectItem[]>(() => {
|
|
88
|
+
if (isAsync) return asyncResults;
|
|
89
|
+
if (isSearchable && searchQuery) {
|
|
90
|
+
return items.filter(
|
|
91
|
+
(i) =>
|
|
92
|
+
i.label.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
93
|
+
(i.description ?? '').toLowerCase().includes(searchQuery.toLowerCase())
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return items;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const groupedItems = $derived.by(() => {
|
|
100
|
+
const hasGroups = filteredItems.some((i) => i.group);
|
|
101
|
+
if (!hasGroups) return null;
|
|
102
|
+
const groups: Array<{ label: string | null; items: SelectItem[] }> = [];
|
|
103
|
+
const idxMap: Record<string, number> = {};
|
|
104
|
+
let nullIdx = -1;
|
|
105
|
+
for (const i of filteredItems) {
|
|
106
|
+
const key = i.group;
|
|
107
|
+
let idx: number;
|
|
108
|
+
if (!key) {
|
|
109
|
+
if (nullIdx === -1) {
|
|
110
|
+
nullIdx = groups.length;
|
|
111
|
+
groups.push({ label: null, items: [] });
|
|
112
|
+
}
|
|
113
|
+
idx = nullIdx;
|
|
114
|
+
} else {
|
|
115
|
+
idx = idxMap[key] ?? -1;
|
|
116
|
+
if (idx === -1) {
|
|
117
|
+
idx = groups.length;
|
|
118
|
+
idxMap[key] = idx;
|
|
119
|
+
groups.push({ label: key, items: [] });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
groups[idx].items.push(i);
|
|
123
|
+
}
|
|
124
|
+
return groups;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
$effect(() => {
|
|
128
|
+
if (!isAsync || !onsearch || !hasSearched) return;
|
|
129
|
+
const query = searchQuery;
|
|
130
|
+
clearTimeout(debounceTimer);
|
|
131
|
+
if (query.length < minSearchLength) {
|
|
132
|
+
++searchSeq;
|
|
133
|
+
asyncResults = [];
|
|
134
|
+
asyncLoading = false;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
debounceTimer = setTimeout(async () => {
|
|
138
|
+
const seq = ++searchSeq;
|
|
139
|
+
asyncLoading = true;
|
|
140
|
+
try {
|
|
141
|
+
const fetched = await onsearch(query);
|
|
142
|
+
if (seq !== searchSeq) return;
|
|
143
|
+
asyncResults = fetched;
|
|
144
|
+
} finally {
|
|
145
|
+
if (seq === searchSeq) asyncLoading = false;
|
|
146
|
+
}
|
|
147
|
+
}, debounceMs);
|
|
148
|
+
return () => clearTimeout(debounceTimer);
|
|
149
|
+
});
|
|
74
150
|
|
|
75
151
|
function handleToggle() {
|
|
76
152
|
if (disabled) return;
|
|
77
153
|
open = !open;
|
|
78
154
|
|
|
79
155
|
if (open) {
|
|
80
|
-
highlightedIndex = !multiple ? filteredItems.findIndex((
|
|
156
|
+
highlightedIndex = !multiple ? filteredItems.findIndex((i) => i.value === value) : -1;
|
|
81
157
|
|
|
82
158
|
onopen();
|
|
83
159
|
|
|
84
|
-
if (
|
|
160
|
+
if (isSearchable) {
|
|
85
161
|
tick().then(() => {
|
|
86
162
|
searchInputRef?.focus();
|
|
87
163
|
});
|
|
@@ -89,6 +165,7 @@
|
|
|
89
165
|
} else {
|
|
90
166
|
onclose();
|
|
91
167
|
searchQuery = '';
|
|
168
|
+
hasSearched = false;
|
|
92
169
|
}
|
|
93
170
|
}
|
|
94
171
|
|
|
@@ -107,7 +184,7 @@
|
|
|
107
184
|
}
|
|
108
185
|
|
|
109
186
|
// Keep dropdown open when multiple selection is enabled
|
|
110
|
-
if (
|
|
187
|
+
if (isSearchable && searchInputRef) {
|
|
111
188
|
searchInputRef.focus();
|
|
112
189
|
}
|
|
113
190
|
} else {
|
|
@@ -196,6 +273,69 @@
|
|
|
196
273
|
}
|
|
197
274
|
</script>
|
|
198
275
|
|
|
276
|
+
{#snippet itemButton(
|
|
277
|
+
selectItem: SelectItem,
|
|
278
|
+
index: number,
|
|
279
|
+
selected: boolean,
|
|
280
|
+
highlighted: boolean
|
|
281
|
+
)}
|
|
282
|
+
<li>
|
|
283
|
+
<button
|
|
284
|
+
type="button"
|
|
285
|
+
onclick={(event) => {
|
|
286
|
+
handleSelect(selectItem);
|
|
287
|
+
event.preventDefault();
|
|
288
|
+
}}
|
|
289
|
+
disabled={selectItem.disabled}
|
|
290
|
+
class={itemClass_}
|
|
291
|
+
role="option"
|
|
292
|
+
aria-selected={selected}
|
|
293
|
+
data-selected={selected}
|
|
294
|
+
data-highlighted={highlighted}
|
|
295
|
+
data-index={index}
|
|
296
|
+
data-testid={buildTestId('select', 'option', testId, index)}
|
|
297
|
+
>
|
|
298
|
+
{#if itemSnippet}
|
|
299
|
+
{@render itemSnippet(selectItem, { highlighted, selected })}
|
|
300
|
+
{:else}
|
|
301
|
+
<span class="flex w-full items-center justify-between">
|
|
302
|
+
<span class="flex items-center gap-2 overflow-hidden">
|
|
303
|
+
{#if selectItem.icon}
|
|
304
|
+
{@const ItemIcon = selectItem.icon}
|
|
305
|
+
<ItemIcon class="h-4 w-4 flex-shrink-0" />
|
|
306
|
+
{/if}
|
|
307
|
+
<span class="min-w-0">
|
|
308
|
+
<span class="block truncate">{selectItem.label}</span>
|
|
309
|
+
{#if selectItem.description}
|
|
310
|
+
<span class="text-default-500 block truncate text-xs">
|
|
311
|
+
{selectItem.description}
|
|
312
|
+
</span>
|
|
313
|
+
{/if}
|
|
314
|
+
</span>
|
|
315
|
+
</span>
|
|
316
|
+
|
|
317
|
+
{#if selected}
|
|
318
|
+
<svg
|
|
319
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
320
|
+
width="16"
|
|
321
|
+
height="16"
|
|
322
|
+
viewBox="0 0 24 24"
|
|
323
|
+
fill="none"
|
|
324
|
+
stroke="currentColor"
|
|
325
|
+
stroke-width="2"
|
|
326
|
+
stroke-linecap="round"
|
|
327
|
+
stroke-linejoin="round"
|
|
328
|
+
class="text-info-500 flex-shrink-0"
|
|
329
|
+
>
|
|
330
|
+
<polyline points="20 6 9 17 4 12" />
|
|
331
|
+
</svg>
|
|
332
|
+
{/if}
|
|
333
|
+
</span>
|
|
334
|
+
{/if}
|
|
335
|
+
</button>
|
|
336
|
+
</li>
|
|
337
|
+
{/snippet}
|
|
338
|
+
|
|
199
339
|
<svelte:window onclick={handleClickOutside} onkeydown={handleKeydown} />
|
|
200
340
|
|
|
201
341
|
<label
|
|
@@ -228,7 +368,7 @@
|
|
|
228
368
|
{selectedItem.label}
|
|
229
369
|
</span>
|
|
230
370
|
{:else}
|
|
231
|
-
<span id="{selectId}-label" class="text-default-
|
|
371
|
+
<span id="{selectId}-label" class="text-default-400 px-1">
|
|
232
372
|
{placeholder}
|
|
233
373
|
</span>
|
|
234
374
|
{/if}
|
|
@@ -254,6 +394,12 @@
|
|
|
254
394
|
</span>
|
|
255
395
|
</label>
|
|
256
396
|
|
|
397
|
+
{#if errors?.length}
|
|
398
|
+
{#each errors as error, i (i)}
|
|
399
|
+
<p class="text-danger-600 mt-1 text-sm">{error}</p>
|
|
400
|
+
{/each}
|
|
401
|
+
{/if}
|
|
402
|
+
|
|
257
403
|
{#if open}
|
|
258
404
|
<Portal target={labelRef}>
|
|
259
405
|
<div
|
|
@@ -262,7 +408,7 @@
|
|
|
262
408
|
aria-labelledby="{selectId}-label"
|
|
263
409
|
data-testid={buildTestId('select', 'list', testId)}
|
|
264
410
|
>
|
|
265
|
-
{#if
|
|
411
|
+
{#if isSearchable}
|
|
266
412
|
<div class={searchInputClass_}>
|
|
267
413
|
<svg
|
|
268
414
|
xmlns="http://www.w3.org/2000/svg"
|
|
@@ -280,63 +426,45 @@
|
|
|
280
426
|
bind:this={searchInputRef}
|
|
281
427
|
bind:value={searchQuery}
|
|
282
428
|
type="text"
|
|
283
|
-
class="ring-0 outline-0"
|
|
429
|
+
class="w-full ring-0 outline-0"
|
|
284
430
|
placeholder="Search..."
|
|
285
431
|
aria-label="Search select options"
|
|
432
|
+
oninput={() => (hasSearched = true)}
|
|
286
433
|
data-testid={buildTestId('select', 'search', testId)}
|
|
287
434
|
/>
|
|
288
435
|
</div>
|
|
289
436
|
{/if}
|
|
290
437
|
|
|
291
|
-
{#if
|
|
292
|
-
<div class={emptyMessageClass}>
|
|
293
|
-
{:else}
|
|
438
|
+
{#if asyncLoading}
|
|
439
|
+
<div class={emptyMessageClass} data-select-loading="">{loadingText}</div>
|
|
440
|
+
{:else if filteredItems.length === 0}
|
|
441
|
+
<div class={emptyMessageClass}>{emptyText}</div>
|
|
442
|
+
{:else if groupedItems}
|
|
294
443
|
<ul class={listClass_}>
|
|
295
|
-
{#each
|
|
296
|
-
|
|
297
|
-
<
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
event.preventDefault();
|
|
302
|
-
}}
|
|
303
|
-
disabled={item.disabled}
|
|
304
|
-
class={itemClass_}
|
|
305
|
-
role="option"
|
|
306
|
-
aria-selected={valueArray.includes(item.value)}
|
|
307
|
-
data-selected={valueArray.includes(item.value)}
|
|
308
|
-
data-highlighted={index === highlightedIndex}
|
|
309
|
-
data-index={index}
|
|
310
|
-
data-testid={buildTestId('select', 'option', testId, index)}
|
|
444
|
+
{#each groupedItems as group, gIdx (group.label ?? `__null__${gIdx}`)}
|
|
445
|
+
{#if group.label !== null}
|
|
446
|
+
<li
|
|
447
|
+
class="text-default-500 bg-default-50 px-3 py-1.5 text-xs font-semibold tracking-wide uppercase"
|
|
448
|
+
role="presentation"
|
|
449
|
+
data-select-group={group.label}
|
|
311
450
|
>
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
stroke-width="2"
|
|
330
|
-
stroke-linecap="round"
|
|
331
|
-
stroke-linejoin="round"
|
|
332
|
-
class="text-info-500"
|
|
333
|
-
>
|
|
334
|
-
<polyline points="20 6 9 17 4 12" />
|
|
335
|
-
</svg>
|
|
336
|
-
{/if}
|
|
337
|
-
</span>
|
|
338
|
-
</button>
|
|
339
|
-
</li>
|
|
451
|
+
{group.label}
|
|
452
|
+
</li>
|
|
453
|
+
{/if}
|
|
454
|
+
{#each group.items as groupItem (groupItem.value)}
|
|
455
|
+
{@const flatIdx = filteredItems.indexOf(groupItem)}
|
|
456
|
+
{@const selected = valueArray.includes(groupItem.value)}
|
|
457
|
+
{@const highlighted = flatIdx === highlightedIndex}
|
|
458
|
+
{@render itemButton(groupItem, flatIdx, selected, highlighted)}
|
|
459
|
+
{/each}
|
|
460
|
+
{/each}
|
|
461
|
+
</ul>
|
|
462
|
+
{:else}
|
|
463
|
+
<ul class={listClass_}>
|
|
464
|
+
{#each filteredItems as flatItem, index (flatItem.value)}
|
|
465
|
+
{@const selected = valueArray.includes(flatItem.value)}
|
|
466
|
+
{@const highlighted = index === highlightedIndex}
|
|
467
|
+
{@render itemButton(flatItem, index, selected, highlighted)}
|
|
340
468
|
{/each}
|
|
341
469
|
</ul>
|
|
342
470
|
{/if}
|
|
@@ -41,9 +41,11 @@ export type SelectItem = {
|
|
|
41
41
|
value: string;
|
|
42
42
|
disabled?: boolean;
|
|
43
43
|
icon?: Component;
|
|
44
|
+
description?: string;
|
|
45
|
+
group?: string;
|
|
44
46
|
};
|
|
45
47
|
export type SelectProps = {
|
|
46
|
-
items
|
|
48
|
+
items?: SelectItem[];
|
|
47
49
|
value?: string | string[];
|
|
48
50
|
multiple?: boolean;
|
|
49
51
|
placeholder?: string;
|
|
@@ -59,10 +61,20 @@ export type SelectProps = {
|
|
|
59
61
|
clearable?: boolean;
|
|
60
62
|
icon?: Component;
|
|
61
63
|
iconClass?: ClassValue;
|
|
64
|
+
errors?: string[];
|
|
62
65
|
onselect?: ({ value }: {
|
|
63
66
|
value: string | string[];
|
|
64
67
|
}) => void;
|
|
65
68
|
onopen?: () => void;
|
|
66
69
|
onclose?: () => void;
|
|
67
70
|
testId?: string;
|
|
71
|
+
onsearch?: (query: string) => SelectItem[] | Promise<SelectItem[]>;
|
|
72
|
+
debounceMs?: number;
|
|
73
|
+
minSearchLength?: number;
|
|
74
|
+
loadingText?: string;
|
|
75
|
+
emptyText?: string;
|
|
76
|
+
itemSnippet?: Snippet<[SelectItem, {
|
|
77
|
+
highlighted: boolean;
|
|
78
|
+
selected: boolean;
|
|
79
|
+
}]>;
|
|
68
80
|
};
|
|
@@ -50,6 +50,11 @@ export declare const selectTV: import("tailwind-variants").TVReturnType<{
|
|
|
50
50
|
item: string;
|
|
51
51
|
};
|
|
52
52
|
};
|
|
53
|
+
error: {
|
|
54
|
+
true: {
|
|
55
|
+
trigger: string;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
53
58
|
multiple: {
|
|
54
59
|
true: {
|
|
55
60
|
trigger: string;
|
|
@@ -116,6 +121,11 @@ export declare const selectTV: import("tailwind-variants").TVReturnType<{
|
|
|
116
121
|
item: string;
|
|
117
122
|
};
|
|
118
123
|
};
|
|
124
|
+
error: {
|
|
125
|
+
true: {
|
|
126
|
+
trigger: string;
|
|
127
|
+
};
|
|
128
|
+
};
|
|
119
129
|
multiple: {
|
|
120
130
|
true: {
|
|
121
131
|
trigger: string;
|
|
@@ -182,6 +192,11 @@ export declare const selectTV: import("tailwind-variants").TVReturnType<{
|
|
|
182
192
|
item: string;
|
|
183
193
|
};
|
|
184
194
|
};
|
|
195
|
+
error: {
|
|
196
|
+
true: {
|
|
197
|
+
trigger: string;
|
|
198
|
+
};
|
|
199
|
+
};
|
|
185
200
|
multiple: {
|
|
186
201
|
true: {
|
|
187
202
|
trigger: string;
|
|
@@ -2,7 +2,7 @@ import { tv } from 'tailwind-variants';
|
|
|
2
2
|
import { Size } from '../../variants.js';
|
|
3
3
|
export const selectTV = tv({
|
|
4
4
|
slots: {
|
|
5
|
-
base: '',
|
|
5
|
+
base: 'w-full',
|
|
6
6
|
trigger: `relative flex items-center justify-between w-full text-left bg-white border
|
|
7
7
|
border-default-300 text-sm focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:border-primary-500 focus-within:ring-primary-500 rounded-lg shadow-xs cursor-pointer transition-colors hover:border-default-400`,
|
|
8
8
|
triggerIcon: 'transition-transform duration-200 text-default-500',
|
|
@@ -23,42 +23,42 @@ export const selectTV = tv({
|
|
|
23
23
|
triggerIcon: 'h-3 w-3',
|
|
24
24
|
container: 'max-h-40',
|
|
25
25
|
item: 'px-2 py-1 text-xs',
|
|
26
|
-
base: 'w-24'
|
|
26
|
+
base: 'min-w-24'
|
|
27
27
|
},
|
|
28
28
|
[Size.SM]: {
|
|
29
29
|
trigger: 'h-8 px-3 py-2 text-sm gap-1.5',
|
|
30
30
|
triggerIcon: 'h-3.5 w-3.5',
|
|
31
31
|
container: 'max-h-48',
|
|
32
32
|
item: 'px-2.5 py-1.5 text-xs',
|
|
33
|
-
base: 'w-32'
|
|
33
|
+
base: 'min-w-32'
|
|
34
34
|
},
|
|
35
35
|
[Size.BASE]: {
|
|
36
36
|
trigger: 'h-10 px-3 py-2 text-base gap-2',
|
|
37
37
|
triggerIcon: 'h-4 w-4',
|
|
38
38
|
container: 'max-h-60',
|
|
39
39
|
item: 'px-3 py-2 text-sm',
|
|
40
|
-
base: 'w-40'
|
|
40
|
+
base: 'min-w-40'
|
|
41
41
|
},
|
|
42
42
|
[Size.LG]: {
|
|
43
43
|
trigger: 'h-12 px-3 py-2 text-lg gap-2.5',
|
|
44
44
|
triggerIcon: 'h-5 w-5',
|
|
45
45
|
container: 'max-h-72',
|
|
46
46
|
item: 'px-4 py-2.5 text-base',
|
|
47
|
-
base: 'w-48'
|
|
47
|
+
base: 'min-w-48'
|
|
48
48
|
},
|
|
49
49
|
[Size.XL]: {
|
|
50
50
|
trigger: 'h-12 px-5 py-3 text-lg gap-3',
|
|
51
51
|
triggerIcon: 'h-6 w-6',
|
|
52
52
|
container: 'max-h-80',
|
|
53
53
|
item: 'px-5 py-3 text-lg',
|
|
54
|
-
base: 'w-56'
|
|
54
|
+
base: 'min-w-56'
|
|
55
55
|
},
|
|
56
56
|
[Size.XXL]: {
|
|
57
57
|
trigger: 'h-14 px-6 py-3.5 text-xl gap-4',
|
|
58
58
|
triggerIcon: 'h-7 w-7',
|
|
59
59
|
container: 'max-h-96',
|
|
60
60
|
item: 'px-6 py-3.5 text-xl',
|
|
61
|
-
base: 'w-64'
|
|
61
|
+
base: 'min-w-64'
|
|
62
62
|
}
|
|
63
63
|
},
|
|
64
64
|
disabled: {
|
|
@@ -68,6 +68,11 @@ export const selectTV = tv({
|
|
|
68
68
|
item: 'disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent'
|
|
69
69
|
}
|
|
70
70
|
},
|
|
71
|
+
error: {
|
|
72
|
+
true: {
|
|
73
|
+
trigger: 'border-danger-300 focus-within:ring-danger-500 focus-within:border-danger-500'
|
|
74
|
+
}
|
|
75
|
+
},
|
|
71
76
|
multiple: {
|
|
72
77
|
true: {
|
|
73
78
|
trigger: 'flex-wrap min-h-[2.5rem]'
|
|
@@ -77,6 +82,7 @@ export const selectTV = tv({
|
|
|
77
82
|
defaultVariants: {
|
|
78
83
|
size: 'base',
|
|
79
84
|
disabled: false,
|
|
80
|
-
multiple: false
|
|
85
|
+
multiple: false,
|
|
86
|
+
error: false
|
|
81
87
|
}
|
|
82
88
|
});
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
startLabel = 'Start date',
|
|
15
15
|
endLabel = 'End date',
|
|
16
16
|
format = 'MM/dd/yyyy',
|
|
17
|
+
errors = [],
|
|
17
18
|
id,
|
|
18
19
|
name,
|
|
19
20
|
onselect
|
|
@@ -236,14 +237,18 @@
|
|
|
236
237
|
{id}
|
|
237
238
|
type="button"
|
|
238
239
|
class={cn(
|
|
239
|
-
'border-default-300 flex w-full items-center justify-between rounded-
|
|
240
|
+
'border-default-300 flex w-full items-center justify-between rounded-lg border bg-white px-3 py-2 text-sm shadow-xs',
|
|
240
241
|
disabled
|
|
241
|
-
? 'bg-default-100 text-default-400 cursor-not-allowed'
|
|
242
|
-
:
|
|
242
|
+
? 'bg-default-100 text-default-400 cursor-not-allowed opacity-50'
|
|
243
|
+
: errors?.length
|
|
244
|
+
? 'border-danger-300 focus-within:ring-danger-500 focus-within:border-danger-500 focus-within:ring-2'
|
|
245
|
+
: 'focus-visible:border-primary-500 focus-visible:ring-primary-500 hover:border-default-400 focus-visible:ring-2'
|
|
243
246
|
)}
|
|
244
247
|
onclick={toggleDatepicker}
|
|
245
248
|
aria-haspopup="true"
|
|
246
249
|
aria-expanded={isOpen}
|
|
250
|
+
aria-invalid={errors?.length ? 'true' : undefined}
|
|
251
|
+
aria-describedby={errors?.length ? `${id}-errors` : undefined}
|
|
247
252
|
{disabled}
|
|
248
253
|
>
|
|
249
254
|
<span class={startDate && endDate ? 'text-default-900' : 'text-default-500'}>
|
|
@@ -280,6 +285,14 @@
|
|
|
280
285
|
{/if}
|
|
281
286
|
</div>
|
|
282
287
|
|
|
288
|
+
{#if errors?.length}
|
|
289
|
+
<div id="{id}-errors">
|
|
290
|
+
{#each errors as error, i (i)}
|
|
291
|
+
<p class="text-danger-600 mt-1 text-sm">{error}</p>
|
|
292
|
+
{/each}
|
|
293
|
+
</div>
|
|
294
|
+
{/if}
|
|
295
|
+
|
|
283
296
|
{#if isOpen}
|
|
284
297
|
<Portal target={datePickerRef}>
|
|
285
298
|
<div
|
package/dist/forms/Input.svelte
CHANGED
|
@@ -22,12 +22,13 @@
|
|
|
22
22
|
const BASIC_TYPES = ['text', 'email', 'password', 'number', 'tel', 'url', 'date', 'textarea'];
|
|
23
23
|
const inputClasses = $derived(
|
|
24
24
|
cn(
|
|
25
|
-
'transition-colors',
|
|
25
|
+
'transition-colors placeholder:text-default-400',
|
|
26
26
|
{
|
|
27
|
-
'border rounded-lg shadow-xs w-full bg-white px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2':
|
|
27
|
+
'border rounded-lg shadow-xs w-full bg-white px-3 py-2 text-sm focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2':
|
|
28
28
|
BASIC_TYPES.includes(type),
|
|
29
29
|
'w-full bg-white px-3 py-2 text-sm resize-y min-h-[100px]': type === 'textarea',
|
|
30
|
-
'border-danger-300 focus:border-danger-500 focus:ring-danger-500':
|
|
30
|
+
'border-danger-300 focus-within:border-danger-500 focus-within:ring-danger-500':
|
|
31
|
+
errors.length,
|
|
31
32
|
'opacity-50 cursor-not-allowed': disabled,
|
|
32
33
|
...(BASIC_TYPES.includes(type)
|
|
33
34
|
? {
|
|
@@ -36,7 +37,8 @@
|
|
|
36
37
|
'h-12 text-lg': size === Size.LG
|
|
37
38
|
}
|
|
38
39
|
: {}),
|
|
39
|
-
'border-default-300 focus:border-primary-500 focus:ring-primary-500':
|
|
40
|
+
'border-default-300 focus-within:border-primary-500 focus-within:ring-primary-500':
|
|
41
|
+
!errors.length
|
|
40
42
|
},
|
|
41
43
|
className
|
|
42
44
|
)
|
|
@@ -14,14 +14,12 @@
|
|
|
14
14
|
name = '',
|
|
15
15
|
showFlags = true,
|
|
16
16
|
label = '',
|
|
17
|
-
variant = undefined,
|
|
18
|
-
collapsed = false,
|
|
19
17
|
appearance = 'surface',
|
|
20
18
|
orientation = 'horizontal',
|
|
21
|
-
labelLayout =
|
|
19
|
+
labelLayout = 'inline',
|
|
22
20
|
labelClass = undefined,
|
|
23
21
|
color = Color.PRIMARY,
|
|
24
|
-
size = Size.
|
|
22
|
+
size = Size.SM,
|
|
25
23
|
compact = false,
|
|
26
24
|
disabled = false,
|
|
27
25
|
class: className = '',
|
|
@@ -29,24 +27,6 @@
|
|
|
29
27
|
testId
|
|
30
28
|
}: MarketSelectorProps = $props();
|
|
31
29
|
|
|
32
|
-
const resolvedAppearance = $derived.by(() => {
|
|
33
|
-
if (variant === 'default') return 'clarkDefault';
|
|
34
|
-
if (variant === 'sidebar') return 'clarkSidebar';
|
|
35
|
-
return appearance;
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const resolvedLabelLayout = $derived(labelLayout ?? (variant !== undefined ? 'inline' : 'above'));
|
|
39
|
-
|
|
40
|
-
const resolvedOrientation = $derived(
|
|
41
|
-
variant === 'sidebar' && collapsed ? 'vertical' : orientation
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
const resolvedCompact = $derived(variant === 'sidebar' && collapsed ? true : compact);
|
|
45
|
-
|
|
46
|
-
const resolvedLabelClass = $derived(
|
|
47
|
-
labelClass ?? (variant === 'sidebar' && collapsed && label ? 'sr-only' : undefined)
|
|
48
|
-
);
|
|
49
|
-
|
|
50
30
|
const options = $derived.by((): SegmentedOption[] => {
|
|
51
31
|
return markets.map((code) => ({
|
|
52
32
|
value: code,
|
|
@@ -77,13 +57,13 @@
|
|
|
77
57
|
{options}
|
|
78
58
|
{name}
|
|
79
59
|
{label}
|
|
80
|
-
appearance
|
|
81
|
-
orientation
|
|
82
|
-
labelLayout
|
|
83
|
-
labelClass
|
|
60
|
+
{appearance}
|
|
61
|
+
{orientation}
|
|
62
|
+
{labelLayout}
|
|
63
|
+
{labelClass}
|
|
84
64
|
{color}
|
|
85
65
|
{size}
|
|
86
|
-
compact
|
|
66
|
+
{compact}
|
|
87
67
|
{disabled}
|
|
88
68
|
{testId}
|
|
89
69
|
onchange={handleChange}
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
const containerClass = $derived(
|
|
30
30
|
cn(
|
|
31
|
-
'relative flex items-center gap-1 rounded-lg border bg-white shadow-
|
|
31
|
+
'relative flex items-center gap-1 rounded-lg border bg-white shadow-xs',
|
|
32
32
|
{
|
|
33
33
|
'border-danger-300': errors?.length,
|
|
34
34
|
'cursor-not-allowed opacity-50': disabled
|
|
@@ -44,11 +44,14 @@
|
|
|
44
44
|
);
|
|
45
45
|
|
|
46
46
|
const inputClass = $derived(
|
|
47
|
-
cn(
|
|
48
|
-
'
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
cn(
|
|
48
|
+
'w-full bg-transparent outline-none disabled:cursor-not-allowed px-3 placeholder:text-default-400',
|
|
49
|
+
{
|
|
50
|
+
'text-sm': size === Size.SM,
|
|
51
|
+
'text-base': size === Size.BASE,
|
|
52
|
+
'text-lg': size === Size.LG
|
|
53
|
+
}
|
|
54
|
+
)
|
|
52
55
|
);
|
|
53
56
|
|
|
54
57
|
const dropdownClass = cn(
|