@aiaiai-pt/design-system 0.3.8 → 0.4.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/components/Breadcrumb.svelte +142 -0
- package/components/Breadcrumb.svelte.d.ts +38 -0
- package/components/DataTable.svelte +420 -0
- package/components/DataTable.svelte.d.ts +75 -0
- package/components/Pagination.svelte +299 -0
- package/components/Pagination.svelte.d.ts +66 -0
- package/components/index.d.ts +3 -0
- package/components/index.js +5 -0
- package/package.json +1 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component Breadcrumb
|
|
3
|
+
|
|
4
|
+
Navigation breadcrumb trail. Items with `href` are links; the last item
|
|
5
|
+
(current page, no href) is rendered as plain bold text.
|
|
6
|
+
|
|
7
|
+
@example
|
|
8
|
+
<Breadcrumb items={[
|
|
9
|
+
{ label: 'Home', href: '/' },
|
|
10
|
+
{ label: 'Settings', href: '/settings' },
|
|
11
|
+
{ label: 'Profile' },
|
|
12
|
+
]} />
|
|
13
|
+
|
|
14
|
+
@example With truncation
|
|
15
|
+
<Breadcrumb
|
|
16
|
+
items={deepPath}
|
|
17
|
+
max_items={4}
|
|
18
|
+
/>
|
|
19
|
+
-->
|
|
20
|
+
<script>
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {{ label: string, href?: string }} BreadcrumbItem
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
/** @type {BreadcrumbItem[]} */
|
|
27
|
+
items = [],
|
|
28
|
+
/** @type {number | undefined} */
|
|
29
|
+
max_items = undefined,
|
|
30
|
+
/** @type {string} */
|
|
31
|
+
class: className = '',
|
|
32
|
+
...rest
|
|
33
|
+
} = $props();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Truncate items when max_items is set.
|
|
37
|
+
* Keeps first item + ellipsis + last (max_items - 2) items.
|
|
38
|
+
* Minimum effective max_items is 3 (first + ellipsis + last).
|
|
39
|
+
* @type {(BreadcrumbItem | null)[]}
|
|
40
|
+
*/
|
|
41
|
+
const visible_items = $derived(
|
|
42
|
+
!max_items || items.length <= max_items
|
|
43
|
+
? items
|
|
44
|
+
: (() => {
|
|
45
|
+
const keep_end = Math.max(1, max_items - 2);
|
|
46
|
+
/** @type {(BreadcrumbItem | null)[]} */
|
|
47
|
+
const result = [items[0], null];
|
|
48
|
+
for (let i = items.length - keep_end; i < items.length; i++) {
|
|
49
|
+
result.push(items[i]);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
})()
|
|
53
|
+
);
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<nav class="breadcrumb {className}" aria-label="Breadcrumb" {...rest}>
|
|
57
|
+
<ol class="breadcrumb-list">
|
|
58
|
+
{#each visible_items as item, idx}
|
|
59
|
+
{#if item === null}
|
|
60
|
+
<li class="breadcrumb-item" aria-hidden="true">
|
|
61
|
+
<span class="breadcrumb-separator" aria-hidden="true">/</span>
|
|
62
|
+
<span class="breadcrumb-ellipsis">…</span>
|
|
63
|
+
</li>
|
|
64
|
+
{:else}
|
|
65
|
+
{#if idx > 0}
|
|
66
|
+
<li class="breadcrumb-sep-item" aria-hidden="true">
|
|
67
|
+
<span class="breadcrumb-separator">/</span>
|
|
68
|
+
</li>
|
|
69
|
+
{/if}
|
|
70
|
+
<li class="breadcrumb-item">
|
|
71
|
+
{#if item.href}
|
|
72
|
+
<a href={item.href} class="breadcrumb-link">{item.label}</a>
|
|
73
|
+
{:else}
|
|
74
|
+
<span class="breadcrumb-current" aria-current="page">{item.label}</span>
|
|
75
|
+
{/if}
|
|
76
|
+
</li>
|
|
77
|
+
{/if}
|
|
78
|
+
{/each}
|
|
79
|
+
</ol>
|
|
80
|
+
</nav>
|
|
81
|
+
|
|
82
|
+
<style>
|
|
83
|
+
.breadcrumb {
|
|
84
|
+
/* no outer constraints — caller controls width */
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.breadcrumb-list {
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
flex-wrap: wrap;
|
|
91
|
+
gap: 0;
|
|
92
|
+
list-style: none;
|
|
93
|
+
margin: 0;
|
|
94
|
+
padding: 0;
|
|
95
|
+
font-family: var(--type-body-sm-font);
|
|
96
|
+
font-size: var(--type-body-sm-size);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.breadcrumb-item {
|
|
100
|
+
display: inline-flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.breadcrumb-sep-item {
|
|
105
|
+
display: inline-flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.breadcrumb-separator {
|
|
110
|
+
margin: 0 var(--space-xs);
|
|
111
|
+
color: var(--color-text-muted);
|
|
112
|
+
user-select: none;
|
|
113
|
+
font-size: var(--type-body-sm-size);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.breadcrumb-link {
|
|
117
|
+
color: var(--color-text-secondary);
|
|
118
|
+
text-decoration: none;
|
|
119
|
+
transition: color var(--duration-instant) var(--easing-default);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.breadcrumb-link:hover {
|
|
123
|
+
color: var(--color-text);
|
|
124
|
+
text-decoration: underline;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.breadcrumb-link:focus-visible {
|
|
128
|
+
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
|
129
|
+
outline-offset: var(--focus-ring-offset);
|
|
130
|
+
border-radius: var(--radius-sm);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.breadcrumb-current {
|
|
134
|
+
color: var(--color-text);
|
|
135
|
+
font-weight: var(--raw-font-weight-semibold, 600);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.breadcrumb-ellipsis {
|
|
139
|
+
color: var(--color-text-muted);
|
|
140
|
+
user-select: none;
|
|
141
|
+
}
|
|
142
|
+
</style>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export default Breadcrumb;
|
|
2
|
+
type Breadcrumb = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Breadcrumb
|
|
8
|
+
*
|
|
9
|
+
* Navigation breadcrumb trail. Items with `href` are links; the last item
|
|
10
|
+
* (current page, no href) is rendered as plain bold text.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* <Breadcrumb items={[
|
|
14
|
+
* { label: 'Home', href: '/' },
|
|
15
|
+
* { label: 'Settings', href: '/settings' },
|
|
16
|
+
* { label: 'Profile' },
|
|
17
|
+
* ]} />
|
|
18
|
+
*
|
|
19
|
+
* @example With truncation
|
|
20
|
+
* <Breadcrumb
|
|
21
|
+
* items={deepPath}
|
|
22
|
+
* max_items={4}
|
|
23
|
+
* />
|
|
24
|
+
*/
|
|
25
|
+
declare const Breadcrumb: import("svelte").Component<
|
|
26
|
+
{
|
|
27
|
+
items?: any[];
|
|
28
|
+
max_items?: any;
|
|
29
|
+
class?: string;
|
|
30
|
+
} & Record<string, any>,
|
|
31
|
+
{},
|
|
32
|
+
""
|
|
33
|
+
>;
|
|
34
|
+
type $$ComponentProps = {
|
|
35
|
+
items?: any[];
|
|
36
|
+
max_items?: any;
|
|
37
|
+
class?: string;
|
|
38
|
+
} & Record<string, any>;
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component DataTable
|
|
3
|
+
|
|
4
|
+
Data table with sortable columns, row selection, loading and empty states.
|
|
5
|
+
Composes Skeleton and EmptyState primitives. Consumes --table-* tokens from
|
|
6
|
+
components.css, falling back to semantic tokens.
|
|
7
|
+
|
|
8
|
+
@example Basic
|
|
9
|
+
<DataTable
|
|
10
|
+
columns={[
|
|
11
|
+
{ key: 'name', label: 'NAME' },
|
|
12
|
+
{ key: 'status', label: 'STATUS', width: '120px' },
|
|
13
|
+
]}
|
|
14
|
+
rows={items}
|
|
15
|
+
on_sort={(key, dir) => sort(key, dir)}
|
|
16
|
+
on_row_click={(row) => goto(`/items/${row.id}`)}
|
|
17
|
+
/>
|
|
18
|
+
|
|
19
|
+
@example Selectable
|
|
20
|
+
<DataTable
|
|
21
|
+
{columns}
|
|
22
|
+
{rows}
|
|
23
|
+
selectable
|
|
24
|
+
bind:selected_rows={selected}
|
|
25
|
+
on_select={(sel) => console.log(sel)}
|
|
26
|
+
/>
|
|
27
|
+
|
|
28
|
+
@example Loading
|
|
29
|
+
<DataTable {columns} rows={[]} loading />
|
|
30
|
+
-->
|
|
31
|
+
<script>
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {{ key: string, label: string, width?: string, sortable?: boolean, render?: (value: unknown, row: Record<string, unknown>) => string }} ColumnDef
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import Skeleton from './Skeleton.svelte';
|
|
37
|
+
import EmptyState from './EmptyState.svelte';
|
|
38
|
+
|
|
39
|
+
let {
|
|
40
|
+
/** @type {ColumnDef[]} */
|
|
41
|
+
columns = [],
|
|
42
|
+
/** @type {Record<string, unknown>[]} */
|
|
43
|
+
rows = [],
|
|
44
|
+
/** @type {boolean} */
|
|
45
|
+
loading = false,
|
|
46
|
+
/** @type {boolean} */
|
|
47
|
+
sortable = true,
|
|
48
|
+
/** @type {string | undefined} */
|
|
49
|
+
sort_key = undefined,
|
|
50
|
+
/** @type {'asc' | 'desc'} */
|
|
51
|
+
sort_direction = 'asc',
|
|
52
|
+
/** @type {boolean} */
|
|
53
|
+
selectable = false,
|
|
54
|
+
/** @type {Set<string>} */
|
|
55
|
+
selected_rows = $bindable(new Set()),
|
|
56
|
+
/** @type {string} */
|
|
57
|
+
row_key = 'id',
|
|
58
|
+
/** @type {string} */
|
|
59
|
+
empty_heading = 'No data',
|
|
60
|
+
/** @type {string} */
|
|
61
|
+
empty_body = 'No items to display',
|
|
62
|
+
/** @type {((key: string, direction: 'asc' | 'desc') => void) | undefined} */
|
|
63
|
+
on_sort = undefined,
|
|
64
|
+
/** @type {((selected: Set<string>) => void) | undefined} */
|
|
65
|
+
on_select = undefined,
|
|
66
|
+
/** @type {((row: Record<string, unknown>) => void) | undefined} */
|
|
67
|
+
on_row_click = undefined,
|
|
68
|
+
/** @type {import('svelte').Snippet | undefined} */
|
|
69
|
+
children = undefined,
|
|
70
|
+
/** @type {string} */
|
|
71
|
+
class: className = '',
|
|
72
|
+
...rest
|
|
73
|
+
} = $props();
|
|
74
|
+
|
|
75
|
+
const SKELETON_ROWS = 5;
|
|
76
|
+
|
|
77
|
+
const all_selected = $derived(
|
|
78
|
+
rows.length > 0 &&
|
|
79
|
+
rows.every((row) => selected_rows.has(String(row[row_key])))
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const some_selected = $derived(
|
|
83
|
+
rows.some((row) => selected_rows.has(String(row[row_key]))) && !all_selected
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {string} key
|
|
88
|
+
*/
|
|
89
|
+
function handle_header_click(key) {
|
|
90
|
+
const col = columns.find((c) => c.key === key);
|
|
91
|
+
const col_sortable = col?.sortable ?? sortable;
|
|
92
|
+
if (!col_sortable || !on_sort) return;
|
|
93
|
+
|
|
94
|
+
const next_dir = sort_key === key && sort_direction === 'asc' ? 'desc' : 'asc';
|
|
95
|
+
on_sort(key, next_dir);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** @param {string} row_id */
|
|
99
|
+
function handle_row_check(row_id) {
|
|
100
|
+
const next = new Set(selected_rows);
|
|
101
|
+
if (next.has(row_id)) {
|
|
102
|
+
next.delete(row_id);
|
|
103
|
+
} else {
|
|
104
|
+
next.add(row_id);
|
|
105
|
+
}
|
|
106
|
+
selected_rows = next;
|
|
107
|
+
on_select?.(next);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function handle_select_all() {
|
|
111
|
+
let next;
|
|
112
|
+
if (all_selected) {
|
|
113
|
+
next = new Set(/** @type {Set<string>} */ (selected_rows));
|
|
114
|
+
for (const row of rows) {
|
|
115
|
+
next.delete(String(row[row_key]));
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
next = new Set(/** @type {Set<string>} */ (selected_rows));
|
|
119
|
+
for (const row of rows) {
|
|
120
|
+
next.add(String(row[row_key]));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
selected_rows = next;
|
|
124
|
+
on_select?.(next);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @param {ColumnDef} col
|
|
129
|
+
* @param {unknown} value
|
|
130
|
+
* @param {Record<string, unknown>} row
|
|
131
|
+
*/
|
|
132
|
+
function render_cell(col, value, row) {
|
|
133
|
+
if (col.render) return col.render(value, row);
|
|
134
|
+
if (value === null || value === undefined) return '';
|
|
135
|
+
return String(value);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {string} key
|
|
140
|
+
*/
|
|
141
|
+
function is_col_sortable(key) {
|
|
142
|
+
const col = columns.find((c) => c.key === key);
|
|
143
|
+
return col?.sortable ?? sortable;
|
|
144
|
+
}
|
|
145
|
+
</script>
|
|
146
|
+
|
|
147
|
+
<div class="table-wrap {className}" {...rest}>
|
|
148
|
+
{#if children}
|
|
149
|
+
<div class="table-toolbar">
|
|
150
|
+
{@render children()}
|
|
151
|
+
</div>
|
|
152
|
+
{/if}
|
|
153
|
+
|
|
154
|
+
<div class="table-scroll">
|
|
155
|
+
<table class="table">
|
|
156
|
+
<thead class="table-head">
|
|
157
|
+
<tr>
|
|
158
|
+
{#if selectable}
|
|
159
|
+
<th class="table-th table-th-check" scope="col">
|
|
160
|
+
<input
|
|
161
|
+
type="checkbox"
|
|
162
|
+
class="table-checkbox"
|
|
163
|
+
checked={all_selected}
|
|
164
|
+
indeterminate={some_selected}
|
|
165
|
+
disabled={loading}
|
|
166
|
+
aria-label="Select all rows"
|
|
167
|
+
onchange={handle_select_all}
|
|
168
|
+
/>
|
|
169
|
+
</th>
|
|
170
|
+
{/if}
|
|
171
|
+
{#each columns as col}
|
|
172
|
+
{@const col_sort = is_col_sortable(col.key)}
|
|
173
|
+
<th
|
|
174
|
+
class="table-th"
|
|
175
|
+
class:table-th-sortable={col_sort && !!on_sort}
|
|
176
|
+
scope="col"
|
|
177
|
+
style:width={col.width ?? undefined}
|
|
178
|
+
onclick={col_sort && on_sort ? () => handle_header_click(col.key) : undefined}
|
|
179
|
+
aria-sort={!col_sort ? undefined : sort_key === col.key ? (sort_direction === 'asc' ? 'ascending' : 'descending') : 'none'}
|
|
180
|
+
>
|
|
181
|
+
<span class="table-th-inner">
|
|
182
|
+
{col.label}
|
|
183
|
+
{#if col_sort && on_sort}
|
|
184
|
+
<span class="table-sort-icon" aria-hidden="true">
|
|
185
|
+
{#if sort_key === col.key}
|
|
186
|
+
{#if sort_direction === 'asc'}
|
|
187
|
+
<svg width="10" height="10" viewBox="0 0 10 10" fill="none">
|
|
188
|
+
<path d="M5 2L9 8H1L5 2Z" fill="currentColor"/>
|
|
189
|
+
</svg>
|
|
190
|
+
{:else}
|
|
191
|
+
<svg width="10" height="10" viewBox="0 0 10 10" fill="none">
|
|
192
|
+
<path d="M5 8L1 2H9L5 8Z" fill="currentColor"/>
|
|
193
|
+
</svg>
|
|
194
|
+
{/if}
|
|
195
|
+
{:else}
|
|
196
|
+
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" style="opacity: 0.3">
|
|
197
|
+
<path d="M5 2L9 5H1L5 2Z" fill="currentColor"/>
|
|
198
|
+
<path d="M5 8L1 5H9L5 8Z" fill="currentColor"/>
|
|
199
|
+
</svg>
|
|
200
|
+
{/if}
|
|
201
|
+
</span>
|
|
202
|
+
{/if}
|
|
203
|
+
</span>
|
|
204
|
+
</th>
|
|
205
|
+
{/each}
|
|
206
|
+
</tr>
|
|
207
|
+
</thead>
|
|
208
|
+
|
|
209
|
+
<tbody class="table-body">
|
|
210
|
+
{#if loading}
|
|
211
|
+
{#each { length: SKELETON_ROWS } as _, i}
|
|
212
|
+
<tr class="table-row" aria-hidden="true">
|
|
213
|
+
{#if selectable}
|
|
214
|
+
<td class="table-td table-td-check">
|
|
215
|
+
<Skeleton width="16px" height="16px" radius="var(--radius-sm)" />
|
|
216
|
+
</td>
|
|
217
|
+
{/if}
|
|
218
|
+
{#each columns as col}
|
|
219
|
+
<td class="table-td">
|
|
220
|
+
<Skeleton width={i % 3 === 0 ? '60%' : i % 3 === 1 ? '80%' : '70%'} height="14px" />
|
|
221
|
+
</td>
|
|
222
|
+
{/each}
|
|
223
|
+
</tr>
|
|
224
|
+
{/each}
|
|
225
|
+
{:else if rows.length === 0}
|
|
226
|
+
<tr>
|
|
227
|
+
<td colspan={selectable ? columns.length + 1 : columns.length} class="table-td-empty">
|
|
228
|
+
<EmptyState heading={empty_heading} body={empty_body} />
|
|
229
|
+
</td>
|
|
230
|
+
</tr>
|
|
231
|
+
{:else}
|
|
232
|
+
{#each rows as row, row_index}
|
|
233
|
+
{@const row_id = String(row[row_key])}
|
|
234
|
+
{@const is_selected = selected_rows.has(row_id)}
|
|
235
|
+
<tr
|
|
236
|
+
class="table-row"
|
|
237
|
+
class:table-row-even={row_index % 2 === 1}
|
|
238
|
+
class:table-row-selected={is_selected}
|
|
239
|
+
class:table-row-clickable={!!on_row_click}
|
|
240
|
+
onclick={on_row_click ? () => on_row_click(row) : undefined}
|
|
241
|
+
tabindex={on_row_click ? 0 : undefined}
|
|
242
|
+
onkeydown={on_row_click ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); on_row_click(row); } } : undefined}
|
|
243
|
+
>
|
|
244
|
+
{#if selectable}
|
|
245
|
+
<td
|
|
246
|
+
class="table-td table-td-check"
|
|
247
|
+
onclick={(e) => { e.stopPropagation(); handle_row_check(row_id); }}
|
|
248
|
+
>
|
|
249
|
+
<input
|
|
250
|
+
type="checkbox"
|
|
251
|
+
class="table-checkbox"
|
|
252
|
+
checked={is_selected}
|
|
253
|
+
aria-label="Select row"
|
|
254
|
+
onchange={() => handle_row_check(row_id)}
|
|
255
|
+
onclick={(e) => e.stopPropagation()}
|
|
256
|
+
/>
|
|
257
|
+
</td>
|
|
258
|
+
{/if}
|
|
259
|
+
{#each columns as col}
|
|
260
|
+
<td class="table-td">
|
|
261
|
+
{render_cell(col, row[col.key], row)}
|
|
262
|
+
</td>
|
|
263
|
+
{/each}
|
|
264
|
+
</tr>
|
|
265
|
+
{/each}
|
|
266
|
+
{/if}
|
|
267
|
+
</tbody>
|
|
268
|
+
</table>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<style>
|
|
273
|
+
.table-wrap {
|
|
274
|
+
width: 100%;
|
|
275
|
+
display: flex;
|
|
276
|
+
flex-direction: column;
|
|
277
|
+
border: var(--elevation-border);
|
|
278
|
+
border-radius: var(--radius-md);
|
|
279
|
+
overflow: hidden;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.table-toolbar {
|
|
283
|
+
padding: var(--space-sm) var(--space-md);
|
|
284
|
+
border-bottom: var(--elevation-border);
|
|
285
|
+
background: var(--color-surface);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.table-scroll {
|
|
289
|
+
width: 100%;
|
|
290
|
+
overflow: auto;
|
|
291
|
+
/* When a max-height is set on .table-wrap, this enables sticky thead. */
|
|
292
|
+
max-height: inherit;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.table {
|
|
296
|
+
width: 100%;
|
|
297
|
+
border-collapse: separate;
|
|
298
|
+
border-spacing: 0;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/* ─── Head ───
|
|
302
|
+
Sticky headers require the consumer to set a fixed height on .table-wrap
|
|
303
|
+
or a scroll-owning ancestor. Without a constrained height the header will
|
|
304
|
+
simply sit at the top of the table and not "stick" because this scroll
|
|
305
|
+
container owns the overflow. */
|
|
306
|
+
.table-head {
|
|
307
|
+
position: sticky;
|
|
308
|
+
top: 0;
|
|
309
|
+
z-index: 1;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.table-th {
|
|
313
|
+
background: var(--color-surface-tertiary);
|
|
314
|
+
font-family: var(--type-label-font);
|
|
315
|
+
font-size: var(--type-label-size);
|
|
316
|
+
letter-spacing: var(--type-label-tracking);
|
|
317
|
+
color: var(--color-text-secondary);
|
|
318
|
+
font-weight: var(--raw-font-weight-regular, 400);
|
|
319
|
+
text-align: left;
|
|
320
|
+
padding: var(--space-sm) var(--space-md);
|
|
321
|
+
white-space: nowrap;
|
|
322
|
+
user-select: none;
|
|
323
|
+
border-bottom: var(--elevation-border);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.table-th-sortable {
|
|
327
|
+
cursor: pointer;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.table-th-sortable:hover {
|
|
331
|
+
color: var(--color-text);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.table-th-check {
|
|
335
|
+
width: 40px;
|
|
336
|
+
padding-left: var(--space-md);
|
|
337
|
+
padding-right: var(--space-sm);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.table-th-inner {
|
|
341
|
+
display: inline-flex;
|
|
342
|
+
align-items: center;
|
|
343
|
+
gap: var(--space-xs);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.table-sort-icon {
|
|
347
|
+
display: inline-flex;
|
|
348
|
+
align-items: center;
|
|
349
|
+
color: var(--color-text-muted);
|
|
350
|
+
flex-shrink: 0;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.table-th-sortable:hover .table-sort-icon {
|
|
354
|
+
color: var(--color-text-secondary);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* ─── Body ─── */
|
|
358
|
+
.table-body {
|
|
359
|
+
background: var(--color-surface);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.table-row {
|
|
363
|
+
transition: background var(--duration-instant) var(--easing-default);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.table-row-even {
|
|
367
|
+
background: var(--color-surface-secondary);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.table-row:hover {
|
|
371
|
+
background: var(--color-surface-tertiary);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.table-row-selected {
|
|
375
|
+
background: var(--color-accent-subtle) !important;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.table-row-clickable {
|
|
379
|
+
cursor: pointer;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.table-row-clickable:focus-visible {
|
|
383
|
+
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
|
384
|
+
outline-offset: -2px;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.table-td {
|
|
388
|
+
font-family: var(--type-data-font);
|
|
389
|
+
font-size: var(--type-data-size);
|
|
390
|
+
color: var(--color-text);
|
|
391
|
+
padding: var(--space-sm) var(--space-md);
|
|
392
|
+
border-bottom: var(--elevation-border);
|
|
393
|
+
vertical-align: middle;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/* Remove border from last row */
|
|
397
|
+
.table-body tr:last-child .table-td {
|
|
398
|
+
border-bottom: none;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.table-td-check {
|
|
402
|
+
width: 40px;
|
|
403
|
+
padding-left: var(--space-md);
|
|
404
|
+
padding-right: var(--space-sm);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.table-td-empty {
|
|
408
|
+
padding: 0;
|
|
409
|
+
border-bottom: none;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/* ─── Checkbox ─── */
|
|
413
|
+
.table-checkbox {
|
|
414
|
+
width: var(--checkbox-size);
|
|
415
|
+
height: var(--checkbox-size);
|
|
416
|
+
accent-color: var(--color-accent);
|
|
417
|
+
cursor: pointer;
|
|
418
|
+
display: block;
|
|
419
|
+
}
|
|
420
|
+
</style>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export default DataTable;
|
|
2
|
+
type DataTable = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* DataTable
|
|
8
|
+
*
|
|
9
|
+
* Data table with sortable columns, row selection, loading and empty states.
|
|
10
|
+
* Composes Skeleton and EmptyState primitives. Consumes --table-* tokens from
|
|
11
|
+
* components.css, falling back to semantic tokens.
|
|
12
|
+
*
|
|
13
|
+
* @example Basic
|
|
14
|
+
* <DataTable
|
|
15
|
+
* columns={[
|
|
16
|
+
* { key: 'name', label: 'NAME' },
|
|
17
|
+
* { key: 'status', label: 'STATUS', width: '120px' },
|
|
18
|
+
* ]}
|
|
19
|
+
* rows={items}
|
|
20
|
+
* on_sort={(key, dir) => sort(key, dir)}
|
|
21
|
+
* on_row_click={(row) => goto(`/items/${row.id}`)}
|
|
22
|
+
* />
|
|
23
|
+
*
|
|
24
|
+
* @example Selectable
|
|
25
|
+
* <DataTable
|
|
26
|
+
* {columns}
|
|
27
|
+
* {rows}
|
|
28
|
+
* selectable
|
|
29
|
+
* bind:selected_rows={selected}
|
|
30
|
+
* on_select={(sel) => console.log(sel)}
|
|
31
|
+
* />
|
|
32
|
+
*
|
|
33
|
+
* @example Loading
|
|
34
|
+
* <DataTable {columns} rows={[]} loading />
|
|
35
|
+
*/
|
|
36
|
+
declare const DataTable: import("svelte").Component<
|
|
37
|
+
{
|
|
38
|
+
columns?: any[];
|
|
39
|
+
rows?: any[];
|
|
40
|
+
loading?: boolean;
|
|
41
|
+
sortable?: boolean;
|
|
42
|
+
sort_key?: any;
|
|
43
|
+
sort_direction?: string;
|
|
44
|
+
selectable?: boolean;
|
|
45
|
+
selected_rows?: Set<string>;
|
|
46
|
+
row_key?: string;
|
|
47
|
+
empty_heading?: string;
|
|
48
|
+
empty_body?: string;
|
|
49
|
+
on_sort?: any;
|
|
50
|
+
on_select?: any;
|
|
51
|
+
on_row_click?: any;
|
|
52
|
+
children?: any;
|
|
53
|
+
class?: string;
|
|
54
|
+
} & Record<string, any>,
|
|
55
|
+
{},
|
|
56
|
+
"selected_rows"
|
|
57
|
+
>;
|
|
58
|
+
type $$ComponentProps = {
|
|
59
|
+
columns?: any[];
|
|
60
|
+
rows?: any[];
|
|
61
|
+
loading?: boolean;
|
|
62
|
+
sortable?: boolean;
|
|
63
|
+
sort_key?: any;
|
|
64
|
+
sort_direction?: string;
|
|
65
|
+
selectable?: boolean;
|
|
66
|
+
selected_rows?: Set<string>;
|
|
67
|
+
row_key?: string;
|
|
68
|
+
empty_heading?: string;
|
|
69
|
+
empty_body?: string;
|
|
70
|
+
on_sort?: any;
|
|
71
|
+
on_select?: any;
|
|
72
|
+
on_row_click?: any;
|
|
73
|
+
children?: any;
|
|
74
|
+
class?: string;
|
|
75
|
+
} & Record<string, any>;
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component Pagination
|
|
3
|
+
|
|
4
|
+
Cursor-based and offset-based pagination with page size selector.
|
|
5
|
+
Self-contained nav controls with correct icon placement (chevron
|
|
6
|
+
leading for PREV, trailing for NEXT).
|
|
7
|
+
|
|
8
|
+
@example Cursor mode (default)
|
|
9
|
+
<Pagination
|
|
10
|
+
has_next={page.has_next}
|
|
11
|
+
has_prev={page.has_prev}
|
|
12
|
+
on_next={() => loadNext()}
|
|
13
|
+
on_prev={() => loadPrev()}
|
|
14
|
+
total_items={page.count}
|
|
15
|
+
page_size={25}
|
|
16
|
+
/>
|
|
17
|
+
|
|
18
|
+
@example Offset mode
|
|
19
|
+
<Pagination
|
|
20
|
+
mode="offset"
|
|
21
|
+
current_page={page}
|
|
22
|
+
total_pages={totalPages}
|
|
23
|
+
total_items={totalItems}
|
|
24
|
+
on_page_change={(p) => setPage(p)}
|
|
25
|
+
page_sizes={[10, 25, 50, 100]}
|
|
26
|
+
on_page_size_change={(s) => setSize(s)}
|
|
27
|
+
/>
|
|
28
|
+
-->
|
|
29
|
+
<script>
|
|
30
|
+
import Select from './Select.svelte';
|
|
31
|
+
|
|
32
|
+
let {
|
|
33
|
+
/** @type {'cursor' | 'offset'} */
|
|
34
|
+
mode = 'cursor',
|
|
35
|
+
|
|
36
|
+
/* Cursor mode */
|
|
37
|
+
/** @type {boolean} */
|
|
38
|
+
has_next = false,
|
|
39
|
+
/** @type {boolean} */
|
|
40
|
+
has_prev = false,
|
|
41
|
+
/** @type {(() => void) | undefined} */
|
|
42
|
+
on_next = undefined,
|
|
43
|
+
/** @type {(() => void) | undefined} */
|
|
44
|
+
on_prev = undefined,
|
|
45
|
+
|
|
46
|
+
/* Offset mode */
|
|
47
|
+
/** @type {number} */
|
|
48
|
+
current_page = 1,
|
|
49
|
+
/** @type {number | undefined} */
|
|
50
|
+
total_pages = undefined,
|
|
51
|
+
/** @type {number | undefined} */
|
|
52
|
+
total_items = undefined,
|
|
53
|
+
/** @type {((page: number) => void) | undefined} */
|
|
54
|
+
on_page_change = undefined,
|
|
55
|
+
|
|
56
|
+
/* Shared */
|
|
57
|
+
/** @type {number} */
|
|
58
|
+
page_size = 25,
|
|
59
|
+
/** @type {number[] | undefined} */
|
|
60
|
+
page_sizes = undefined,
|
|
61
|
+
/** @type {((size: number) => void) | undefined} */
|
|
62
|
+
on_page_size_change = undefined,
|
|
63
|
+
|
|
64
|
+
/** @type {string} */
|
|
65
|
+
class: className = '',
|
|
66
|
+
...rest
|
|
67
|
+
} = $props();
|
|
68
|
+
|
|
69
|
+
const page_size_options = $derived(
|
|
70
|
+
(page_sizes ?? []).map((s) => ({ value: String(s), label: String(s) }))
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const page_size_value = $derived(String(page_size));
|
|
74
|
+
|
|
75
|
+
const page_numbers = $derived(
|
|
76
|
+
mode === 'offset' && total_pages
|
|
77
|
+
? build_pages(current_page, total_pages)
|
|
78
|
+
: /** @type {(number | null)[]} */ ([])
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const item_range_text = $derived(
|
|
82
|
+
total_items === undefined
|
|
83
|
+
? ''
|
|
84
|
+
: mode === 'cursor'
|
|
85
|
+
? `${total_items.toLocaleString()} total`
|
|
86
|
+
: (() => {
|
|
87
|
+
const start = (current_page - 1) * page_size + 1;
|
|
88
|
+
const end = Math.min(current_page * page_size, total_items);
|
|
89
|
+
return start > total_items
|
|
90
|
+
? `${total_items.toLocaleString()} total`
|
|
91
|
+
: `${start}–${end} of ${total_items.toLocaleString()}`;
|
|
92
|
+
})()
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @param {number} current
|
|
97
|
+
* @param {number} total
|
|
98
|
+
* @returns {(number | null)[]}
|
|
99
|
+
*/
|
|
100
|
+
function build_pages(current, total) {
|
|
101
|
+
if (total <= 7) {
|
|
102
|
+
return Array.from({ length: total }, (_, i) => i + 1);
|
|
103
|
+
}
|
|
104
|
+
const pages = /** @type {(number | null)[]} */ ([]);
|
|
105
|
+
pages.push(1);
|
|
106
|
+
if (current > 3) pages.push(null);
|
|
107
|
+
const win_start = Math.max(2, current - 1);
|
|
108
|
+
const win_end = Math.min(total - 1, current + 1);
|
|
109
|
+
for (let p = win_start; p <= win_end; p++) pages.push(p);
|
|
110
|
+
if (current < total - 2) pages.push(null);
|
|
111
|
+
pages.push(total);
|
|
112
|
+
return pages;
|
|
113
|
+
}
|
|
114
|
+
</script>
|
|
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
|
+
|
|
128
|
+
<div class="pagination {className}" {...rest}>
|
|
129
|
+
{#if page_sizes && page_sizes.length > 0}
|
|
130
|
+
<div class="pagination-size">
|
|
131
|
+
<Select
|
|
132
|
+
size="sm"
|
|
133
|
+
value={page_size_value}
|
|
134
|
+
options={page_size_options}
|
|
135
|
+
onchange={(val) => on_page_size_change?.(Number(val))}
|
|
136
|
+
aria-label="Rows per page"
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
{/if}
|
|
140
|
+
|
|
141
|
+
{#if total_items !== undefined}
|
|
142
|
+
<span class="pagination-info">{item_range_text}</span>
|
|
143
|
+
{/if}
|
|
144
|
+
|
|
145
|
+
<span class="pagination-spacer" aria-hidden="true"></span>
|
|
146
|
+
|
|
147
|
+
{#if mode === 'cursor'}
|
|
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>
|
|
154
|
+
|
|
155
|
+
{:else}
|
|
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>
|
|
159
|
+
|
|
160
|
+
<div class="pagination-pages">
|
|
161
|
+
{#each page_numbers as p}
|
|
162
|
+
{#if p === null}
|
|
163
|
+
<span class="pagination-ellipsis" aria-hidden="true">…</span>
|
|
164
|
+
{:else}
|
|
165
|
+
<button
|
|
166
|
+
type="button"
|
|
167
|
+
class="pagination-page"
|
|
168
|
+
class:pagination-page-active={p === current_page}
|
|
169
|
+
onclick={() => on_page_change?.(/** @type {number} */ (p))}
|
|
170
|
+
aria-label={`Page ${p}`}
|
|
171
|
+
aria-current={p === current_page ? 'page' : undefined}
|
|
172
|
+
>
|
|
173
|
+
{p}
|
|
174
|
+
</button>
|
|
175
|
+
{/if}
|
|
176
|
+
{/each}
|
|
177
|
+
</div>
|
|
178
|
+
|
|
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>
|
|
182
|
+
{/if}
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<style>
|
|
186
|
+
.pagination {
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
gap: var(--space-sm);
|
|
190
|
+
flex-wrap: wrap;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.pagination-size {
|
|
194
|
+
width: 80px;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.pagination-info {
|
|
198
|
+
font-family: var(--type-body-sm-font);
|
|
199
|
+
font-size: var(--type-body-sm-size);
|
|
200
|
+
color: var(--color-text-muted);
|
|
201
|
+
white-space: nowrap;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.pagination-spacer {
|
|
205
|
+
flex: 1;
|
|
206
|
+
min-width: var(--space-sm);
|
|
207
|
+
}
|
|
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
|
+
/* ─── Offset page numbers ─── */
|
|
248
|
+
.pagination-pages {
|
|
249
|
+
display: flex;
|
|
250
|
+
align-items: center;
|
|
251
|
+
gap: var(--space-2xs);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.pagination-page {
|
|
255
|
+
display: inline-flex;
|
|
256
|
+
align-items: center;
|
|
257
|
+
justify-content: center;
|
|
258
|
+
min-width: 28px;
|
|
259
|
+
height: 28px;
|
|
260
|
+
padding: 0 var(--space-xs);
|
|
261
|
+
font-family: var(--type-label-font);
|
|
262
|
+
font-size: var(--type-label-size);
|
|
263
|
+
letter-spacing: var(--type-label-tracking);
|
|
264
|
+
color: var(--color-text-secondary);
|
|
265
|
+
background: transparent;
|
|
266
|
+
border: none;
|
|
267
|
+
border-radius: var(--radius-sm);
|
|
268
|
+
cursor: pointer;
|
|
269
|
+
transition: background var(--duration-instant) var(--easing-default);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.pagination-page:hover:not(.pagination-page-active) {
|
|
273
|
+
background: var(--color-surface-secondary);
|
|
274
|
+
color: var(--color-text);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.pagination-page:focus-visible {
|
|
278
|
+
outline: var(--focus-ring-width) solid var(--focus-ring-color);
|
|
279
|
+
outline-offset: var(--focus-ring-offset);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.pagination-page-active {
|
|
283
|
+
background: var(--color-accent);
|
|
284
|
+
color: var(--color-text-on-accent);
|
|
285
|
+
cursor: default;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.pagination-ellipsis {
|
|
289
|
+
display: inline-flex;
|
|
290
|
+
align-items: center;
|
|
291
|
+
justify-content: center;
|
|
292
|
+
min-width: 28px;
|
|
293
|
+
height: 28px;
|
|
294
|
+
font-family: var(--type-label-font);
|
|
295
|
+
font-size: var(--type-label-size);
|
|
296
|
+
color: var(--color-text-muted);
|
|
297
|
+
user-select: none;
|
|
298
|
+
}
|
|
299
|
+
</style>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export default Pagination;
|
|
2
|
+
type Pagination = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Pagination
|
|
8
|
+
*
|
|
9
|
+
* Cursor-based and offset-based pagination with page size selector.
|
|
10
|
+
* Consumes --pagination-* tokens (falls back to semantic tokens).
|
|
11
|
+
*
|
|
12
|
+
* @example Cursor mode (default)
|
|
13
|
+
* <Pagination
|
|
14
|
+
* has_next={page.has_next}
|
|
15
|
+
* has_prev={page.has_prev}
|
|
16
|
+
* on_next={() => loadNext()}
|
|
17
|
+
* on_prev={() => loadPrev()}
|
|
18
|
+
* total_items={page.count}
|
|
19
|
+
* page_size={25}
|
|
20
|
+
* />
|
|
21
|
+
*
|
|
22
|
+
* @example Offset mode
|
|
23
|
+
* <Pagination
|
|
24
|
+
* mode="offset"
|
|
25
|
+
* current_page={page}
|
|
26
|
+
* total_pages={totalPages}
|
|
27
|
+
* total_items={totalItems}
|
|
28
|
+
* on_page_change={(p) => setPage(p)}
|
|
29
|
+
* page_sizes={[10, 25, 50, 100]}
|
|
30
|
+
* on_page_size_change={(s) => setSize(s)}
|
|
31
|
+
* />
|
|
32
|
+
*/
|
|
33
|
+
declare const Pagination: import("svelte").Component<
|
|
34
|
+
{
|
|
35
|
+
mode?: string;
|
|
36
|
+
has_next?: boolean;
|
|
37
|
+
has_prev?: boolean;
|
|
38
|
+
on_next?: any;
|
|
39
|
+
on_prev?: any;
|
|
40
|
+
current_page?: number;
|
|
41
|
+
total_pages?: any;
|
|
42
|
+
total_items?: any;
|
|
43
|
+
on_page_change?: any;
|
|
44
|
+
page_size?: number;
|
|
45
|
+
page_sizes?: number[];
|
|
46
|
+
on_page_size_change?: any;
|
|
47
|
+
class?: string;
|
|
48
|
+
} & Record<string, any>,
|
|
49
|
+
{},
|
|
50
|
+
""
|
|
51
|
+
>;
|
|
52
|
+
type $$ComponentProps = {
|
|
53
|
+
mode?: string;
|
|
54
|
+
has_next?: boolean;
|
|
55
|
+
has_prev?: boolean;
|
|
56
|
+
on_next?: any;
|
|
57
|
+
on_prev?: any;
|
|
58
|
+
current_page?: number;
|
|
59
|
+
total_pages?: any;
|
|
60
|
+
total_items?: any;
|
|
61
|
+
on_page_change?: any;
|
|
62
|
+
page_size?: number;
|
|
63
|
+
page_sizes?: number[];
|
|
64
|
+
on_page_size_change?: any;
|
|
65
|
+
class?: string;
|
|
66
|
+
} & Record<string, any>;
|
package/components/index.d.ts
CHANGED
|
@@ -47,3 +47,6 @@ export { default as CollapsibleSection } from "./CollapsibleSection.svelte";
|
|
|
47
47
|
export { default as OptionGrid } from "./OptionGrid.svelte";
|
|
48
48
|
export { default as ConditionTable } from "./ConditionTable.svelte";
|
|
49
49
|
export { default as LogViewer } from "./LogViewer.svelte";
|
|
50
|
+
export { default as DataTable } from "./DataTable.svelte";
|
|
51
|
+
export { default as Pagination } from "./Pagination.svelte";
|
|
52
|
+
export { default as Breadcrumb } from "./Breadcrumb.svelte";
|
package/components/index.js
CHANGED
|
@@ -76,3 +76,8 @@ export { default as CollapsibleSection } from "./CollapsibleSection.svelte";
|
|
|
76
76
|
export { default as OptionGrid } from "./OptionGrid.svelte";
|
|
77
77
|
export { default as ConditionTable } from "./ConditionTable.svelte";
|
|
78
78
|
export { default as LogViewer } from "./LogViewer.svelte";
|
|
79
|
+
|
|
80
|
+
// Data & navigation
|
|
81
|
+
export { default as DataTable } from "./DataTable.svelte";
|
|
82
|
+
export { default as Pagination } from "./Pagination.svelte";
|
|
83
|
+
export { default as Breadcrumb } from "./Breadcrumb.svelte";
|