@aiaiai-pt/design-system 0.4.0 → 0.4.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.
|
@@ -53,6 +53,10 @@
|
|
|
53
53
|
size = 'md',
|
|
54
54
|
/** @type {((value: string) => void) | undefined} */
|
|
55
55
|
onchange = undefined,
|
|
56
|
+
/** @type {((query: string) => void) | undefined} — async search; when set, disables internal filtering */
|
|
57
|
+
onsearch = undefined,
|
|
58
|
+
/** @type {boolean} */
|
|
59
|
+
loading = false,
|
|
56
60
|
/** @type {string} */
|
|
57
61
|
class: className = '',
|
|
58
62
|
...rest
|
|
@@ -76,8 +80,21 @@
|
|
|
76
80
|
// Selected item label for display
|
|
77
81
|
const selectedItem = $derived(items.find(i => i.value === value));
|
|
78
82
|
|
|
79
|
-
//
|
|
83
|
+
// Debounced onsearch dispatch
|
|
84
|
+
let _searchTimer = 0;
|
|
85
|
+
$effect(() => {
|
|
86
|
+
// Subscribe to query changes; only fire if onsearch is set
|
|
87
|
+
const q = query;
|
|
88
|
+
if (!onsearch) return;
|
|
89
|
+
clearTimeout(_searchTimer);
|
|
90
|
+
if (!q) return;
|
|
91
|
+
_searchTimer = /** @type {any} */ (setTimeout(() => onsearch(q), 300));
|
|
92
|
+
return () => clearTimeout(_searchTimer);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Filter items by query — skip when onsearch is set (parent controls items)
|
|
80
96
|
const filtered = $derived.by(() => {
|
|
97
|
+
if (onsearch) return items;
|
|
81
98
|
if (!query) return items;
|
|
82
99
|
const q = query.toLowerCase();
|
|
83
100
|
return items.filter(i =>
|
|
@@ -228,7 +245,11 @@
|
|
|
228
245
|
role="listbox"
|
|
229
246
|
aria-label={label ?? 'Options'}
|
|
230
247
|
>
|
|
231
|
-
{#if
|
|
248
|
+
{#if loading}
|
|
249
|
+
<li class="combobox-empty" role="option" aria-selected="false" aria-disabled="true">
|
|
250
|
+
Searching…
|
|
251
|
+
</li>
|
|
252
|
+
{:else if filtered.length === 0}
|
|
232
253
|
<li class="combobox-empty" role="option" aria-selected="false" aria-disabled="true">
|
|
233
254
|
No results found
|
|
234
255
|
</li>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
export default Combobox;
|
|
2
2
|
export type ComboboxItem = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
group?: string;
|
|
6
|
+
description?: string;
|
|
7
7
|
};
|
|
8
8
|
type Combobox = {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
10
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
13
13
|
* Combobox
|
|
@@ -34,7 +34,8 @@ type Combobox = {
|
|
|
34
34
|
* bind:value={selected}
|
|
35
35
|
* />
|
|
36
36
|
*/
|
|
37
|
-
declare const Combobox: import("svelte").Component<
|
|
37
|
+
declare const Combobox: import("svelte").Component<
|
|
38
|
+
{
|
|
38
39
|
items?: any[];
|
|
39
40
|
value?: string;
|
|
40
41
|
label?: any;
|
|
@@ -44,17 +45,26 @@ declare const Combobox: import("svelte").Component<{
|
|
|
44
45
|
help?: any;
|
|
45
46
|
size?: string;
|
|
46
47
|
onchange?: any;
|
|
48
|
+
/** Async search callback; when set, disables internal filtering. Parent must update `items` with results. */
|
|
49
|
+
onsearch?: (query: string) => void;
|
|
50
|
+
/** Show "Searching…" in dropdown while loading async results */
|
|
51
|
+
loading?: boolean;
|
|
47
52
|
class?: string;
|
|
48
|
-
} & Record<string, any>,
|
|
53
|
+
} & Record<string, any>,
|
|
54
|
+
{},
|
|
55
|
+
"value"
|
|
56
|
+
>;
|
|
49
57
|
type $$ComponentProps = {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
items?: any[];
|
|
59
|
+
value?: string;
|
|
60
|
+
label?: any;
|
|
61
|
+
placeholder?: string;
|
|
62
|
+
disabled?: boolean;
|
|
63
|
+
error?: any;
|
|
64
|
+
help?: any;
|
|
65
|
+
size?: string;
|
|
66
|
+
onchange?: any;
|
|
67
|
+
onsearch?: (query: string) => void;
|
|
68
|
+
loading?: boolean;
|
|
69
|
+
class?: string;
|
|
60
70
|
} & Record<string, any>;
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
@component Pagination
|
|
3
3
|
|
|
4
4
|
Cursor-based and offset-based pagination with page size selector.
|
|
5
|
-
|
|
5
|
+
Self-contained nav controls with correct icon placement (chevron
|
|
6
|
+
leading for PREV, trailing for NEXT).
|
|
6
7
|
|
|
7
8
|
@example Cursor mode (default)
|
|
8
9
|
<Pagination
|
|
@@ -27,7 +28,6 @@
|
|
|
27
28
|
-->
|
|
28
29
|
<script>
|
|
29
30
|
import Select from './Select.svelte';
|
|
30
|
-
import Button from './Button.svelte';
|
|
31
31
|
|
|
32
32
|
let {
|
|
33
33
|
/** @type {'cursor' | 'offset'} */
|
|
@@ -72,7 +72,6 @@
|
|
|
72
72
|
|
|
73
73
|
const page_size_value = $derived(String(page_size));
|
|
74
74
|
|
|
75
|
-
/** Compute visible page numbers with ellipsis for offset mode */
|
|
76
75
|
const page_numbers = $derived(
|
|
77
76
|
mode === 'offset' && total_pages
|
|
78
77
|
? build_pages(current_page, total_pages)
|
|
@@ -103,20 +102,29 @@
|
|
|
103
102
|
return Array.from({ length: total }, (_, i) => i + 1);
|
|
104
103
|
}
|
|
105
104
|
const pages = /** @type {(number | null)[]} */ ([]);
|
|
106
|
-
// Always show first
|
|
107
105
|
pages.push(1);
|
|
108
|
-
if (current > 3) pages.push(null);
|
|
109
|
-
// Window around current
|
|
106
|
+
if (current > 3) pages.push(null);
|
|
110
107
|
const win_start = Math.max(2, current - 1);
|
|
111
108
|
const win_end = Math.min(total - 1, current + 1);
|
|
112
109
|
for (let p = win_start; p <= win_end; p++) pages.push(p);
|
|
113
|
-
if (current < total - 2) pages.push(null);
|
|
114
|
-
// Always show last
|
|
110
|
+
if (current < total - 2) pages.push(null);
|
|
115
111
|
pages.push(total);
|
|
116
112
|
return pages;
|
|
117
113
|
}
|
|
118
114
|
</script>
|
|
119
115
|
|
|
116
|
+
{#snippet chevronLeft()}
|
|
117
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
118
|
+
<path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
119
|
+
</svg>
|
|
120
|
+
{/snippet}
|
|
121
|
+
|
|
122
|
+
{#snippet chevronRight()}
|
|
123
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
124
|
+
<path d="M6 12L10 8L6 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
125
|
+
</svg>
|
|
126
|
+
{/snippet}
|
|
127
|
+
|
|
120
128
|
<div class="pagination {className}" {...rest}>
|
|
121
129
|
{#if page_sizes && page_sizes.length > 0}
|
|
122
130
|
<div class="pagination-size">
|
|
@@ -137,51 +145,17 @@
|
|
|
137
145
|
<span class="pagination-spacer" aria-hidden="true"></span>
|
|
138
146
|
|
|
139
147
|
{#if mode === 'cursor'}
|
|
140
|
-
<
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
>
|
|
147
|
-
{#snippet icon()}
|
|
148
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
149
|
-
<path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
150
|
-
</svg>
|
|
151
|
-
{/snippet}
|
|
152
|
-
PREV
|
|
153
|
-
</Button>
|
|
154
|
-
|
|
155
|
-
<Button
|
|
156
|
-
variant="ghost"
|
|
157
|
-
size="sm"
|
|
158
|
-
disabled={!has_next}
|
|
159
|
-
onclick={on_next}
|
|
160
|
-
aria-label="Next page"
|
|
161
|
-
>
|
|
162
|
-
NEXT
|
|
163
|
-
{#snippet icon()}
|
|
164
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
165
|
-
<path d="M6 12L10 8L6 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
166
|
-
</svg>
|
|
167
|
-
{/snippet}
|
|
168
|
-
</Button>
|
|
148
|
+
<button class="pagination-nav" disabled={!has_prev} onclick={on_prev} aria-label="Previous page">
|
|
149
|
+
{@render chevronLeft()}<span>PREV</span>
|
|
150
|
+
</button>
|
|
151
|
+
<button class="pagination-nav" disabled={!has_next} onclick={on_next} aria-label="Next page">
|
|
152
|
+
<span>NEXT</span>{@render chevronRight()}
|
|
153
|
+
</button>
|
|
169
154
|
|
|
170
155
|
{:else}
|
|
171
|
-
<
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
disabled={current_page <= 1}
|
|
175
|
-
onclick={() => on_page_change?.(current_page - 1)}
|
|
176
|
-
aria-label="Previous page"
|
|
177
|
-
>
|
|
178
|
-
{#snippet icon()}
|
|
179
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
180
|
-
<path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
181
|
-
</svg>
|
|
182
|
-
{/snippet}
|
|
183
|
-
PREV
|
|
184
|
-
</Button>
|
|
156
|
+
<button class="pagination-nav" disabled={current_page <= 1} onclick={() => on_page_change?.(current_page - 1)} aria-label="Previous page">
|
|
157
|
+
{@render chevronLeft()}<span>PREV</span>
|
|
158
|
+
</button>
|
|
185
159
|
|
|
186
160
|
<div class="pagination-pages">
|
|
187
161
|
{#each page_numbers as p}
|
|
@@ -202,20 +176,9 @@
|
|
|
202
176
|
{/each}
|
|
203
177
|
</div>
|
|
204
178
|
|
|
205
|
-
<
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
disabled={current_page >= (total_pages ?? current_page)}
|
|
209
|
-
onclick={() => on_page_change?.(current_page + 1)}
|
|
210
|
-
aria-label="Next page"
|
|
211
|
-
>
|
|
212
|
-
NEXT
|
|
213
|
-
{#snippet icon()}
|
|
214
|
-
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
215
|
-
<path d="M6 12L10 8L6 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
216
|
-
</svg>
|
|
217
|
-
{/snippet}
|
|
218
|
-
</Button>
|
|
179
|
+
<button class="pagination-nav" disabled={current_page >= (total_pages ?? current_page)} onclick={() => on_page_change?.(current_page + 1)} aria-label="Next page">
|
|
180
|
+
<span>NEXT</span>{@render chevronRight()}
|
|
181
|
+
</button>
|
|
219
182
|
{/if}
|
|
220
183
|
</div>
|
|
221
184
|
|
|
@@ -228,7 +191,6 @@
|
|
|
228
191
|
}
|
|
229
192
|
|
|
230
193
|
.pagination-size {
|
|
231
|
-
/* Constrain size selector width */
|
|
232
194
|
width: 80px;
|
|
233
195
|
}
|
|
234
196
|
|
|
@@ -244,6 +206,44 @@
|
|
|
244
206
|
min-width: var(--space-sm);
|
|
245
207
|
}
|
|
246
208
|
|
|
209
|
+
/* ─── Nav buttons (PREV / NEXT) ─── */
|
|
210
|
+
.pagination-nav {
|
|
211
|
+
all: unset;
|
|
212
|
+
display: inline-flex;
|
|
213
|
+
align-items: center;
|
|
214
|
+
gap: var(--space-xs);
|
|
215
|
+
padding: var(--space-xs) var(--space-sm);
|
|
216
|
+
font-family: var(--type-label-font);
|
|
217
|
+
font-size: var(--type-label-size);
|
|
218
|
+
letter-spacing: var(--type-label-tracking);
|
|
219
|
+
color: var(--color-text-secondary);
|
|
220
|
+
cursor: pointer;
|
|
221
|
+
border-radius: var(--radius-sm);
|
|
222
|
+
transition: color var(--duration-instant) var(--easing-default),
|
|
223
|
+
background var(--duration-instant) var(--easing-default);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.pagination-nav:hover:not(:disabled) {
|
|
227
|
+
color: var(--color-text);
|
|
228
|
+
background: var(--color-surface-secondary);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.pagination-nav:disabled {
|
|
232
|
+
opacity: 0.4;
|
|
233
|
+
cursor: default;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.pagination-nav:focus-visible {
|
|
237
|
+
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
|
238
|
+
outline-offset: var(--focus-ring-offset);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.pagination-nav svg {
|
|
242
|
+
width: var(--icon-size-sm);
|
|
243
|
+
height: var(--icon-size-sm);
|
|
244
|
+
flex-shrink: 0;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
247
|
/* ─── Offset page numbers ─── */
|
|
248
248
|
.pagination-pages {
|
|
249
249
|
display: flex;
|