@makolabs/ripple 0.0.1 → 0.0.4
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/README.md +575 -8
- package/dist/adapters/storage/BaseAdapter.d.ts +20 -0
- package/dist/adapters/storage/BaseAdapter.js +171 -0
- package/dist/adapters/storage/S3Adapter.d.ts +21 -0
- package/dist/adapters/storage/S3Adapter.js +194 -0
- package/dist/adapters/storage/index.d.ts +3 -0
- package/dist/adapters/storage/index.js +3 -0
- package/dist/adapters/storage/types.d.ts +102 -0
- package/dist/adapters/storage/types.js +4 -0
- package/dist/button/Button.svelte +48 -0
- package/dist/button/Button.svelte.d.ts +4 -0
- package/dist/button/button.d.ts +113 -0
- package/dist/button/button.js +168 -0
- package/dist/charts/Chart.svelte +545 -0
- package/dist/charts/Chart.svelte.d.ts +4 -0
- package/dist/drawer/Drawer.svelte +224 -0
- package/dist/drawer/Drawer.svelte.d.ts +4 -0
- package/dist/drawer/drawer.d.ts +160 -0
- package/dist/drawer/drawer.js +80 -0
- package/dist/elements/accordion/Accordion.svelte +98 -0
- package/dist/elements/accordion/Accordion.svelte.d.ts +4 -0
- package/dist/elements/accordion/accordion.d.ts +227 -0
- package/dist/elements/accordion/accordion.js +138 -0
- package/dist/elements/alert/Alert.svelte +57 -0
- package/dist/elements/alert/Alert.svelte.d.ts +4 -0
- package/dist/elements/badge/Badge.svelte +43 -0
- package/dist/elements/badge/Badge.svelte.d.ts +4 -0
- package/dist/elements/badge/badge.d.ts +181 -0
- package/dist/elements/badge/badge.js +65 -0
- package/dist/elements/dropdown/Dropdown.svelte +234 -0
- package/dist/elements/dropdown/Dropdown.svelte.d.ts +4 -0
- package/dist/elements/dropdown/Select.svelte +333 -0
- package/dist/elements/dropdown/Select.svelte.d.ts +4 -0
- package/dist/elements/dropdown/dropdown.d.ts +251 -0
- package/dist/elements/dropdown/dropdown.js +95 -0
- package/dist/elements/dropdown/select.d.ts +200 -0
- package/dist/elements/dropdown/select.js +82 -0
- package/dist/elements/file-upload/FileUpload.svelte +135 -0
- package/dist/elements/file-upload/FileUpload.svelte.d.ts +4 -0
- package/dist/elements/file-upload/FilesPreview.svelte +93 -0
- package/dist/elements/file-upload/FilesPreview.svelte.d.ts +4 -0
- package/dist/elements/progress/Progress.svelte +145 -0
- package/dist/elements/progress/Progress.svelte.d.ts +4 -0
- package/dist/elements/timeline/Timeline.svelte +92 -0
- package/dist/elements/timeline/Timeline.svelte.d.ts +7 -0
- package/dist/file-browser/FileBrowser.svelte +877 -0
- package/dist/file-browser/FileBrowser.svelte.d.ts +14 -0
- package/dist/file-browser/index.d.ts +1 -0
- package/dist/file-browser/index.js +1 -0
- package/dist/filters/CompactFilters.svelte +147 -0
- package/dist/filters/CompactFilters.svelte.d.ts +4 -0
- package/dist/filters/index.d.ts +1 -0
- package/dist/filters/index.js +1 -0
- package/dist/forms/Checkbox.svelte +54 -0
- package/dist/forms/Checkbox.svelte.d.ts +4 -0
- package/dist/forms/DateRange.svelte +493 -0
- package/dist/forms/DateRange.svelte.d.ts +4 -0
- package/dist/forms/Form.svelte +39 -0
- package/dist/forms/Form.svelte.d.ts +4 -0
- package/dist/forms/Input.svelte +86 -0
- package/dist/forms/Input.svelte.d.ts +4 -0
- package/dist/forms/NumberInput.svelte +159 -0
- package/dist/forms/NumberInput.svelte.d.ts +4 -0
- package/dist/forms/RadioInputs.svelte +64 -0
- package/dist/forms/RadioInputs.svelte.d.ts +4 -0
- package/dist/forms/RadioPill.svelte +66 -0
- package/dist/forms/RadioPill.svelte.d.ts +4 -0
- package/dist/forms/Slider.svelte +342 -0
- package/dist/forms/Slider.svelte.d.ts +4 -0
- package/dist/forms/Tags.svelte +181 -0
- package/dist/forms/Tags.svelte.d.ts +4 -0
- package/dist/forms/Toggle.svelte +132 -0
- package/dist/forms/Toggle.svelte.d.ts +4 -0
- package/dist/forms/slider.d.ts +143 -0
- package/dist/forms/slider.js +62 -0
- package/dist/header/Breadcrumbs.svelte +73 -0
- package/dist/header/Breadcrumbs.svelte.d.ts +4 -0
- package/dist/header/PageHeader.svelte +68 -0
- package/dist/header/PageHeader.svelte.d.ts +4 -0
- package/dist/header/breadcrumbs.d.ts +226 -0
- package/dist/header/breadcrumbs.js +87 -0
- package/dist/helper/cls.d.ts +1 -0
- package/dist/helper/cls.js +4 -0
- package/dist/helper/date.d.ts +7 -0
- package/dist/helper/date.js +15 -0
- package/dist/helper/nav.svelte.d.ts +6 -0
- package/dist/helper/nav.svelte.js +23 -0
- package/dist/index.d.ts +856 -1
- package/dist/index.js +78 -1
- package/dist/layout/card/Card.svelte +41 -0
- package/dist/layout/card/Card.svelte.d.ts +4 -0
- package/dist/layout/card/MetricCard.svelte +64 -0
- package/dist/layout/card/MetricCard.svelte.d.ts +4 -0
- package/dist/layout/card/StatsCard.svelte +266 -0
- package/dist/layout/card/StatsCard.svelte.d.ts +4 -0
- package/dist/layout/card/card.d.ts +128 -0
- package/dist/layout/card/card.js +51 -0
- package/dist/layout/card/metric-card.d.ts +49 -0
- package/dist/layout/card/metric-card.js +10 -0
- package/dist/layout/card/stats-card.d.ts +191 -0
- package/dist/layout/card/stats-card.js +73 -0
- package/dist/layout/navbar/Navbar.svelte +206 -0
- package/dist/layout/navbar/Navbar.svelte.d.ts +4 -0
- package/dist/layout/navbar/navbar.d.ts +205 -0
- package/dist/layout/navbar/navbar.js +98 -0
- package/dist/layout/sidebar/NavGroup.svelte +91 -0
- package/dist/layout/sidebar/NavGroup.svelte.d.ts +4 -0
- package/dist/layout/sidebar/NavItem.svelte +29 -0
- package/dist/layout/sidebar/NavItem.svelte.d.ts +4 -0
- package/dist/layout/sidebar/Sidebar.svelte +193 -0
- package/dist/layout/sidebar/Sidebar.svelte.d.ts +4 -0
- package/dist/layout/table/Cells.svelte +111 -0
- package/dist/layout/table/Cells.svelte.d.ts +27 -0
- package/dist/layout/table/Table.svelte +790 -0
- package/dist/layout/table/Table.svelte.d.ts +4 -0
- package/dist/layout/table/table.d.ts +256 -0
- package/dist/layout/table/table.js +141 -0
- package/dist/layout/tabs/Tab.svelte +60 -0
- package/dist/layout/tabs/Tab.svelte.d.ts +4 -0
- package/dist/layout/tabs/TabContent.svelte +30 -0
- package/dist/layout/tabs/TabContent.svelte.d.ts +4 -0
- package/dist/layout/tabs/TabGroup.svelte +62 -0
- package/dist/layout/tabs/TabGroup.svelte.d.ts +4 -0
- package/dist/layout/tabs/tabs.d.ts +140 -0
- package/dist/layout/tabs/tabs.js +298 -0
- package/dist/modal/Modal.svelte +207 -0
- package/dist/modal/Modal.svelte.d.ts +4 -0
- package/dist/modal/modal.d.ts +211 -0
- package/dist/modal/modal.js +81 -0
- package/dist/sonner/sonner.svelte +13 -0
- package/dist/sonner/sonner.svelte.d.ts +4 -0
- package/dist/types/variants.d.ts +1 -0
- package/dist/types/variants.js +1 -0
- package/dist/utils/Portal.svelte +108 -0
- package/dist/utils/Portal.svelte.d.ts +8 -0
- package/dist/utils/dateUtils.d.ts +7 -0
- package/dist/utils/dateUtils.js +26 -0
- package/dist/variants.d.ts +30 -0
- package/dist/variants.js +36 -0
- package/package.json +39 -6
- package/dist/layout/Card.svelte +0 -179
- package/dist/layout/Card.svelte.d.ts +0 -208
- package/dist/layout/index.d.ts +0 -1
- package/dist/layout/index.js +0 -1
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../../helper/cls.js';
|
|
3
|
+
import { table } from './table.js';
|
|
4
|
+
import type { TableProps, SortDirection, SortState } from '../../index.js';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
data = [],
|
|
8
|
+
columns = [],
|
|
9
|
+
bordered = true,
|
|
10
|
+
striped = false,
|
|
11
|
+
pageSize = 10,
|
|
12
|
+
currentPage: externalCurrentPage,
|
|
13
|
+
totalItems,
|
|
14
|
+
selectable = false,
|
|
15
|
+
selected = $bindable([]),
|
|
16
|
+
onrowclick,
|
|
17
|
+
onsort = () => {},
|
|
18
|
+
onselect = () => {},
|
|
19
|
+
onpagechange,
|
|
20
|
+
onpagesizechange,
|
|
21
|
+
class: classname = '',
|
|
22
|
+
wrapperclass: wrapperClass = '',
|
|
23
|
+
tableclass: tableClass = '',
|
|
24
|
+
theadclass: theadClass = '',
|
|
25
|
+
tbodyclass: tbodyClass = '',
|
|
26
|
+
trclass: trClass = 'bg-white',
|
|
27
|
+
thclass: thClass = '',
|
|
28
|
+
tdclass: tdClass = '',
|
|
29
|
+
footerclass: footerClass = '',
|
|
30
|
+
paginationclass: paginationClass = '',
|
|
31
|
+
rowclass = () => '',
|
|
32
|
+
loading = false,
|
|
33
|
+
expandedContent,
|
|
34
|
+
pagination = true,
|
|
35
|
+
showPagination = true,
|
|
36
|
+
showPageSize = false,
|
|
37
|
+
pageSizeOptions = [5, 10, 25, 50, 100],
|
|
38
|
+
paginationPosition = 'bottom',
|
|
39
|
+
paginationTemplate = 'full'
|
|
40
|
+
}: TableProps<any> = $props();
|
|
41
|
+
|
|
42
|
+
let sortColumn = $state('');
|
|
43
|
+
let sortDirection = $state<SortDirection>(null);
|
|
44
|
+
let internalCurrentPage = $state(externalCurrentPage || 1);
|
|
45
|
+
let internalPageSize = $state(pageSize);
|
|
46
|
+
|
|
47
|
+
// Use external current page if provided
|
|
48
|
+
$effect(() => {
|
|
49
|
+
if (externalCurrentPage !== undefined) {
|
|
50
|
+
internalCurrentPage = externalCurrentPage;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Pagination is automatically determined by pageSize and pagination prop
|
|
55
|
+
const showPaginationControls = $derived(
|
|
56
|
+
pagination &&
|
|
57
|
+
showPagination &&
|
|
58
|
+
(data.length > internalPageSize || totalItems > internalPageSize)
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Calculate total items and pages
|
|
62
|
+
const effectiveTotalItems = $derived(totalItems !== undefined ? totalItems : data.length);
|
|
63
|
+
const totalPages = $derived(Math.ceil(effectiveTotalItems / internalPageSize));
|
|
64
|
+
|
|
65
|
+
const {
|
|
66
|
+
base: baseClass,
|
|
67
|
+
wrapper: wrapperBaseClass,
|
|
68
|
+
table: tableBaseClass,
|
|
69
|
+
thead: theadBaseClass,
|
|
70
|
+
tbody: tbodyBaseClass,
|
|
71
|
+
tr: trBaseClass,
|
|
72
|
+
th: thBaseClass,
|
|
73
|
+
td: tdBaseClass,
|
|
74
|
+
footer: footerBaseClass,
|
|
75
|
+
pagination: paginationBaseClass,
|
|
76
|
+
emptyState: emptyStateBaseClass,
|
|
77
|
+
sortButton: sortButtonBaseClass,
|
|
78
|
+
sortIcon: sortIconBaseClass
|
|
79
|
+
} = $derived(
|
|
80
|
+
table({
|
|
81
|
+
bordered,
|
|
82
|
+
striped
|
|
83
|
+
})
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const baseClasses = $derived(cn(baseClass(), classname));
|
|
87
|
+
const wrapperClasses = $derived(cn(wrapperBaseClass(), wrapperClass));
|
|
88
|
+
const tableClasses = $derived(cn(tableBaseClass(), tableClass));
|
|
89
|
+
const theadClasses = $derived(cn(theadBaseClass(), theadClass));
|
|
90
|
+
const tbodyClasses = $derived(cn(tbodyBaseClass(), tbodyClass));
|
|
91
|
+
const trClasses = $derived(cn(trBaseClass(), trClass));
|
|
92
|
+
const thClasses = $derived(cn(thBaseClass(), thClass));
|
|
93
|
+
const tdClasses = $derived(cn(tdBaseClass(), tdClass));
|
|
94
|
+
const footerClasses = $derived(cn(footerBaseClass(), footerClass));
|
|
95
|
+
const paginationClasses = $derived(cn(paginationBaseClass(), paginationClass));
|
|
96
|
+
const emptyStateClasses = $derived(emptyStateBaseClass());
|
|
97
|
+
|
|
98
|
+
// Handle pagination
|
|
99
|
+
function getPaginatedData() {
|
|
100
|
+
// If no pagination or all data fits on one page, return all data
|
|
101
|
+
if (!pagination || data.length <= internalPageSize) {
|
|
102
|
+
return data;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// If external data source might be handling pagination
|
|
106
|
+
if (totalItems !== undefined) {
|
|
107
|
+
// If data.length is less than or equal to pageSize, assume it's already paginated
|
|
108
|
+
if (data.length <= internalPageSize) {
|
|
109
|
+
return data;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Otherwise, handle pagination internally
|
|
114
|
+
const startIndex = (internalCurrentPage - 1) * internalPageSize;
|
|
115
|
+
return data.slice(startIndex, startIndex + internalPageSize);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Handle sorting
|
|
119
|
+
function toggleSort(column: string) {
|
|
120
|
+
const columnDef = columns.find((col) => (col.sortKey || col.key) === column);
|
|
121
|
+
if (!columnDef?.sortable) return;
|
|
122
|
+
|
|
123
|
+
if (sortColumn === column) {
|
|
124
|
+
// Cycle through: asc -> desc -> null
|
|
125
|
+
if (sortDirection === 'asc') {
|
|
126
|
+
sortDirection = 'desc';
|
|
127
|
+
} else if (sortDirection === 'desc') {
|
|
128
|
+
sortDirection = null;
|
|
129
|
+
sortColumn = '';
|
|
130
|
+
} else {
|
|
131
|
+
sortDirection = 'asc';
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
sortColumn = column;
|
|
135
|
+
sortDirection = 'asc';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const newSortState: SortState = { column: sortColumn, direction: sortDirection };
|
|
139
|
+
onsort(newSortState);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function toggleRowSelection(row: any) {
|
|
143
|
+
if (!selectable) return;
|
|
144
|
+
|
|
145
|
+
const index = selected.findIndex((r) => r === row);
|
|
146
|
+
if (index === -1) {
|
|
147
|
+
selected = [...selected, row];
|
|
148
|
+
} else {
|
|
149
|
+
selected = selected.filter((r) => r !== row);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
onselect(selected);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function isRowSelected(row: any) {
|
|
156
|
+
return selected.includes(row);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function handleRowClick(row: any, index: number) {
|
|
160
|
+
onrowclick?.(row, index);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function goToFirstPage() {
|
|
164
|
+
if (internalCurrentPage !== 1) {
|
|
165
|
+
internalCurrentPage = 1;
|
|
166
|
+
onpagechange?.(internalCurrentPage);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function goToLastPage() {
|
|
171
|
+
if (internalCurrentPage !== totalPages) {
|
|
172
|
+
internalCurrentPage = totalPages;
|
|
173
|
+
onpagechange?.(internalCurrentPage);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function nextPage() {
|
|
178
|
+
if (internalCurrentPage < totalPages) {
|
|
179
|
+
internalCurrentPage++;
|
|
180
|
+
onpagechange?.(internalCurrentPage);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function prevPage() {
|
|
185
|
+
if (internalCurrentPage > 1) {
|
|
186
|
+
internalCurrentPage--;
|
|
187
|
+
onpagechange?.(internalCurrentPage);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function goToPage(page: number) {
|
|
192
|
+
if (page >= 1 && page <= totalPages && page !== internalCurrentPage) {
|
|
193
|
+
internalCurrentPage = page;
|
|
194
|
+
onpagechange?.(internalCurrentPage);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function handlePageSizeChange(event: Event) {
|
|
199
|
+
const select = event.target as HTMLSelectElement;
|
|
200
|
+
const newPageSize = parseInt(select.value, 10);
|
|
201
|
+
internalPageSize = newPageSize;
|
|
202
|
+
|
|
203
|
+
// Adjust current page if it would exceed the new total pages
|
|
204
|
+
const newTotalPages = Math.ceil(effectiveTotalItems / newPageSize);
|
|
205
|
+
if (internalCurrentPage > newTotalPages) {
|
|
206
|
+
internalCurrentPage = newTotalPages || 1;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
onpagesizechange?.(newPageSize);
|
|
210
|
+
onpagechange?.(internalCurrentPage);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function getPageNumbers(): number[] {
|
|
214
|
+
const pages: number[] = [];
|
|
215
|
+
const maxPages = 5;
|
|
216
|
+
const halfMax = Math.floor(maxPages / 2);
|
|
217
|
+
|
|
218
|
+
let start = Math.max(1, internalCurrentPage - halfMax);
|
|
219
|
+
let end = Math.min(totalPages, start + maxPages - 1);
|
|
220
|
+
|
|
221
|
+
if (end - start + 1 < maxPages) {
|
|
222
|
+
start = Math.max(1, end - maxPages + 1);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
for (let i = start; i <= end; i++) {
|
|
226
|
+
pages.push(i);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return pages;
|
|
230
|
+
}
|
|
231
|
+
</script>
|
|
232
|
+
|
|
233
|
+
<div class={baseClasses}>
|
|
234
|
+
{#if showPaginationControls && (paginationPosition === 'top' || paginationPosition === 'both')}
|
|
235
|
+
<div class={footerClasses}>
|
|
236
|
+
<div class={paginationClasses}>
|
|
237
|
+
<div class="flex items-center gap-2">
|
|
238
|
+
{#if showPageSize}
|
|
239
|
+
<div class="flex items-center gap-2">
|
|
240
|
+
<label for="table-page-size" class="text-default-500 text-sm">Show</label>
|
|
241
|
+
<select
|
|
242
|
+
id="table-page-size"
|
|
243
|
+
class="border-default-200 rounded-md border px-2 py-1 text-sm"
|
|
244
|
+
value={internalPageSize}
|
|
245
|
+
onchange={handlePageSizeChange}
|
|
246
|
+
>
|
|
247
|
+
{#each pageSizeOptions as option}
|
|
248
|
+
<option value={option}>{option}</option>
|
|
249
|
+
{/each}
|
|
250
|
+
</select>
|
|
251
|
+
<span class="text-default-500 text-sm">entries</span>
|
|
252
|
+
</div>
|
|
253
|
+
{/if}
|
|
254
|
+
<span class="text-default-500 text-sm">
|
|
255
|
+
Showing {Math.min(
|
|
256
|
+
(internalCurrentPage - 1) * internalPageSize + 1,
|
|
257
|
+
effectiveTotalItems
|
|
258
|
+
)}
|
|
259
|
+
to {Math.min(internalCurrentPage * internalPageSize, effectiveTotalItems)} of {effectiveTotalItems}
|
|
260
|
+
entries
|
|
261
|
+
</span>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
<div class="flex items-center gap-1">
|
|
265
|
+
{#if paginationTemplate === 'full'}
|
|
266
|
+
<!-- First page button -->
|
|
267
|
+
<button
|
|
268
|
+
type="button"
|
|
269
|
+
class={cn(
|
|
270
|
+
'relative inline-flex items-center rounded-md px-2 py-1 text-sm font-medium',
|
|
271
|
+
internalCurrentPage === 1
|
|
272
|
+
? 'text-default-300 cursor-not-allowed'
|
|
273
|
+
: 'text-default-700 hover:bg-default-100'
|
|
274
|
+
)}
|
|
275
|
+
onclick={goToFirstPage}
|
|
276
|
+
disabled={internalCurrentPage === 1}
|
|
277
|
+
aria-label="First page"
|
|
278
|
+
>
|
|
279
|
+
<!-- Double Chevron Left SVG -->
|
|
280
|
+
<svg
|
|
281
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
282
|
+
width="16"
|
|
283
|
+
height="16"
|
|
284
|
+
viewBox="0 0 24 24"
|
|
285
|
+
fill="none"
|
|
286
|
+
stroke="currentColor"
|
|
287
|
+
stroke-width="2"
|
|
288
|
+
stroke-linecap="round"
|
|
289
|
+
stroke-linejoin="round"
|
|
290
|
+
class="h-4 w-4"
|
|
291
|
+
>
|
|
292
|
+
<path d="m11 17-5-5 5-5"></path>
|
|
293
|
+
<path d="m18 17-5-5 5-5"></path>
|
|
294
|
+
</svg>
|
|
295
|
+
</button>
|
|
296
|
+
{/if}
|
|
297
|
+
|
|
298
|
+
<!-- Previous page button -->
|
|
299
|
+
<button
|
|
300
|
+
type="button"
|
|
301
|
+
class={cn(
|
|
302
|
+
'relative inline-flex items-center rounded-md px-2 py-1 text-sm font-medium',
|
|
303
|
+
internalCurrentPage === 1
|
|
304
|
+
? 'text-default-300 cursor-not-allowed'
|
|
305
|
+
: 'text-default-700 hover:bg-default-100'
|
|
306
|
+
)}
|
|
307
|
+
onclick={prevPage}
|
|
308
|
+
disabled={internalCurrentPage === 1}
|
|
309
|
+
aria-label="Previous page"
|
|
310
|
+
>
|
|
311
|
+
<!-- Chevron Left SVG -->
|
|
312
|
+
<svg
|
|
313
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
314
|
+
width="16"
|
|
315
|
+
height="16"
|
|
316
|
+
viewBox="0 0 24 24"
|
|
317
|
+
fill="none"
|
|
318
|
+
stroke="currentColor"
|
|
319
|
+
stroke-width="2"
|
|
320
|
+
stroke-linecap="round"
|
|
321
|
+
stroke-linejoin="round"
|
|
322
|
+
class="h-4 w-4"
|
|
323
|
+
>
|
|
324
|
+
<path d="m15 18-6-6 6-6"></path>
|
|
325
|
+
</svg>
|
|
326
|
+
</button>
|
|
327
|
+
|
|
328
|
+
<!-- Page numbers -->
|
|
329
|
+
{#if paginationTemplate === 'full'}
|
|
330
|
+
{#each getPageNumbers() as pageNum}
|
|
331
|
+
<button
|
|
332
|
+
type="button"
|
|
333
|
+
class={cn(
|
|
334
|
+
'relative inline-flex items-center rounded-md px-3 py-1 text-sm font-medium',
|
|
335
|
+
internalCurrentPage === pageNum
|
|
336
|
+
? 'bg-primary-100 text-primary-700'
|
|
337
|
+
: 'text-default-700 hover:bg-default-100'
|
|
338
|
+
)}
|
|
339
|
+
onclick={() => goToPage(pageNum)}
|
|
340
|
+
aria-label={`Page ${pageNum}`}
|
|
341
|
+
aria-current={internalCurrentPage === pageNum ? 'page' : undefined}
|
|
342
|
+
>
|
|
343
|
+
{pageNum}
|
|
344
|
+
</button>
|
|
345
|
+
{/each}
|
|
346
|
+
{:else}
|
|
347
|
+
<span class="text-default-500 px-2 text-sm">
|
|
348
|
+
Page {internalCurrentPage} of {totalPages}
|
|
349
|
+
</span>
|
|
350
|
+
{/if}
|
|
351
|
+
|
|
352
|
+
<!-- Next page button -->
|
|
353
|
+
<button
|
|
354
|
+
type="button"
|
|
355
|
+
class={cn(
|
|
356
|
+
'relative inline-flex items-center rounded-md px-2 py-1 text-sm font-medium',
|
|
357
|
+
internalCurrentPage === totalPages
|
|
358
|
+
? 'text-default-300 cursor-not-allowed'
|
|
359
|
+
: 'text-default-700 hover:bg-default-100'
|
|
360
|
+
)}
|
|
361
|
+
onclick={nextPage}
|
|
362
|
+
disabled={internalCurrentPage === totalPages}
|
|
363
|
+
aria-label="Next page"
|
|
364
|
+
>
|
|
365
|
+
<!-- Chevron Right SVG -->
|
|
366
|
+
<svg
|
|
367
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
368
|
+
width="16"
|
|
369
|
+
height="16"
|
|
370
|
+
viewBox="0 0 24 24"
|
|
371
|
+
fill="none"
|
|
372
|
+
stroke="currentColor"
|
|
373
|
+
stroke-width="2"
|
|
374
|
+
stroke-linecap="round"
|
|
375
|
+
stroke-linejoin="round"
|
|
376
|
+
class="h-4 w-4"
|
|
377
|
+
>
|
|
378
|
+
<path d="m9 18 6-6-6-6"></path>
|
|
379
|
+
</svg>
|
|
380
|
+
</button>
|
|
381
|
+
|
|
382
|
+
{#if paginationTemplate === 'full'}
|
|
383
|
+
<!-- Last page button -->
|
|
384
|
+
<button
|
|
385
|
+
type="button"
|
|
386
|
+
class={cn(
|
|
387
|
+
'relative inline-flex items-center rounded-md px-2 py-1 text-sm font-medium',
|
|
388
|
+
internalCurrentPage === totalPages
|
|
389
|
+
? 'text-default-300 cursor-not-allowed'
|
|
390
|
+
: 'text-default-700 hover:bg-default-100'
|
|
391
|
+
)}
|
|
392
|
+
onclick={goToLastPage}
|
|
393
|
+
disabled={internalCurrentPage === totalPages}
|
|
394
|
+
aria-label="Last page"
|
|
395
|
+
>
|
|
396
|
+
<!-- Double Chevron Right SVG -->
|
|
397
|
+
<svg
|
|
398
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
399
|
+
width="16"
|
|
400
|
+
height="16"
|
|
401
|
+
viewBox="0 0 24 24"
|
|
402
|
+
fill="none"
|
|
403
|
+
stroke="currentColor"
|
|
404
|
+
stroke-width="2"
|
|
405
|
+
stroke-linecap="round"
|
|
406
|
+
stroke-linejoin="round"
|
|
407
|
+
class="h-4 w-4"
|
|
408
|
+
>
|
|
409
|
+
<path d="m13 17 5-5-5-5"></path>
|
|
410
|
+
<path d="m6 17 5-5-5-5"></path>
|
|
411
|
+
</svg>
|
|
412
|
+
</button>
|
|
413
|
+
{/if}
|
|
414
|
+
</div>
|
|
415
|
+
</div>
|
|
416
|
+
</div>
|
|
417
|
+
{/if}
|
|
418
|
+
|
|
419
|
+
<div class={wrapperClasses}>
|
|
420
|
+
<table class={tableClasses}>
|
|
421
|
+
<thead class={theadClasses}>
|
|
422
|
+
<tr>
|
|
423
|
+
{#if selectable}
|
|
424
|
+
<th class={cn(thClasses, 'text-center')}>
|
|
425
|
+
<input
|
|
426
|
+
type="checkbox"
|
|
427
|
+
onchange={() => {
|
|
428
|
+
if (selected.length === data.length) {
|
|
429
|
+
selected = [];
|
|
430
|
+
} else {
|
|
431
|
+
selected = [...data];
|
|
432
|
+
}
|
|
433
|
+
onselect(selected);
|
|
434
|
+
}}
|
|
435
|
+
checked={selected.length === data.length && data.length > 0}
|
|
436
|
+
aria-label="Select all rows"
|
|
437
|
+
/>
|
|
438
|
+
</th>
|
|
439
|
+
{/if}
|
|
440
|
+
|
|
441
|
+
{#each columns as column (column.key)}
|
|
442
|
+
<th
|
|
443
|
+
class={cn(
|
|
444
|
+
thClasses,
|
|
445
|
+
column.align === 'center' && 'text-center',
|
|
446
|
+
column.align === 'right' && 'text-right',
|
|
447
|
+
column.class
|
|
448
|
+
)}
|
|
449
|
+
style={column.width ? `width: ${column.width}` : undefined}
|
|
450
|
+
>
|
|
451
|
+
{#if column.sortable}
|
|
452
|
+
<button
|
|
453
|
+
type="button"
|
|
454
|
+
class={sortButtonBaseClass()}
|
|
455
|
+
onclick={() => toggleSort(column.sortKey || column.key)}
|
|
456
|
+
aria-label={`Sort by ${column.header}`}
|
|
457
|
+
>
|
|
458
|
+
{column.header}
|
|
459
|
+
<span class={sortIconBaseClass()}>
|
|
460
|
+
{#if sortColumn === (column.sortKey || column.key)}
|
|
461
|
+
{#if sortDirection === 'asc'}
|
|
462
|
+
<svg
|
|
463
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
464
|
+
viewBox="0 0 20 20"
|
|
465
|
+
fill="currentColor"
|
|
466
|
+
class="h-4 w-4"
|
|
467
|
+
>
|
|
468
|
+
<path
|
|
469
|
+
d="M10 15a.75.75 0 01-.75-.75V7.612L6.058 10.8a.75.75 0 01-1.061-1.061l3.75-3.75a.75.75 0 011.06 0l3.75 3.75a.75.75 0 11-1.06 1.061L10.75 7.612v6.638A.75.75 0 0110 15z"
|
|
470
|
+
/>
|
|
471
|
+
</svg>
|
|
472
|
+
{:else if sortDirection === 'desc'}
|
|
473
|
+
<svg
|
|
474
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
475
|
+
viewBox="0 0 20 20"
|
|
476
|
+
fill="currentColor"
|
|
477
|
+
class="h-4 w-4"
|
|
478
|
+
>
|
|
479
|
+
<path
|
|
480
|
+
d="M10 5a.75.75 0 01.75.75v6.638l3.192-3.187a.75.75 0 111.06 1.061l-3.75 3.75a.75.75 0 01-1.06 0l-3.75-3.75a.75.75 0 111.06-1.061L9.25 12.389V5.75A.75.75 0 0110 5z"
|
|
481
|
+
/>
|
|
482
|
+
</svg>
|
|
483
|
+
{/if}
|
|
484
|
+
{:else}
|
|
485
|
+
<svg
|
|
486
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
487
|
+
fill="none"
|
|
488
|
+
viewBox="0 0 24 24"
|
|
489
|
+
stroke-width="1.5"
|
|
490
|
+
stroke="currentColor"
|
|
491
|
+
class="h-4 w-4 opacity-40"
|
|
492
|
+
>
|
|
493
|
+
<path
|
|
494
|
+
stroke-linecap="round"
|
|
495
|
+
stroke-linejoin="round"
|
|
496
|
+
d="M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9"
|
|
497
|
+
/>
|
|
498
|
+
</svg>
|
|
499
|
+
{/if}
|
|
500
|
+
</span>
|
|
501
|
+
</button>
|
|
502
|
+
{:else}
|
|
503
|
+
{column.header}
|
|
504
|
+
{/if}
|
|
505
|
+
</th>
|
|
506
|
+
{/each}
|
|
507
|
+
</tr>
|
|
508
|
+
</thead>
|
|
509
|
+
|
|
510
|
+
<tbody class={tbodyClasses}>
|
|
511
|
+
{#if loading}
|
|
512
|
+
<tr>
|
|
513
|
+
<td
|
|
514
|
+
colspan={selectable ? columns.length + 1 : columns.length}
|
|
515
|
+
class={cn(tdClasses, 'py-8 text-center')}
|
|
516
|
+
>
|
|
517
|
+
<div class="flex justify-center">
|
|
518
|
+
<svg
|
|
519
|
+
class="text-default-500 h-6 w-6 animate-spin"
|
|
520
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
521
|
+
fill="none"
|
|
522
|
+
viewBox="0 0 24 24"
|
|
523
|
+
>
|
|
524
|
+
<circle
|
|
525
|
+
class="opacity-25"
|
|
526
|
+
cx="12"
|
|
527
|
+
cy="12"
|
|
528
|
+
r="10"
|
|
529
|
+
stroke="currentColor"
|
|
530
|
+
stroke-width="4"
|
|
531
|
+
></circle>
|
|
532
|
+
<path
|
|
533
|
+
class="opacity-75"
|
|
534
|
+
fill="currentColor"
|
|
535
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
536
|
+
></path>
|
|
537
|
+
</svg>
|
|
538
|
+
</div>
|
|
539
|
+
</td>
|
|
540
|
+
</tr>
|
|
541
|
+
{:else if getPaginatedData().length === 0}
|
|
542
|
+
<tr>
|
|
543
|
+
<td
|
|
544
|
+
colspan={selectable ? columns.length + 1 : columns.length}
|
|
545
|
+
class={emptyStateClasses}
|
|
546
|
+
>
|
|
547
|
+
No data available
|
|
548
|
+
</td>
|
|
549
|
+
</tr>
|
|
550
|
+
{:else}
|
|
551
|
+
{#each getPaginatedData() as row, rowIndex}
|
|
552
|
+
<tr
|
|
553
|
+
class={cn(trClasses, rowclass(row, rowIndex), {
|
|
554
|
+
'bg-primary-100': selectable && isRowSelected(row),
|
|
555
|
+
'cursor-pointer': onrowclick
|
|
556
|
+
})}
|
|
557
|
+
onclick={() => handleRowClick(row, rowIndex)}
|
|
558
|
+
aria-selected={selectable && isRowSelected(row)}
|
|
559
|
+
>
|
|
560
|
+
{#if selectable}
|
|
561
|
+
<td class={cn(tdClasses, 'text-center')}>
|
|
562
|
+
<input
|
|
563
|
+
type="checkbox"
|
|
564
|
+
checked={isRowSelected(row)}
|
|
565
|
+
onclick={(e) => {
|
|
566
|
+
e.stopPropagation(); // Prevent row click
|
|
567
|
+
toggleRowSelection(row);
|
|
568
|
+
}}
|
|
569
|
+
aria-label={`Select row ${rowIndex + 1}`}
|
|
570
|
+
/>
|
|
571
|
+
</td>
|
|
572
|
+
{/if}
|
|
573
|
+
|
|
574
|
+
{#each columns as column (column.key)}
|
|
575
|
+
<td
|
|
576
|
+
class={cn(
|
|
577
|
+
tdClasses,
|
|
578
|
+
column.align === 'center' && 'text-center',
|
|
579
|
+
column.align === 'right' && 'text-right',
|
|
580
|
+
column.class
|
|
581
|
+
)}
|
|
582
|
+
>
|
|
583
|
+
{#if column.cell}
|
|
584
|
+
{@render column.cell(row, column.key, rowIndex)}
|
|
585
|
+
{:else if row[column.key] === undefined || row[column.key] === null}
|
|
586
|
+
<span class="text-default-300">—</span>
|
|
587
|
+
{:else}
|
|
588
|
+
{row[column.key]}
|
|
589
|
+
{/if}
|
|
590
|
+
</td>
|
|
591
|
+
{/each}
|
|
592
|
+
</tr>
|
|
593
|
+
{#if expandedContent}
|
|
594
|
+
<tr class="expandedContent-row">
|
|
595
|
+
<td colspan={selectable ? columns.length + 1 : columns.length} class="border-0 p-0">
|
|
596
|
+
{@render expandedContent(row)}
|
|
597
|
+
</td>
|
|
598
|
+
</tr>
|
|
599
|
+
{/if}
|
|
600
|
+
{/each}
|
|
601
|
+
{/if}
|
|
602
|
+
</tbody>
|
|
603
|
+
</table>
|
|
604
|
+
</div>
|
|
605
|
+
|
|
606
|
+
{#if showPaginationControls && (paginationPosition === 'bottom' || paginationPosition === 'both')}
|
|
607
|
+
<div class={footerClasses}>
|
|
608
|
+
<div class={paginationClasses}>
|
|
609
|
+
<div class="flex items-center gap-2">
|
|
610
|
+
{#if showPageSize}
|
|
611
|
+
<div class="flex items-center gap-2">
|
|
612
|
+
<label for="table-page-size-bottom" class="text-default-500 text-sm">Show</label>
|
|
613
|
+
<select
|
|
614
|
+
id="table-page-size-bottom"
|
|
615
|
+
class="border-default-200 rounded-md border px-2 py-1 text-sm"
|
|
616
|
+
value={internalPageSize}
|
|
617
|
+
onchange={handlePageSizeChange}
|
|
618
|
+
>
|
|
619
|
+
{#each pageSizeOptions as option}
|
|
620
|
+
<option value={option}>{option}</option>
|
|
621
|
+
{/each}
|
|
622
|
+
</select>
|
|
623
|
+
<span class="text-default-500 text-sm">entries</span>
|
|
624
|
+
</div>
|
|
625
|
+
{/if}
|
|
626
|
+
<span class="text-default-500 text-sm">
|
|
627
|
+
Showing {Math.min(
|
|
628
|
+
(internalCurrentPage - 1) * internalPageSize + 1,
|
|
629
|
+
effectiveTotalItems
|
|
630
|
+
)}
|
|
631
|
+
to {Math.min(internalCurrentPage * internalPageSize, effectiveTotalItems)} of {effectiveTotalItems}
|
|
632
|
+
entries
|
|
633
|
+
</span>
|
|
634
|
+
</div>
|
|
635
|
+
|
|
636
|
+
<div class="flex items-center gap-1">
|
|
637
|
+
{#if paginationTemplate === 'full'}
|
|
638
|
+
<!-- First page button -->
|
|
639
|
+
<button
|
|
640
|
+
type="button"
|
|
641
|
+
class={cn(
|
|
642
|
+
'relative inline-flex items-center rounded-md px-2 py-1 text-sm font-medium',
|
|
643
|
+
internalCurrentPage === 1
|
|
644
|
+
? 'text-default-300 cursor-not-allowed'
|
|
645
|
+
: 'text-default-700 hover:bg-default-100'
|
|
646
|
+
)}
|
|
647
|
+
onclick={goToFirstPage}
|
|
648
|
+
disabled={internalCurrentPage === 1}
|
|
649
|
+
aria-label="First page"
|
|
650
|
+
>
|
|
651
|
+
<!-- Double Chevron Left SVG -->
|
|
652
|
+
<svg
|
|
653
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
654
|
+
width="16"
|
|
655
|
+
height="16"
|
|
656
|
+
viewBox="0 0 24 24"
|
|
657
|
+
fill="none"
|
|
658
|
+
stroke="currentColor"
|
|
659
|
+
stroke-width="2"
|
|
660
|
+
stroke-linecap="round"
|
|
661
|
+
stroke-linejoin="round"
|
|
662
|
+
class="h-4 w-4"
|
|
663
|
+
>
|
|
664
|
+
<path d="m11 17-5-5 5-5"></path>
|
|
665
|
+
<path d="m18 17-5-5 5-5"></path>
|
|
666
|
+
</svg>
|
|
667
|
+
</button>
|
|
668
|
+
{/if}
|
|
669
|
+
|
|
670
|
+
<!-- Previous page button -->
|
|
671
|
+
<button
|
|
672
|
+
type="button"
|
|
673
|
+
class={cn(
|
|
674
|
+
'relative inline-flex items-center rounded-md px-2 py-1 text-sm font-medium',
|
|
675
|
+
internalCurrentPage === 1
|
|
676
|
+
? 'text-default-300 cursor-not-allowed'
|
|
677
|
+
: 'text-default-700 hover:bg-default-100'
|
|
678
|
+
)}
|
|
679
|
+
onclick={prevPage}
|
|
680
|
+
disabled={internalCurrentPage === 1}
|
|
681
|
+
aria-label="Previous page"
|
|
682
|
+
>
|
|
683
|
+
<!-- Chevron Left SVG -->
|
|
684
|
+
<svg
|
|
685
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
686
|
+
width="16"
|
|
687
|
+
height="16"
|
|
688
|
+
viewBox="0 0 24 24"
|
|
689
|
+
fill="none"
|
|
690
|
+
stroke="currentColor"
|
|
691
|
+
stroke-width="2"
|
|
692
|
+
stroke-linecap="round"
|
|
693
|
+
stroke-linejoin="round"
|
|
694
|
+
class="h-4 w-4"
|
|
695
|
+
>
|
|
696
|
+
<path d="m15 18-6-6 6-6"></path>
|
|
697
|
+
</svg>
|
|
698
|
+
</button>
|
|
699
|
+
|
|
700
|
+
<!-- Page numbers -->
|
|
701
|
+
{#if paginationTemplate === 'full'}
|
|
702
|
+
{#each getPageNumbers() as pageNum}
|
|
703
|
+
<button
|
|
704
|
+
type="button"
|
|
705
|
+
class={cn(
|
|
706
|
+
'relative inline-flex items-center rounded-md px-3 py-1 text-sm font-medium',
|
|
707
|
+
internalCurrentPage === pageNum
|
|
708
|
+
? 'bg-primary-100 text-primary-700'
|
|
709
|
+
: 'text-default-700 hover:bg-default-100'
|
|
710
|
+
)}
|
|
711
|
+
onclick={() => goToPage(pageNum)}
|
|
712
|
+
aria-label={`Page ${pageNum}`}
|
|
713
|
+
aria-current={internalCurrentPage === pageNum ? 'page' : undefined}
|
|
714
|
+
>
|
|
715
|
+
{pageNum}
|
|
716
|
+
</button>
|
|
717
|
+
{/each}
|
|
718
|
+
{:else}
|
|
719
|
+
<span class="text-default-500 px-2 text-sm">
|
|
720
|
+
Page {internalCurrentPage} of {totalPages}
|
|
721
|
+
</span>
|
|
722
|
+
{/if}
|
|
723
|
+
|
|
724
|
+
<!-- Next page button -->
|
|
725
|
+
<button
|
|
726
|
+
type="button"
|
|
727
|
+
class={cn(
|
|
728
|
+
'relative inline-flex items-center rounded-md px-2 py-1 text-sm font-medium',
|
|
729
|
+
internalCurrentPage === totalPages
|
|
730
|
+
? 'text-default-300 cursor-not-allowed'
|
|
731
|
+
: 'text-default-700 hover:bg-default-100'
|
|
732
|
+
)}
|
|
733
|
+
onclick={nextPage}
|
|
734
|
+
disabled={internalCurrentPage === totalPages}
|
|
735
|
+
aria-label="Next page"
|
|
736
|
+
>
|
|
737
|
+
<!-- Chevron Right SVG -->
|
|
738
|
+
<svg
|
|
739
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
740
|
+
width="16"
|
|
741
|
+
height="16"
|
|
742
|
+
viewBox="0 0 24 24"
|
|
743
|
+
fill="none"
|
|
744
|
+
stroke="currentColor"
|
|
745
|
+
stroke-width="2"
|
|
746
|
+
stroke-linecap="round"
|
|
747
|
+
stroke-linejoin="round"
|
|
748
|
+
class="h-4 w-4"
|
|
749
|
+
>
|
|
750
|
+
<path d="m9 18 6-6-6-6"></path>
|
|
751
|
+
</svg>
|
|
752
|
+
</button>
|
|
753
|
+
|
|
754
|
+
{#if paginationTemplate === 'full'}
|
|
755
|
+
<!-- Last page button -->
|
|
756
|
+
<button
|
|
757
|
+
type="button"
|
|
758
|
+
class={cn(
|
|
759
|
+
'relative inline-flex items-center rounded-md px-2 py-1 text-sm font-medium',
|
|
760
|
+
internalCurrentPage === totalPages
|
|
761
|
+
? 'text-default-300 cursor-not-allowed'
|
|
762
|
+
: 'text-default-700 hover:bg-default-100'
|
|
763
|
+
)}
|
|
764
|
+
onclick={goToLastPage}
|
|
765
|
+
disabled={internalCurrentPage === totalPages}
|
|
766
|
+
aria-label="Last page"
|
|
767
|
+
>
|
|
768
|
+
<!-- Double Chevron Right SVG -->
|
|
769
|
+
<svg
|
|
770
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
771
|
+
width="16"
|
|
772
|
+
height="16"
|
|
773
|
+
viewBox="0 0 24 24"
|
|
774
|
+
fill="none"
|
|
775
|
+
stroke="currentColor"
|
|
776
|
+
stroke-width="2"
|
|
777
|
+
stroke-linecap="round"
|
|
778
|
+
stroke-linejoin="round"
|
|
779
|
+
class="h-4 w-4"
|
|
780
|
+
>
|
|
781
|
+
<path d="m13 17 5-5-5-5"></path>
|
|
782
|
+
<path d="m6 17 5-5-5-5"></path>
|
|
783
|
+
</svg>
|
|
784
|
+
</button>
|
|
785
|
+
{/if}
|
|
786
|
+
</div>
|
|
787
|
+
</div>
|
|
788
|
+
</div>
|
|
789
|
+
{/if}
|
|
790
|
+
</div>
|