@makolabs/ripple 1.12.0 → 1.13.1
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/collapsible/Collapsible.svelte +79 -0
- package/dist/elements/collapsible/Collapsible.svelte.d.ts +4 -0
- package/dist/elements/collapsible/CollapsibleTestWrapper.svelte +23 -0
- package/dist/elements/collapsible/CollapsibleTestWrapper.svelte.d.ts +8 -0
- package/dist/elements/collapsible/collapsible-types.d.ts +16 -0
- package/dist/elements/collapsible/collapsible-types.js +1 -0
- package/dist/elements/combobox/Combobox.svelte +274 -0
- package/dist/elements/combobox/Combobox.svelte.d.ts +25 -0
- package/dist/elements/combobox/ComboboxTestWrapper.svelte +38 -0
- package/dist/elements/combobox/ComboboxTestWrapper.svelte.d.ts +4 -0
- package/dist/elements/combobox/combobox-types.d.ts +39 -0
- package/dist/elements/combobox/combobox-types.js +1 -0
- package/dist/elements/empty-state/EmptyState.svelte +39 -0
- package/dist/elements/empty-state/EmptyState.svelte.d.ts +4 -0
- package/dist/elements/empty-state/EmptyStateTestWrapper.svelte +25 -0
- package/dist/elements/empty-state/EmptyStateTestWrapper.svelte.d.ts +8 -0
- package/dist/elements/empty-state/empty-state-types.d.ts +12 -0
- package/dist/elements/empty-state/empty-state-types.js +1 -0
- package/dist/elements/empty-state/empty-state.d.ts +100 -0
- package/dist/elements/empty-state/empty-state.js +42 -0
- package/dist/elements/pagination/Pagination.svelte +1 -1
- package/dist/elements/spinner/Spinner.svelte +38 -0
- package/dist/elements/spinner/Spinner.svelte.d.ts +4 -0
- package/dist/elements/spinner/spinner-types.d.ts +9 -0
- package/dist/elements/spinner/spinner-types.js +1 -0
- package/dist/elements/spinner/spinner.d.ts +163 -0
- package/dist/elements/spinner/spinner.js +32 -0
- package/dist/elements/tooltip/Tooltip.svelte +82 -0
- package/dist/elements/tooltip/Tooltip.svelte.d.ts +4 -0
- package/dist/elements/tooltip/TooltipTestWrapper.svelte +14 -0
- package/dist/elements/tooltip/TooltipTestWrapper.svelte.d.ts +7 -0
- package/dist/elements/tooltip/tooltip-types.d.ts +13 -0
- package/dist/elements/tooltip/tooltip-types.js +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +13 -0
- package/dist/layout/card/MetricCard.svelte +61 -5
- package/dist/layout/card/MetricCardActionWrapper.svelte +29 -0
- package/dist/layout/card/MetricCardActionWrapper.svelte.d.ts +8 -0
- package/dist/layout/card/card-types.d.ts +19 -0
- package/dist/layout/card/metric-card.d.ts +26 -26
- package/dist/layout/card/metric-card.js +16 -2
- package/dist/layout/navbar/navbar.js +1 -1
- package/dist/modal/Modal.svelte +1 -1
- package/dist/modal/ModalFooter.svelte +35 -0
- package/dist/modal/ModalFooter.svelte.d.ts +11 -0
- package/dist/modal/ModalFooterTestWrapper.svelte +17 -0
- package/dist/modal/ModalFooterTestWrapper.svelte.d.ts +8 -0
- package/dist/modal/modal.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../../helper/cls.js';
|
|
3
|
+
import { buildTestId } from '../../helper/testid.js';
|
|
4
|
+
import type { CollapsibleProps } from '../../index.js';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
open = $bindable(false),
|
|
8
|
+
title,
|
|
9
|
+
header,
|
|
10
|
+
children,
|
|
11
|
+
class: className = '',
|
|
12
|
+
headerClass = '',
|
|
13
|
+
contentClass = '',
|
|
14
|
+
disabled = false,
|
|
15
|
+
ontoggle,
|
|
16
|
+
testId
|
|
17
|
+
}: CollapsibleProps = $props();
|
|
18
|
+
|
|
19
|
+
function toggle() {
|
|
20
|
+
if (disabled) return;
|
|
21
|
+
open = !open;
|
|
22
|
+
ontoggle?.(open);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
26
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
27
|
+
event.preventDefault();
|
|
28
|
+
toggle();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<div
|
|
34
|
+
class={cn('border-default-200 overflow-hidden rounded-md border', className)}
|
|
35
|
+
data-testid={buildTestId('collapsible', undefined, testId)}
|
|
36
|
+
data-open={open ? 'true' : 'false'}
|
|
37
|
+
>
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
class={cn(
|
|
41
|
+
'flex w-full cursor-pointer items-center justify-between gap-2 px-4 py-3 text-left transition-colors',
|
|
42
|
+
'hover:bg-default-50 focus-visible:ring-primary-500 focus-visible:ring-2 focus-visible:outline-none',
|
|
43
|
+
disabled && 'cursor-not-allowed opacity-50',
|
|
44
|
+
headerClass
|
|
45
|
+
)}
|
|
46
|
+
aria-expanded={open}
|
|
47
|
+
aria-disabled={disabled ? 'true' : undefined}
|
|
48
|
+
{disabled}
|
|
49
|
+
onclick={toggle}
|
|
50
|
+
onkeydown={handleKeydown}
|
|
51
|
+
>
|
|
52
|
+
{#if header}
|
|
53
|
+
{@render header({ open })}
|
|
54
|
+
{:else}
|
|
55
|
+
<span class="text-default-800 font-medium">{title}</span>
|
|
56
|
+
{/if}
|
|
57
|
+
<svg
|
|
58
|
+
class={cn(
|
|
59
|
+
'text-default-500 h-4 w-4 shrink-0 transition-transform duration-200',
|
|
60
|
+
open && 'rotate-180'
|
|
61
|
+
)}
|
|
62
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
63
|
+
viewBox="0 0 20 20"
|
|
64
|
+
fill="currentColor"
|
|
65
|
+
aria-hidden="true"
|
|
66
|
+
>
|
|
67
|
+
<path
|
|
68
|
+
fill-rule="evenodd"
|
|
69
|
+
d="M5.23 7.21a.75.75 0 011.06.02L10 11.06l3.71-3.83a.75.75 0 111.08 1.04l-4.25 4.39a.75.75 0 01-1.08 0L5.21 8.27a.75.75 0 01.02-1.06z"
|
|
70
|
+
clip-rule="evenodd"
|
|
71
|
+
/>
|
|
72
|
+
</svg>
|
|
73
|
+
</button>
|
|
74
|
+
{#if open}
|
|
75
|
+
<div class={cn('border-default-200 border-t px-4 py-3', contentClass)}>
|
|
76
|
+
{@render children()}
|
|
77
|
+
</div>
|
|
78
|
+
{/if}
|
|
79
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Collapsible from './Collapsible.svelte';
|
|
3
|
+
import type { CollapsibleProps } from '../../index.js';
|
|
4
|
+
|
|
5
|
+
type Props = Omit<CollapsibleProps, 'children' | 'header'> & {
|
|
6
|
+
bodyText?: string;
|
|
7
|
+
headerText?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let { bodyText = '', headerText, ...rest }: Props = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
{#snippet body()}
|
|
14
|
+
<span data-testid="wrapper-body">{bodyText}</span>
|
|
15
|
+
{/snippet}
|
|
16
|
+
|
|
17
|
+
{#snippet customHeader({ open }: { open: boolean })}
|
|
18
|
+
<span data-testid="wrapper-header">{headerText} [{open ? 'open' : 'closed'}]</span>
|
|
19
|
+
{/snippet}
|
|
20
|
+
|
|
21
|
+
<Collapsible {...rest} header={headerText ? customHeader : undefined}>
|
|
22
|
+
{@render body()}
|
|
23
|
+
</Collapsible>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CollapsibleProps } from '../../index.js';
|
|
2
|
+
type Props = Omit<CollapsibleProps, 'children' | 'header'> & {
|
|
3
|
+
bodyText?: string;
|
|
4
|
+
headerText?: string;
|
|
5
|
+
};
|
|
6
|
+
declare const CollapsibleTestWrapper: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type CollapsibleTestWrapper = ReturnType<typeof CollapsibleTestWrapper>;
|
|
8
|
+
export default CollapsibleTestWrapper;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ClassValue } from 'tailwind-variants';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
export type CollapsibleProps = {
|
|
4
|
+
open?: boolean;
|
|
5
|
+
title?: string;
|
|
6
|
+
header?: Snippet<[{
|
|
7
|
+
open: boolean;
|
|
8
|
+
}]>;
|
|
9
|
+
children: Snippet;
|
|
10
|
+
class?: ClassValue;
|
|
11
|
+
headerClass?: ClassValue;
|
|
12
|
+
contentClass?: ClassValue;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
ontoggle?: (open: boolean) => void;
|
|
15
|
+
testId?: string;
|
|
16
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
<script lang="ts" generics="T extends ComboboxItem">
|
|
2
|
+
import { cn } from '../../helper/cls.js';
|
|
3
|
+
import { buildTestId } from '../../helper/testid.js';
|
|
4
|
+
import type { ComboboxItem, ComboboxProps } from '../../index.js';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
items,
|
|
8
|
+
onsearch,
|
|
9
|
+
value = null,
|
|
10
|
+
onselect,
|
|
11
|
+
placeholder = 'Search...',
|
|
12
|
+
emptyText = 'No results',
|
|
13
|
+
loadingText = 'Loading...',
|
|
14
|
+
debounceMs = 200,
|
|
15
|
+
minSearchLength = 0,
|
|
16
|
+
disabled = false,
|
|
17
|
+
class: className = '',
|
|
18
|
+
inputClass = '',
|
|
19
|
+
listClass = '',
|
|
20
|
+
itemSnippet,
|
|
21
|
+
testId
|
|
22
|
+
}: ComboboxProps<T> = $props();
|
|
23
|
+
|
|
24
|
+
let inputValue = $state(value?.label ?? '');
|
|
25
|
+
let isOpen = $state(false);
|
|
26
|
+
let highlightedIndex = $state(0);
|
|
27
|
+
let asyncResults = $state<T[]>([]);
|
|
28
|
+
let loading = $state(false);
|
|
29
|
+
let hasSearched = $state(false);
|
|
30
|
+
let rootEl: HTMLDivElement | undefined = $state();
|
|
31
|
+
let debounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
32
|
+
let searchSeq = 0;
|
|
33
|
+
|
|
34
|
+
function normalizeFilter(q: string): string {
|
|
35
|
+
return q.trim().toLowerCase();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function filterStatic(query: string, list: T[]): T[] {
|
|
39
|
+
const q = normalizeFilter(query);
|
|
40
|
+
if (!q) return list;
|
|
41
|
+
return list.filter(
|
|
42
|
+
(item) =>
|
|
43
|
+
item.label.toLowerCase().includes(q) || (item.description ?? '').toLowerCase().includes(q)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Static filtering is pure and runs every render — no side effects needed.
|
|
48
|
+
const results = $derived.by<T[]>(() => {
|
|
49
|
+
if (items !== undefined) return filterStatic(inputValue, items);
|
|
50
|
+
return asyncResults;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Async search: only triggered after user has interacted (hasSearched flag).
|
|
54
|
+
// Mount alone never fires onsearch, so static-mode tests and first-render
|
|
55
|
+
// async-mode tests don't see a spurious onsearch('') call.
|
|
56
|
+
$effect(() => {
|
|
57
|
+
if (items !== undefined) return;
|
|
58
|
+
if (!onsearch) return;
|
|
59
|
+
if (!hasSearched) return;
|
|
60
|
+
|
|
61
|
+
const query = inputValue;
|
|
62
|
+
clearTimeout(debounceTimer);
|
|
63
|
+
|
|
64
|
+
if (query.length < minSearchLength) {
|
|
65
|
+
asyncResults = [] as T[];
|
|
66
|
+
loading = false;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
debounceTimer = setTimeout(async () => {
|
|
71
|
+
const currentSeq = ++searchSeq;
|
|
72
|
+
loading = true;
|
|
73
|
+
try {
|
|
74
|
+
const fetched = await onsearch(query);
|
|
75
|
+
if (currentSeq !== searchSeq) return;
|
|
76
|
+
asyncResults = fetched as T[];
|
|
77
|
+
} finally {
|
|
78
|
+
if (currentSeq === searchSeq) loading = false;
|
|
79
|
+
}
|
|
80
|
+
}, debounceMs);
|
|
81
|
+
|
|
82
|
+
return () => {
|
|
83
|
+
clearTimeout(debounceTimer);
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Group results, preserving order of first appearance.
|
|
88
|
+
// Uses a plain object as the lookup (local to derivation, no reactivity needed).
|
|
89
|
+
const groupedResults = $derived.by(() => {
|
|
90
|
+
const groups: Array<{ label: string | null; items: T[] }> = [];
|
|
91
|
+
const indexByGroup: Record<string, number> = {};
|
|
92
|
+
let nullGroupIdx = -1;
|
|
93
|
+
for (const item of results) {
|
|
94
|
+
const key = item.group;
|
|
95
|
+
let idx: number;
|
|
96
|
+
if (key === undefined || key === null) {
|
|
97
|
+
if (nullGroupIdx === -1) {
|
|
98
|
+
nullGroupIdx = groups.length;
|
|
99
|
+
groups.push({ label: null, items: [] });
|
|
100
|
+
}
|
|
101
|
+
idx = nullGroupIdx;
|
|
102
|
+
} else {
|
|
103
|
+
idx = indexByGroup[key] ?? -1;
|
|
104
|
+
if (idx === -1) {
|
|
105
|
+
idx = groups.length;
|
|
106
|
+
indexByGroup[key] = idx;
|
|
107
|
+
groups.push({ label: key, items: [] });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
groups[idx].items.push(item);
|
|
111
|
+
}
|
|
112
|
+
return groups;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Flat enabled index for keyboard nav
|
|
116
|
+
const flatEnabled = $derived(results.filter((i) => !i.disabled));
|
|
117
|
+
|
|
118
|
+
function open() {
|
|
119
|
+
if (disabled) return;
|
|
120
|
+
isOpen = true;
|
|
121
|
+
highlightedIndex = 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function close() {
|
|
125
|
+
isOpen = false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function selectItem(item: T) {
|
|
129
|
+
if (item.disabled) return;
|
|
130
|
+
onselect?.(item);
|
|
131
|
+
inputValue = item.label;
|
|
132
|
+
close();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function handleInput(event: Event) {
|
|
136
|
+
const target = event.target as HTMLInputElement;
|
|
137
|
+
inputValue = target.value;
|
|
138
|
+
hasSearched = true;
|
|
139
|
+
if (!isOpen) open();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
143
|
+
if (disabled) return;
|
|
144
|
+
if (event.key === 'ArrowDown') {
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
if (!isOpen) open();
|
|
147
|
+
highlightedIndex = Math.min(highlightedIndex + 1, Math.max(flatEnabled.length - 1, 0));
|
|
148
|
+
} else if (event.key === 'ArrowUp') {
|
|
149
|
+
event.preventDefault();
|
|
150
|
+
highlightedIndex = Math.max(highlightedIndex - 1, 0);
|
|
151
|
+
} else if (event.key === 'Enter') {
|
|
152
|
+
if (isOpen && flatEnabled.length > 0) {
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
selectItem(flatEnabled[highlightedIndex]);
|
|
155
|
+
}
|
|
156
|
+
} else if (event.key === 'Escape') {
|
|
157
|
+
close();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Click-outside handling
|
|
162
|
+
$effect(() => {
|
|
163
|
+
if (!isOpen) return;
|
|
164
|
+
function onDocClick(e: MouseEvent) {
|
|
165
|
+
if (rootEl && !rootEl.contains(e.target as Node)) {
|
|
166
|
+
close();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
document.addEventListener('mousedown', onDocClick);
|
|
170
|
+
return () => document.removeEventListener('mousedown', onDocClick);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
function isHighlighted(item: T): boolean {
|
|
174
|
+
const idx = flatEnabled.indexOf(item);
|
|
175
|
+
return idx !== -1 && idx === highlightedIndex;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function isSelected(item: T): boolean {
|
|
179
|
+
return value != null && value.id === item.id;
|
|
180
|
+
}
|
|
181
|
+
</script>
|
|
182
|
+
|
|
183
|
+
<div
|
|
184
|
+
bind:this={rootEl}
|
|
185
|
+
class={cn('relative inline-flex w-full flex-col', className)}
|
|
186
|
+
data-testid={buildTestId('combobox', undefined, testId)}
|
|
187
|
+
>
|
|
188
|
+
<input
|
|
189
|
+
type="text"
|
|
190
|
+
role="combobox"
|
|
191
|
+
aria-expanded={isOpen}
|
|
192
|
+
aria-controls="combobox-listbox"
|
|
193
|
+
aria-autocomplete="list"
|
|
194
|
+
aria-disabled={disabled ? 'true' : undefined}
|
|
195
|
+
{disabled}
|
|
196
|
+
value={inputValue}
|
|
197
|
+
{placeholder}
|
|
198
|
+
class={cn(
|
|
199
|
+
'border-default-300 w-full rounded-md border bg-white px-3 py-2 text-sm',
|
|
200
|
+
'focus:border-primary-500 focus:ring-primary-500 focus:ring-1 focus:outline-none',
|
|
201
|
+
disabled && 'cursor-not-allowed opacity-50',
|
|
202
|
+
inputClass
|
|
203
|
+
)}
|
|
204
|
+
oninput={handleInput}
|
|
205
|
+
onfocus={open}
|
|
206
|
+
onkeydown={handleKeydown}
|
|
207
|
+
/>
|
|
208
|
+
|
|
209
|
+
{#if isOpen}
|
|
210
|
+
<ul
|
|
211
|
+
id="combobox-listbox"
|
|
212
|
+
role="listbox"
|
|
213
|
+
data-combobox-listbox=""
|
|
214
|
+
class={cn(
|
|
215
|
+
'border-default-200 absolute top-full left-0 z-50 mt-1 max-h-64 w-full overflow-y-auto rounded-md border bg-white shadow-lg',
|
|
216
|
+
listClass
|
|
217
|
+
)}
|
|
218
|
+
>
|
|
219
|
+
{#if loading}
|
|
220
|
+
<li class="text-default-500 px-3 py-2 text-sm" data-combobox-loading="">
|
|
221
|
+
{loadingText}
|
|
222
|
+
</li>
|
|
223
|
+
{:else if results.length === 0}
|
|
224
|
+
<li class="text-default-500 px-3 py-2 text-sm" data-combobox-empty="">
|
|
225
|
+
{emptyText}
|
|
226
|
+
</li>
|
|
227
|
+
{:else}
|
|
228
|
+
{#each groupedResults as group, groupIdx (group.label ?? `__null__${groupIdx}`)}
|
|
229
|
+
{#if group.label !== null}
|
|
230
|
+
<li
|
|
231
|
+
class="text-default-500 bg-default-50 px-3 py-1.5 text-xs font-semibold tracking-wide uppercase"
|
|
232
|
+
data-combobox-group={group.label}
|
|
233
|
+
role="presentation"
|
|
234
|
+
>
|
|
235
|
+
{group.label}
|
|
236
|
+
</li>
|
|
237
|
+
{/if}
|
|
238
|
+
{#each group.items as item (item.id)}
|
|
239
|
+
{@const highlighted = isHighlighted(item)}
|
|
240
|
+
{@const selected = isSelected(item)}
|
|
241
|
+
<li
|
|
242
|
+
role="option"
|
|
243
|
+
aria-selected={selected}
|
|
244
|
+
aria-disabled={item.disabled ? 'true' : undefined}
|
|
245
|
+
data-combobox-item=""
|
|
246
|
+
data-combobox-item-id={item.id}
|
|
247
|
+
class={cn(
|
|
248
|
+
'cursor-pointer px-3 py-2 text-sm',
|
|
249
|
+
highlighted && 'bg-primary-50',
|
|
250
|
+
selected && 'font-semibold',
|
|
251
|
+
item.disabled && 'cursor-not-allowed opacity-50'
|
|
252
|
+
)}
|
|
253
|
+
onclick={() => selectItem(item)}
|
|
254
|
+
onkeydown={() => {}}
|
|
255
|
+
onmouseenter={() => {
|
|
256
|
+
const idx = flatEnabled.indexOf(item);
|
|
257
|
+
if (idx !== -1) highlightedIndex = idx;
|
|
258
|
+
}}
|
|
259
|
+
>
|
|
260
|
+
{#if itemSnippet}
|
|
261
|
+
{@render itemSnippet(item, { highlighted, selected })}
|
|
262
|
+
{:else}
|
|
263
|
+
<div class="text-default-800 font-medium">{item.label}</div>
|
|
264
|
+
{#if item.description}
|
|
265
|
+
<div class="text-default-500 text-xs">{item.description}</div>
|
|
266
|
+
{/if}
|
|
267
|
+
{/if}
|
|
268
|
+
</li>
|
|
269
|
+
{/each}
|
|
270
|
+
{/each}
|
|
271
|
+
{/if}
|
|
272
|
+
</ul>
|
|
273
|
+
{/if}
|
|
274
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ComboboxItem, ComboboxProps } from '../../index.js';
|
|
2
|
+
declare function $$render<T extends ComboboxItem>(): {
|
|
3
|
+
props: ComboboxProps<T>;
|
|
4
|
+
exports: {};
|
|
5
|
+
bindings: "";
|
|
6
|
+
slots: {};
|
|
7
|
+
events: {};
|
|
8
|
+
};
|
|
9
|
+
declare class __sveltets_Render<T extends ComboboxItem> {
|
|
10
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
11
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
12
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
13
|
+
bindings(): "";
|
|
14
|
+
exports(): {};
|
|
15
|
+
}
|
|
16
|
+
interface $$IsomorphicComponent {
|
|
17
|
+
new <T extends ComboboxItem>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
18
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
19
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
20
|
+
<T extends ComboboxItem>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
21
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
22
|
+
}
|
|
23
|
+
declare const Combobox: $$IsomorphicComponent;
|
|
24
|
+
type Combobox<T extends ComboboxItem> = InstanceType<typeof Combobox<T>>;
|
|
25
|
+
export default Combobox;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Combobox from './Combobox.svelte';
|
|
3
|
+
import type { ComboboxItem, ComboboxProps } from '../../index.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
items,
|
|
7
|
+
onsearch,
|
|
8
|
+
value,
|
|
9
|
+
onselect,
|
|
10
|
+
placeholder,
|
|
11
|
+
emptyText,
|
|
12
|
+
loadingText,
|
|
13
|
+
debounceMs,
|
|
14
|
+
minSearchLength,
|
|
15
|
+
disabled,
|
|
16
|
+
class: className,
|
|
17
|
+
inputClass,
|
|
18
|
+
listClass,
|
|
19
|
+
testId
|
|
20
|
+
}: ComboboxProps<ComboboxItem> = $props();
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<Combobox
|
|
24
|
+
{items}
|
|
25
|
+
{onsearch}
|
|
26
|
+
{value}
|
|
27
|
+
{onselect}
|
|
28
|
+
{placeholder}
|
|
29
|
+
{emptyText}
|
|
30
|
+
{loadingText}
|
|
31
|
+
{debounceMs}
|
|
32
|
+
{minSearchLength}
|
|
33
|
+
{disabled}
|
|
34
|
+
class={className}
|
|
35
|
+
{inputClass}
|
|
36
|
+
{listClass}
|
|
37
|
+
{testId}
|
|
38
|
+
/>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ComboboxItem, ComboboxProps } from '../../index.js';
|
|
2
|
+
declare const ComboboxTestWrapper: import("svelte").Component<ComboboxProps<ComboboxItem>, {}, "">;
|
|
3
|
+
type ComboboxTestWrapper = ReturnType<typeof ComboboxTestWrapper>;
|
|
4
|
+
export default ComboboxTestWrapper;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ClassValue } from 'tailwind-variants';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
export type ComboboxItem = {
|
|
4
|
+
id: string | number;
|
|
5
|
+
label: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
group?: string;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export type ComboboxProps<T extends ComboboxItem = ComboboxItem> = {
|
|
11
|
+
/** Static items for client-side filtering. Provide this OR `onsearch`. */
|
|
12
|
+
items?: T[];
|
|
13
|
+
/**
|
|
14
|
+
* Async/sync search callback. Called with the debounced query string.
|
|
15
|
+
* Returns the list of items to display.
|
|
16
|
+
*/
|
|
17
|
+
onsearch?: (query: string) => T[] | Promise<T[]>;
|
|
18
|
+
/** Currently selected item (controlled). */
|
|
19
|
+
value?: T | null;
|
|
20
|
+
/** Fires when the user selects an item (click or Enter on highlighted). */
|
|
21
|
+
onselect?: (item: T) => void;
|
|
22
|
+
placeholder?: string;
|
|
23
|
+
emptyText?: string;
|
|
24
|
+
loadingText?: string;
|
|
25
|
+
/** Milliseconds to debounce onsearch calls (default 200). */
|
|
26
|
+
debounceMs?: number;
|
|
27
|
+
/** Minimum query length before onsearch fires (default 0). */
|
|
28
|
+
minSearchLength?: number;
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
class?: ClassValue;
|
|
31
|
+
inputClass?: ClassValue;
|
|
32
|
+
listClass?: ClassValue;
|
|
33
|
+
/** Optional snippet to override the default item rendering. */
|
|
34
|
+
itemSnippet?: Snippet<[T, {
|
|
35
|
+
highlighted: boolean;
|
|
36
|
+
selected: boolean;
|
|
37
|
+
}]>;
|
|
38
|
+
testId?: string;
|
|
39
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../../helper/cls.js';
|
|
3
|
+
import { buildTestId } from '../../helper/testid.js';
|
|
4
|
+
import { emptyState } from './empty-state.js';
|
|
5
|
+
import { Size } from '../../variants.js';
|
|
6
|
+
import type { EmptyStateProps } from '../../index.js';
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
title,
|
|
10
|
+
description,
|
|
11
|
+
size = Size.BASE,
|
|
12
|
+
class: className = '',
|
|
13
|
+
icon,
|
|
14
|
+
action,
|
|
15
|
+
testId
|
|
16
|
+
}: EmptyStateProps = $props();
|
|
17
|
+
|
|
18
|
+
const slots = $derived(emptyState({ size }));
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<div
|
|
22
|
+
class={cn(slots.wrapper(), className)}
|
|
23
|
+
data-testid={buildTestId('empty-state', undefined, testId)}
|
|
24
|
+
>
|
|
25
|
+
{#if icon}
|
|
26
|
+
<div class={slots.iconWrapper()} aria-hidden="true">
|
|
27
|
+
{@render icon()}
|
|
28
|
+
</div>
|
|
29
|
+
{/if}
|
|
30
|
+
<h3 class={slots.title()}>{title}</h3>
|
|
31
|
+
{#if description}
|
|
32
|
+
<p class={slots.description()}>{description}</p>
|
|
33
|
+
{/if}
|
|
34
|
+
{#if action}
|
|
35
|
+
<div class={slots.actionWrapper()}>
|
|
36
|
+
{@render action()}
|
|
37
|
+
</div>
|
|
38
|
+
{/if}
|
|
39
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import EmptyState from './EmptyState.svelte';
|
|
3
|
+
import type { EmptyStateProps } from '../../index.js';
|
|
4
|
+
|
|
5
|
+
type Props = Omit<EmptyStateProps, 'icon' | 'action'> & {
|
|
6
|
+
iconText?: string;
|
|
7
|
+
actionText?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
let { iconText, actionText, ...rest }: Props = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
{#snippet iconSnippet()}
|
|
14
|
+
<span data-testid="wrapper-icon">{iconText}</span>
|
|
15
|
+
{/snippet}
|
|
16
|
+
|
|
17
|
+
{#snippet actionSnippet()}
|
|
18
|
+
<button type="button" data-testid="wrapper-action">{actionText}</button>
|
|
19
|
+
{/snippet}
|
|
20
|
+
|
|
21
|
+
<EmptyState
|
|
22
|
+
{...rest}
|
|
23
|
+
icon={iconText ? iconSnippet : undefined}
|
|
24
|
+
action={actionText ? actionSnippet : undefined}
|
|
25
|
+
/>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EmptyStateProps } from '../../index.js';
|
|
2
|
+
type Props = Omit<EmptyStateProps, 'icon' | 'action'> & {
|
|
3
|
+
iconText?: string;
|
|
4
|
+
actionText?: string;
|
|
5
|
+
};
|
|
6
|
+
declare const EmptyStateTestWrapper: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type EmptyStateTestWrapper = ReturnType<typeof EmptyStateTestWrapper>;
|
|
8
|
+
export default EmptyStateTestWrapper;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ClassValue } from 'tailwind-variants';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
export type EmptyStateSize = 'sm' | 'base' | 'lg' | 'xl';
|
|
4
|
+
export type EmptyStateProps = {
|
|
5
|
+
title: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
size?: EmptyStateSize;
|
|
8
|
+
class?: ClassValue;
|
|
9
|
+
icon?: Snippet;
|
|
10
|
+
action?: Snippet;
|
|
11
|
+
testId?: string;
|
|
12
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|