@aiaiai-pt/design-system 0.3.8 → 0.4.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/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
|
+
Consumes --pagination-* tokens (falls back to semantic tokens).
|
|
6
|
+
|
|
7
|
+
@example Cursor mode (default)
|
|
8
|
+
<Pagination
|
|
9
|
+
has_next={page.has_next}
|
|
10
|
+
has_prev={page.has_prev}
|
|
11
|
+
on_next={() => loadNext()}
|
|
12
|
+
on_prev={() => loadPrev()}
|
|
13
|
+
total_items={page.count}
|
|
14
|
+
page_size={25}
|
|
15
|
+
/>
|
|
16
|
+
|
|
17
|
+
@example Offset mode
|
|
18
|
+
<Pagination
|
|
19
|
+
mode="offset"
|
|
20
|
+
current_page={page}
|
|
21
|
+
total_pages={totalPages}
|
|
22
|
+
total_items={totalItems}
|
|
23
|
+
on_page_change={(p) => setPage(p)}
|
|
24
|
+
page_sizes={[10, 25, 50, 100]}
|
|
25
|
+
on_page_size_change={(s) => setSize(s)}
|
|
26
|
+
/>
|
|
27
|
+
-->
|
|
28
|
+
<script>
|
|
29
|
+
import Select from './Select.svelte';
|
|
30
|
+
import Button from './Button.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
|
+
/** Compute visible page numbers with ellipsis for offset mode */
|
|
76
|
+
const page_numbers = $derived(
|
|
77
|
+
mode === 'offset' && total_pages
|
|
78
|
+
? build_pages(current_page, total_pages)
|
|
79
|
+
: /** @type {(number | null)[]} */ ([])
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const item_range_text = $derived(
|
|
83
|
+
total_items === undefined
|
|
84
|
+
? ''
|
|
85
|
+
: mode === 'cursor'
|
|
86
|
+
? `${total_items.toLocaleString()} total`
|
|
87
|
+
: (() => {
|
|
88
|
+
const start = (current_page - 1) * page_size + 1;
|
|
89
|
+
const end = Math.min(current_page * page_size, total_items);
|
|
90
|
+
return start > total_items
|
|
91
|
+
? `${total_items.toLocaleString()} total`
|
|
92
|
+
: `${start}–${end} of ${total_items.toLocaleString()}`;
|
|
93
|
+
})()
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {number} current
|
|
98
|
+
* @param {number} total
|
|
99
|
+
* @returns {(number | null)[]}
|
|
100
|
+
*/
|
|
101
|
+
function build_pages(current, total) {
|
|
102
|
+
if (total <= 7) {
|
|
103
|
+
return Array.from({ length: total }, (_, i) => i + 1);
|
|
104
|
+
}
|
|
105
|
+
const pages = /** @type {(number | null)[]} */ ([]);
|
|
106
|
+
// Always show first
|
|
107
|
+
pages.push(1);
|
|
108
|
+
if (current > 3) pages.push(null); // left ellipsis
|
|
109
|
+
// Window around current
|
|
110
|
+
const win_start = Math.max(2, current - 1);
|
|
111
|
+
const win_end = Math.min(total - 1, current + 1);
|
|
112
|
+
for (let p = win_start; p <= win_end; p++) pages.push(p);
|
|
113
|
+
if (current < total - 2) pages.push(null); // right ellipsis
|
|
114
|
+
// Always show last
|
|
115
|
+
pages.push(total);
|
|
116
|
+
return pages;
|
|
117
|
+
}
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
<div class="pagination {className}" {...rest}>
|
|
121
|
+
{#if page_sizes && page_sizes.length > 0}
|
|
122
|
+
<div class="pagination-size">
|
|
123
|
+
<Select
|
|
124
|
+
size="sm"
|
|
125
|
+
value={page_size_value}
|
|
126
|
+
options={page_size_options}
|
|
127
|
+
onchange={(val) => on_page_size_change?.(Number(val))}
|
|
128
|
+
aria-label="Rows per page"
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
{/if}
|
|
132
|
+
|
|
133
|
+
{#if total_items !== undefined}
|
|
134
|
+
<span class="pagination-info">{item_range_text}</span>
|
|
135
|
+
{/if}
|
|
136
|
+
|
|
137
|
+
<span class="pagination-spacer" aria-hidden="true"></span>
|
|
138
|
+
|
|
139
|
+
{#if mode === 'cursor'}
|
|
140
|
+
<Button
|
|
141
|
+
variant="ghost"
|
|
142
|
+
size="sm"
|
|
143
|
+
disabled={!has_prev}
|
|
144
|
+
onclick={on_prev}
|
|
145
|
+
aria-label="Previous page"
|
|
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>
|
|
169
|
+
|
|
170
|
+
{:else}
|
|
171
|
+
<Button
|
|
172
|
+
variant="ghost"
|
|
173
|
+
size="sm"
|
|
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>
|
|
185
|
+
|
|
186
|
+
<div class="pagination-pages">
|
|
187
|
+
{#each page_numbers as p}
|
|
188
|
+
{#if p === null}
|
|
189
|
+
<span class="pagination-ellipsis" aria-hidden="true">…</span>
|
|
190
|
+
{:else}
|
|
191
|
+
<button
|
|
192
|
+
type="button"
|
|
193
|
+
class="pagination-page"
|
|
194
|
+
class:pagination-page-active={p === current_page}
|
|
195
|
+
onclick={() => on_page_change?.(/** @type {number} */ (p))}
|
|
196
|
+
aria-label={`Page ${p}`}
|
|
197
|
+
aria-current={p === current_page ? 'page' : undefined}
|
|
198
|
+
>
|
|
199
|
+
{p}
|
|
200
|
+
</button>
|
|
201
|
+
{/if}
|
|
202
|
+
{/each}
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<Button
|
|
206
|
+
variant="ghost"
|
|
207
|
+
size="sm"
|
|
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>
|
|
219
|
+
{/if}
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
<style>
|
|
223
|
+
.pagination {
|
|
224
|
+
display: flex;
|
|
225
|
+
align-items: center;
|
|
226
|
+
gap: var(--space-sm);
|
|
227
|
+
flex-wrap: wrap;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.pagination-size {
|
|
231
|
+
/* Constrain size selector width */
|
|
232
|
+
width: 80px;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.pagination-info {
|
|
236
|
+
font-family: var(--type-body-sm-font);
|
|
237
|
+
font-size: var(--type-body-sm-size);
|
|
238
|
+
color: var(--color-text-muted);
|
|
239
|
+
white-space: nowrap;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.pagination-spacer {
|
|
243
|
+
flex: 1;
|
|
244
|
+
min-width: var(--space-sm);
|
|
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";
|