@makolabs/ripple 1.7.10 → 1.8.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/dist/adapters/ai/OpenAIAdapter.d.ts +8 -1
- package/dist/adapters/ai/OpenAIAdapter.js +2 -2
- package/dist/adapters/storage/BaseAdapter.js +2 -2
- package/dist/adapters/storage/S3Adapter.js +1 -6
- package/dist/adapters/storage/types.d.ts +3 -3
- package/dist/ai/AIChatInterface.svelte +0 -1
- package/dist/ai/ai-chat-interface.d.ts +21 -22
- package/dist/ai/content-detector.js +0 -1
- package/dist/button/Button.svelte +9 -2
- package/dist/button/button.d.ts +39 -40
- package/dist/charts/Chart.svelte +4 -1
- package/dist/drawer/Drawer.svelte +57 -23
- package/dist/drawer/drawer.d.ts +18 -19
- package/dist/elements/accordion/Accordion.svelte +39 -18
- package/dist/elements/accordion/accordion.d.ts +21 -22
- package/dist/elements/alert/Alert.svelte +20 -8
- package/dist/elements/badge/Badge.svelte +5 -2
- package/dist/elements/badge/badge.d.ts +39 -40
- package/dist/elements/dropdown/Dropdown.svelte +18 -2
- package/dist/elements/dropdown/Select.svelte +17 -5
- package/dist/elements/dropdown/dropdown.d.ts +18 -19
- package/dist/elements/dropdown/select.d.ts +18 -19
- package/dist/elements/pagination/Pagination.svelte +15 -2
- package/dist/elements/pagination/Pagination.svelte.d.ts +1 -0
- package/dist/forms/Checkbox.svelte +16 -4
- package/dist/forms/Form.svelte +0 -2
- package/dist/forms/Input.svelte +16 -4
- package/dist/forms/NumberInput.svelte +8 -1
- package/dist/forms/RadioInputs.svelte +14 -5
- package/dist/forms/Slider.svelte +6 -4
- package/dist/forms/Toggle.svelte +67 -29
- package/dist/forms/slider.d.ts +72 -10
- package/dist/forms/slider.js +21 -0
- package/dist/header/Breadcrumbs.svelte +47 -24
- package/dist/header/PageHeader.svelte +12 -2
- package/dist/header/breadcrumbs.d.ts +47 -39
- package/dist/helper/deprecation.d.ts +14 -0
- package/dist/helper/deprecation.js +24 -0
- package/dist/helper/testid.d.ts +10 -0
- package/dist/helper/testid.js +17 -0
- package/dist/index.d.ts +147 -47
- package/dist/index.js +1 -0
- package/dist/layout/activity-list/activity-list.d.ts +21 -22
- package/dist/layout/card/Card.svelte +19 -5
- package/dist/layout/card/card.d.ts +21 -22
- package/dist/layout/card/ranked-card.d.ts +2 -1
- package/dist/layout/navbar/Navbar.svelte +14 -16
- package/dist/layout/navbar/navbar.d.ts +19 -19
- package/dist/layout/sidebar/Sidebar.svelte +6 -3
- package/dist/layout/table/Table.svelte +237 -303
- package/dist/layout/table/table.d.ts +24 -25
- package/dist/layout/tabs/Tab.svelte +3 -1
- package/dist/layout/tabs/TabGroup.svelte +7 -4
- package/dist/layout/tabs/tabs.d.ts +39 -40
- package/dist/modal/Modal.svelte +124 -21
- package/dist/modal/modal.d.ts +18 -19
- package/dist/modal/modal.js +2 -2
- package/dist/user-management/UserModal.svelte +1 -1
- package/dist/user-management/UserTable.svelte +3 -3
- package/dist/user-management/UserViewModal.svelte +2 -2
- package/dist/variants.d.ts +13 -13
- package/package.json +9 -15
- package/dist/ai/AIChatInterfaceTestWrapper.svelte +0 -26
- package/dist/ai/AIChatInterfaceTestWrapper.svelte.d.ts +0 -17
- package/dist/button/ButtonTestWrapper.svelte +0 -10
- package/dist/button/ButtonTestWrapper.svelte.d.ts +0 -7
- package/dist/drawer/DrawerTestWrapper.svelte +0 -19
- package/dist/drawer/DrawerTestWrapper.svelte.d.ts +0 -9
- package/dist/elements/accordion/AccordionTestWrapper.svelte +0 -21
- package/dist/elements/accordion/AccordionTestWrapper.svelte.d.ts +0 -10
- package/dist/elements/badge/BadgeTestWrapper.svelte +0 -14
- package/dist/elements/badge/BadgeTestWrapper.svelte.d.ts +0 -9
- package/dist/forms/CheckboxTestWrapper.svelte +0 -8
- package/dist/forms/CheckboxTestWrapper.svelte.d.ts +0 -4
- package/dist/forms/InputTestWrapper.svelte +0 -8
- package/dist/forms/InputTestWrapper.svelte.d.ts +0 -4
- package/dist/forms/ToggleTestWrapper.svelte +0 -8
- package/dist/forms/ToggleTestWrapper.svelte.d.ts +0 -7
- package/dist/layout/card/CardTestWrapper.svelte +0 -15
- package/dist/layout/card/CardTestWrapper.svelte.d.ts +0 -7
- package/dist/modal/ModalTestWrapper.svelte +0 -20
- package/dist/modal/ModalTestWrapper.svelte.d.ts +0 -8
- package/dist/user-management/UserManagementTestWrapper.svelte +0 -32
- package/dist/user-management/UserManagementTestWrapper.svelte.d.ts +0 -12
- package/dist/user-management/UserModalTestWrapper.svelte +0 -22
- package/dist/user-management/UserModalTestWrapper.svelte.d.ts +0 -7
- package/dist/user-management/UserTableTestWrapper.svelte +0 -41
- package/dist/user-management/UserTableTestWrapper.svelte.d.ts +0 -7
- package/dist/user-management/UserViewModalTestWrapper.svelte +0 -22
- package/dist/user-management/UserViewModalTestWrapper.svelte.d.ts +0 -7
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { cn } from '../../helper/cls.js';
|
|
3
3
|
import { table } from './table.js';
|
|
4
|
+
import { buildTestId } from '../../helper/testid.js';
|
|
5
|
+
import { warnDeprecatedProps } from '../../helper/deprecation.js';
|
|
4
6
|
import type { TableProps, SortDirection, SortState, DataRow } from '../../index.js';
|
|
5
7
|
import Pagination from '../../elements/pagination/Pagination.svelte';
|
|
6
8
|
import Card from '../../layout/card/Card.svelte';
|
|
9
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
7
10
|
|
|
8
11
|
let {
|
|
9
12
|
data = [],
|
|
@@ -16,21 +19,31 @@
|
|
|
16
19
|
selectable = false,
|
|
17
20
|
selected = $bindable([]),
|
|
18
21
|
onrowclick,
|
|
19
|
-
onsort
|
|
22
|
+
onsort,
|
|
20
23
|
onselect = () => {},
|
|
21
24
|
onpagechange,
|
|
22
25
|
onpagesizechange,
|
|
23
26
|
class: classname = '',
|
|
24
|
-
wrapperclass
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
wrapperclass,
|
|
28
|
+
wrapperClass = wrapperclass ?? '',
|
|
29
|
+
tableclass,
|
|
30
|
+
tableClass = tableclass ?? '',
|
|
31
|
+
theadclass,
|
|
32
|
+
theadClass = theadclass ?? '',
|
|
33
|
+
tbodyclass,
|
|
34
|
+
tbodyClass = tbodyclass ?? '',
|
|
35
|
+
trclass,
|
|
36
|
+
trClass = trclass ?? 'bg-white',
|
|
37
|
+
thclass,
|
|
38
|
+
thClass = thclass ?? '',
|
|
39
|
+
tdclass,
|
|
40
|
+
tdClass = tdclass ?? '',
|
|
41
|
+
footerclass,
|
|
42
|
+
footerClass = footerclass ?? '',
|
|
43
|
+
paginationclass,
|
|
44
|
+
paginationClass = paginationclass ?? '',
|
|
45
|
+
rowclass,
|
|
46
|
+
rowClass = rowclass ?? (() => ''),
|
|
34
47
|
loading = false,
|
|
35
48
|
expandedContent,
|
|
36
49
|
pagination = true,
|
|
@@ -41,9 +54,42 @@
|
|
|
41
54
|
paginationTemplate = 'full',
|
|
42
55
|
title,
|
|
43
56
|
subtitle,
|
|
44
|
-
headerActions
|
|
57
|
+
headerActions,
|
|
58
|
+
selectAllScope = 'page',
|
|
59
|
+
rowKey,
|
|
60
|
+
expandable = false,
|
|
61
|
+
testId
|
|
45
62
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
63
|
}: TableProps<any> = $props();
|
|
64
|
+
|
|
65
|
+
warnDeprecatedProps(
|
|
66
|
+
'Table',
|
|
67
|
+
{
|
|
68
|
+
wrapperclass,
|
|
69
|
+
tableclass,
|
|
70
|
+
theadclass,
|
|
71
|
+
tbodyclass,
|
|
72
|
+
trclass,
|
|
73
|
+
thclass,
|
|
74
|
+
tdclass,
|
|
75
|
+
footerclass,
|
|
76
|
+
paginationclass,
|
|
77
|
+
rowclass
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
wrapperclass: 'wrapperClass',
|
|
81
|
+
tableclass: 'tableClass',
|
|
82
|
+
theadclass: 'theadClass',
|
|
83
|
+
tbodyclass: 'tbodyClass',
|
|
84
|
+
trclass: 'trClass',
|
|
85
|
+
thclass: 'thClass',
|
|
86
|
+
tdclass: 'tdClass',
|
|
87
|
+
footerclass: 'footerClass',
|
|
88
|
+
paginationclass: 'paginationClass',
|
|
89
|
+
rowclass: 'rowClass'
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
|
|
47
93
|
// Determine if we should use Card wrapper
|
|
48
94
|
const hasHeader = $derived(title !== undefined || subtitle !== undefined);
|
|
49
95
|
|
|
@@ -58,6 +104,7 @@
|
|
|
58
104
|
let sortDirection = $state<SortDirection>(null);
|
|
59
105
|
let internalCurrentPage = $state(externalCurrentPage || 1);
|
|
60
106
|
let internalPageSize = $state(pageSize);
|
|
107
|
+
let expandedRows = new SvelteSet<string | number>();
|
|
61
108
|
|
|
62
109
|
// Use external current page if provided
|
|
63
110
|
$effect(() => {
|
|
@@ -109,24 +156,52 @@
|
|
|
109
156
|
const paginationClasses = $derived(cn(paginationBaseClass(), paginationClass));
|
|
110
157
|
const emptyStateClasses = $derived(emptyStateBaseClass());
|
|
111
158
|
|
|
159
|
+
// Apply client-side sorting when no onsort handler is provided
|
|
160
|
+
const sortedData = $derived.by<DataRow[]>(() => {
|
|
161
|
+
if (onsort || !sortColumn || !sortDirection) {
|
|
162
|
+
return data;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return [...data].sort((a, b) => {
|
|
166
|
+
const aVal = a[sortColumn];
|
|
167
|
+
const bVal = b[sortColumn];
|
|
168
|
+
|
|
169
|
+
// Handle null/undefined
|
|
170
|
+
if (aVal == null && bVal == null) return 0;
|
|
171
|
+
if (aVal == null) return sortDirection === 'asc' ? -1 : 1;
|
|
172
|
+
if (bVal == null) return sortDirection === 'asc' ? 1 : -1;
|
|
173
|
+
|
|
174
|
+
// Numeric comparison
|
|
175
|
+
if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
176
|
+
return sortDirection === 'asc' ? aVal - bVal : bVal - aVal;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// String comparison
|
|
180
|
+
const aStr = String(aVal);
|
|
181
|
+
const bStr = String(bVal);
|
|
182
|
+
const cmp = aStr.localeCompare(bStr);
|
|
183
|
+
return sortDirection === 'asc' ? cmp : -cmp;
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
112
187
|
// Handle pagination
|
|
113
188
|
function getPaginatedData() {
|
|
114
189
|
// If no pagination or all data fits on one page, return all data
|
|
115
|
-
if (!pagination ||
|
|
116
|
-
return
|
|
190
|
+
if (!pagination || sortedData.length <= internalPageSize) {
|
|
191
|
+
return sortedData;
|
|
117
192
|
}
|
|
118
193
|
|
|
119
194
|
// If external data source might be handling pagination
|
|
120
195
|
if (totalItems !== undefined) {
|
|
121
196
|
// If data.length is less than or equal to pageSize, assume it's already paginated
|
|
122
|
-
if (
|
|
123
|
-
return
|
|
197
|
+
if (sortedData.length <= internalPageSize) {
|
|
198
|
+
return sortedData;
|
|
124
199
|
}
|
|
125
200
|
}
|
|
126
201
|
|
|
127
202
|
// Otherwise, handle pagination internally
|
|
128
203
|
const startIndex = (internalCurrentPage - 1) * internalPageSize;
|
|
129
|
-
return
|
|
204
|
+
return sortedData.slice(startIndex, startIndex + internalPageSize);
|
|
130
205
|
}
|
|
131
206
|
|
|
132
207
|
// Handle sorting
|
|
@@ -150,26 +225,86 @@
|
|
|
150
225
|
}
|
|
151
226
|
|
|
152
227
|
const newSortState: SortState = { column: sortColumn, direction: sortDirection };
|
|
153
|
-
onsort(newSortState);
|
|
228
|
+
onsort?.(newSortState);
|
|
154
229
|
}
|
|
155
230
|
|
|
156
231
|
function toggleRowSelection(row: DataRow) {
|
|
157
232
|
if (!selectable) return;
|
|
158
233
|
|
|
159
|
-
const index =
|
|
234
|
+
const index = rowKey
|
|
235
|
+
? selected.findIndex((r) => r[rowKey] === row[rowKey])
|
|
236
|
+
: selected.findIndex((r) => r === row);
|
|
160
237
|
if (index === -1) {
|
|
161
238
|
selected = [...selected, row];
|
|
162
239
|
} else {
|
|
163
|
-
selected =
|
|
240
|
+
selected = rowKey
|
|
241
|
+
? selected.filter((r) => r[rowKey] !== row[rowKey])
|
|
242
|
+
: selected.filter((r) => r !== row);
|
|
164
243
|
}
|
|
165
244
|
|
|
166
245
|
onselect(selected);
|
|
167
246
|
}
|
|
168
247
|
|
|
169
248
|
function isRowSelected(row: DataRow) {
|
|
249
|
+
if (rowKey) return selected.some((r) => r[rowKey] === row[rowKey]);
|
|
170
250
|
return selected.includes(row);
|
|
171
251
|
}
|
|
172
252
|
|
|
253
|
+
function getRowExpandKey(row: DataRow, index: number): string | number {
|
|
254
|
+
return rowKey ? row[rowKey] : index;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function toggleRowExpanded(row: DataRow, index: number) {
|
|
258
|
+
const key = getRowExpandKey(row, index);
|
|
259
|
+
if (expandedRows.has(key)) {
|
|
260
|
+
expandedRows.delete(key);
|
|
261
|
+
} else {
|
|
262
|
+
expandedRows.add(key);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function isRowExpanded(row: DataRow, index: number): boolean {
|
|
267
|
+
return expandedRows.has(getRowExpandKey(row, index));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Total column count including selectable checkbox and expandable chevron columns
|
|
271
|
+
const totalColumnCount = $derived(
|
|
272
|
+
columns.length + (selectable ? 1 : 0) + (expandable && expandedContent ? 1 : 0)
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
function handleSelectAll() {
|
|
276
|
+
const pageData = getPaginatedData();
|
|
277
|
+
const scopeData = selectAllScope === 'all' ? data : pageData;
|
|
278
|
+
const allSelected = scopeData.every((r) => isRowSelected(r));
|
|
279
|
+
if (allSelected) {
|
|
280
|
+
if (selectAllScope === 'all') {
|
|
281
|
+
selected = [];
|
|
282
|
+
} else {
|
|
283
|
+
selected = selected.filter(
|
|
284
|
+
(r) => !pageData.some((pr) => (rowKey ? pr[rowKey] === r[rowKey] : pr === r))
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
if (selectAllScope === 'all') {
|
|
289
|
+
selected = [...data];
|
|
290
|
+
} else {
|
|
291
|
+
const newSelected = [...selected];
|
|
292
|
+
for (const r of pageData) {
|
|
293
|
+
if (!isRowSelected(r)) {
|
|
294
|
+
newSelected.push(r);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
selected = newSelected;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
onselect(selected);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function isAllSelected(): boolean {
|
|
304
|
+
const scopeData = selectAllScope === 'all' ? data : getPaginatedData();
|
|
305
|
+
return scopeData.length > 0 && scopeData.every((r) => isRowSelected(r));
|
|
306
|
+
}
|
|
307
|
+
|
|
173
308
|
function handleRowClick(row: DataRow, index: number) {
|
|
174
309
|
onrowclick?.(row, index);
|
|
175
310
|
}
|
|
@@ -193,263 +328,7 @@
|
|
|
193
328
|
}
|
|
194
329
|
</script>
|
|
195
330
|
|
|
196
|
-
{#
|
|
197
|
-
<Card>
|
|
198
|
-
{#snippet custom()}
|
|
199
|
-
<!-- Header Section -->
|
|
200
|
-
<div class="border-default-200 mb-4 border-b pb-3">
|
|
201
|
-
<div class="flex items-center justify-between">
|
|
202
|
-
<div>
|
|
203
|
-
{#if title}
|
|
204
|
-
<h2 class="text-default-900 text-lg font-semibold">{title}</h2>
|
|
205
|
-
{/if}
|
|
206
|
-
{#if subtitle}
|
|
207
|
-
<p class="text-default-500 mt-1 text-xs">{subtitle}</p>
|
|
208
|
-
{/if}
|
|
209
|
-
</div>
|
|
210
|
-
{#if headerActions}
|
|
211
|
-
<div class="flex items-center">
|
|
212
|
-
{@render headerActions()}
|
|
213
|
-
</div>
|
|
214
|
-
{/if}
|
|
215
|
-
</div>
|
|
216
|
-
</div>
|
|
217
|
-
|
|
218
|
-
<!-- Table Content -->
|
|
219
|
-
<div class={baseClasses}>
|
|
220
|
-
{#if showPaginationControls && (paginationPosition === 'top' || paginationPosition === 'both')}
|
|
221
|
-
<div class={footerClasses}>
|
|
222
|
-
<Pagination
|
|
223
|
-
currentPage={internalCurrentPage}
|
|
224
|
-
totalItems={effectiveTotalItems}
|
|
225
|
-
pageSize={internalPageSize}
|
|
226
|
-
onPageChange={handlePageChange}
|
|
227
|
-
onPageSizeChange={handlePageSizeChange}
|
|
228
|
-
{showPageSize}
|
|
229
|
-
{pageSizeOptions}
|
|
230
|
-
template={paginationTemplate === 'full' ? 'full' : 'compact'}
|
|
231
|
-
disabled={loading}
|
|
232
|
-
class={cn(paginationClasses, paginationClass)}
|
|
233
|
-
/>
|
|
234
|
-
</div>
|
|
235
|
-
{/if}
|
|
236
|
-
|
|
237
|
-
<div class={wrapperClasses}>
|
|
238
|
-
<table class={tableClasses}>
|
|
239
|
-
<thead class={theadClasses}>
|
|
240
|
-
<tr>
|
|
241
|
-
{#if selectable}
|
|
242
|
-
<th class={cn(thClasses, 'text-center')}>
|
|
243
|
-
<input
|
|
244
|
-
type="checkbox"
|
|
245
|
-
onchange={() => {
|
|
246
|
-
if (selected.length === data.length) {
|
|
247
|
-
selected = [];
|
|
248
|
-
} else {
|
|
249
|
-
selected = [...data];
|
|
250
|
-
}
|
|
251
|
-
onselect(selected);
|
|
252
|
-
}}
|
|
253
|
-
checked={selected.length === data.length && data.length > 0}
|
|
254
|
-
aria-label="Select all rows"
|
|
255
|
-
/>
|
|
256
|
-
</th>
|
|
257
|
-
{/if}
|
|
258
|
-
|
|
259
|
-
{#each columns as column (column.key)}
|
|
260
|
-
<th
|
|
261
|
-
class={cn(
|
|
262
|
-
thClasses,
|
|
263
|
-
column.align === 'center' && 'text-center',
|
|
264
|
-
column.align === 'right' && 'text-right',
|
|
265
|
-
column.class
|
|
266
|
-
)}
|
|
267
|
-
style={column.width ? `width: ${column.width}` : undefined}
|
|
268
|
-
>
|
|
269
|
-
{#if column.sortable}
|
|
270
|
-
<button
|
|
271
|
-
type="button"
|
|
272
|
-
class={sortButtonBaseClass()}
|
|
273
|
-
onclick={() => toggleSort(column.sortKey || column.key)}
|
|
274
|
-
aria-label={`Sort by ${column.header}`}
|
|
275
|
-
>
|
|
276
|
-
{column.header}
|
|
277
|
-
<span class={sortIconBaseClass()}>
|
|
278
|
-
{#if sortColumn === (column.sortKey || column.key)}
|
|
279
|
-
{#if sortDirection === 'asc'}
|
|
280
|
-
<svg
|
|
281
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
282
|
-
viewBox="0 0 20 20"
|
|
283
|
-
fill="currentColor"
|
|
284
|
-
class="h-4 w-4"
|
|
285
|
-
>
|
|
286
|
-
<path
|
|
287
|
-
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"
|
|
288
|
-
/>
|
|
289
|
-
</svg>
|
|
290
|
-
{:else if sortDirection === 'desc'}
|
|
291
|
-
<svg
|
|
292
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
293
|
-
viewBox="0 0 20 20"
|
|
294
|
-
fill="currentColor"
|
|
295
|
-
class="h-4 w-4"
|
|
296
|
-
>
|
|
297
|
-
<path
|
|
298
|
-
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"
|
|
299
|
-
/>
|
|
300
|
-
</svg>
|
|
301
|
-
{/if}
|
|
302
|
-
{:else}
|
|
303
|
-
<svg
|
|
304
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
305
|
-
fill="none"
|
|
306
|
-
viewBox="0 0 24 24"
|
|
307
|
-
stroke-width="1.5"
|
|
308
|
-
stroke="currentColor"
|
|
309
|
-
class="h-4 w-4 opacity-40"
|
|
310
|
-
>
|
|
311
|
-
<path
|
|
312
|
-
stroke-linecap="round"
|
|
313
|
-
stroke-linejoin="round"
|
|
314
|
-
d="M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9"
|
|
315
|
-
/>
|
|
316
|
-
</svg>
|
|
317
|
-
{/if}
|
|
318
|
-
</span>
|
|
319
|
-
</button>
|
|
320
|
-
{:else}
|
|
321
|
-
{column.header}
|
|
322
|
-
{/if}
|
|
323
|
-
</th>
|
|
324
|
-
{/each}
|
|
325
|
-
</tr>
|
|
326
|
-
</thead>
|
|
327
|
-
|
|
328
|
-
<tbody class={tbodyClasses}>
|
|
329
|
-
{#if loading}
|
|
330
|
-
{#each Array.from({ length: Math.min(internalPageSize, 5) }, (__, i) => i) as rowIdx (rowIdx)}
|
|
331
|
-
<tr class={cn(trClass, rowIdx % 2 === 1 && striped ? 'bg-default-50' : '')}>
|
|
332
|
-
{#if selectable}
|
|
333
|
-
<td class={cn(tdClasses, 'text-center')}>
|
|
334
|
-
<div class="bg-default-200 mx-auto h-4 w-4 animate-pulse rounded"></div>
|
|
335
|
-
</td>
|
|
336
|
-
{/if}
|
|
337
|
-
{#each columns as column, colIdx (column.key)}
|
|
338
|
-
<td class={cn(tdClasses, column.class)}>
|
|
339
|
-
{#if colIdx === 0}
|
|
340
|
-
<div class="flex items-center gap-3">
|
|
341
|
-
<div
|
|
342
|
-
class="bg-default-200 h-8 w-8 shrink-0 animate-pulse rounded-full"
|
|
343
|
-
></div>
|
|
344
|
-
<div class="flex-1 space-y-2">
|
|
345
|
-
<div class="bg-default-200 h-3.5 w-28 animate-pulse rounded"></div>
|
|
346
|
-
<div class="bg-default-100 h-3 w-20 animate-pulse rounded"></div>
|
|
347
|
-
</div>
|
|
348
|
-
</div>
|
|
349
|
-
{:else if colIdx === columns.length - 1}
|
|
350
|
-
<div class="flex items-center justify-end gap-2">
|
|
351
|
-
<div class="bg-default-200 h-5 w-5 animate-pulse rounded"></div>
|
|
352
|
-
<div class="bg-default-200 h-5 w-5 animate-pulse rounded"></div>
|
|
353
|
-
<div class="bg-default-200 h-5 w-5 animate-pulse rounded"></div>
|
|
354
|
-
</div>
|
|
355
|
-
{:else}
|
|
356
|
-
<div
|
|
357
|
-
class="bg-default-200 h-3.5 animate-pulse rounded"
|
|
358
|
-
style="width: {60 + ((rowIdx * 17 + colIdx * 31) % 40)}%"
|
|
359
|
-
></div>
|
|
360
|
-
{/if}
|
|
361
|
-
</td>
|
|
362
|
-
{/each}
|
|
363
|
-
</tr>
|
|
364
|
-
{/each}
|
|
365
|
-
{:else if getPaginatedData().length === 0}
|
|
366
|
-
<tr>
|
|
367
|
-
<td
|
|
368
|
-
colspan={selectable ? columns.length + 1 : columns.length}
|
|
369
|
-
class={emptyStateClasses}
|
|
370
|
-
>
|
|
371
|
-
No data available
|
|
372
|
-
</td>
|
|
373
|
-
</tr>
|
|
374
|
-
{:else}
|
|
375
|
-
{#each getPaginatedData() as row, rowIndex (rowIndex)}
|
|
376
|
-
<tr
|
|
377
|
-
class={cn(trClasses, rowclass(row, rowIndex), {
|
|
378
|
-
'bg-primary-100': selectable && isRowSelected(row),
|
|
379
|
-
'cursor-pointer': onrowclick
|
|
380
|
-
})}
|
|
381
|
-
onclick={() => handleRowClick(row, rowIndex)}
|
|
382
|
-
aria-selected={selectable && isRowSelected(row)}
|
|
383
|
-
>
|
|
384
|
-
{#if selectable}
|
|
385
|
-
<td class={cn(tdClasses, 'text-center')}>
|
|
386
|
-
<input
|
|
387
|
-
type="checkbox"
|
|
388
|
-
checked={isRowSelected(row)}
|
|
389
|
-
onclick={(e) => {
|
|
390
|
-
e.stopPropagation(); // Prevent row click
|
|
391
|
-
toggleRowSelection(row);
|
|
392
|
-
}}
|
|
393
|
-
aria-label={`Select row ${rowIndex + 1}`}
|
|
394
|
-
/>
|
|
395
|
-
</td>
|
|
396
|
-
{/if}
|
|
397
|
-
|
|
398
|
-
{#each columns as column (column.key)}
|
|
399
|
-
<td
|
|
400
|
-
class={cn(
|
|
401
|
-
tdClasses,
|
|
402
|
-
column.align === 'center' && 'text-center',
|
|
403
|
-
column.align === 'right' && 'text-right',
|
|
404
|
-
column.class
|
|
405
|
-
)}
|
|
406
|
-
>
|
|
407
|
-
{#if column.cell}
|
|
408
|
-
{@render column.cell(row, column.key, rowIndex)}
|
|
409
|
-
{:else if row[column.key] === undefined || row[column.key] === null}
|
|
410
|
-
<span class="text-default-300">—</span>
|
|
411
|
-
{:else}
|
|
412
|
-
{row[column.key]}
|
|
413
|
-
{/if}
|
|
414
|
-
</td>
|
|
415
|
-
{/each}
|
|
416
|
-
</tr>
|
|
417
|
-
{#if expandedContent}
|
|
418
|
-
<tr class="expandedContent-row">
|
|
419
|
-
<td
|
|
420
|
-
colspan={selectable ? columns.length + 1 : columns.length}
|
|
421
|
-
class="border-0 p-0"
|
|
422
|
-
>
|
|
423
|
-
{@render expandedContent(row)}
|
|
424
|
-
</td>
|
|
425
|
-
</tr>
|
|
426
|
-
{/if}
|
|
427
|
-
{/each}
|
|
428
|
-
{/if}
|
|
429
|
-
</tbody>
|
|
430
|
-
</table>
|
|
431
|
-
</div>
|
|
432
|
-
|
|
433
|
-
{#if showPaginationControls && (paginationPosition === 'bottom' || paginationPosition === 'both')}
|
|
434
|
-
<div class={footerClasses}>
|
|
435
|
-
<Pagination
|
|
436
|
-
currentPage={internalCurrentPage}
|
|
437
|
-
totalItems={effectiveTotalItems}
|
|
438
|
-
pageSize={internalPageSize}
|
|
439
|
-
onPageChange={handlePageChange}
|
|
440
|
-
onPageSizeChange={handlePageSizeChange}
|
|
441
|
-
{showPageSize}
|
|
442
|
-
{pageSizeOptions}
|
|
443
|
-
template={paginationTemplate === 'full' ? 'full' : 'compact'}
|
|
444
|
-
disabled={loading}
|
|
445
|
-
class={cn(paginationClasses, paginationClass)}
|
|
446
|
-
/>
|
|
447
|
-
</div>
|
|
448
|
-
{/if}
|
|
449
|
-
</div>
|
|
450
|
-
{/snippet}
|
|
451
|
-
</Card>
|
|
452
|
-
{:else}
|
|
331
|
+
{#snippet tableContent()}
|
|
453
332
|
<div class={baseClasses}>
|
|
454
333
|
{#if showPaginationControls && (paginationPosition === 'top' || paginationPosition === 'both')}
|
|
455
334
|
<div class={footerClasses}>
|
|
@@ -469,28 +348,25 @@
|
|
|
469
348
|
{/if}
|
|
470
349
|
|
|
471
350
|
<div class={wrapperClasses}>
|
|
472
|
-
<table class={tableClasses}>
|
|
473
|
-
<thead class={theadClasses}>
|
|
351
|
+
<table class={tableClasses} data-testid={buildTestId('table', undefined, testId)}>
|
|
352
|
+
<thead class={theadClasses} data-testid={buildTestId('table', 'head', testId)}>
|
|
474
353
|
<tr>
|
|
354
|
+
{#if expandable && expandedContent}
|
|
355
|
+
<th class={cn(thClasses, 'w-10')}></th>
|
|
356
|
+
{/if}
|
|
475
357
|
{#if selectable}
|
|
476
358
|
<th class={cn(thClasses, 'text-center')}>
|
|
477
359
|
<input
|
|
478
360
|
type="checkbox"
|
|
479
|
-
onchange={
|
|
480
|
-
|
|
481
|
-
selected = [];
|
|
482
|
-
} else {
|
|
483
|
-
selected = [...data];
|
|
484
|
-
}
|
|
485
|
-
onselect(selected);
|
|
486
|
-
}}
|
|
487
|
-
checked={selected.length === data.length && data.length > 0}
|
|
361
|
+
onchange={handleSelectAll}
|
|
362
|
+
checked={isAllSelected()}
|
|
488
363
|
aria-label="Select all rows"
|
|
364
|
+
data-testid={buildTestId('table', 'select-all', testId)}
|
|
489
365
|
/>
|
|
490
366
|
</th>
|
|
491
367
|
{/if}
|
|
492
368
|
|
|
493
|
-
{#each columns as column (column.key)}
|
|
369
|
+
{#each columns as column, colIndex (column.key)}
|
|
494
370
|
<th
|
|
495
371
|
class={cn(
|
|
496
372
|
thClasses,
|
|
@@ -506,6 +382,7 @@
|
|
|
506
382
|
class={sortButtonBaseClass()}
|
|
507
383
|
onclick={() => toggleSort(column.sortKey || column.key)}
|
|
508
384
|
aria-label={`Sort by ${column.header}`}
|
|
385
|
+
data-testid={buildTestId('table', 'sort', testId, colIndex)}
|
|
509
386
|
>
|
|
510
387
|
{column.header}
|
|
511
388
|
<span class={sortIconBaseClass()}>
|
|
@@ -559,10 +436,15 @@
|
|
|
559
436
|
</tr>
|
|
560
437
|
</thead>
|
|
561
438
|
|
|
562
|
-
<tbody class={tbodyClasses}>
|
|
439
|
+
<tbody class={tbodyClasses} data-testid={buildTestId('table', 'body', testId)}>
|
|
563
440
|
{#if loading}
|
|
564
441
|
{#each Array.from({ length: Math.min(internalPageSize, 5) }, (__, i) => i) as rowIdx (rowIdx)}
|
|
565
|
-
<tr class={cn(
|
|
442
|
+
<tr class={cn(trClasses, rowIdx % 2 === 1 && striped ? 'bg-default-50' : '')}>
|
|
443
|
+
{#if expandable && expandedContent}
|
|
444
|
+
<td class={cn(tdClasses, 'w-10')}>
|
|
445
|
+
<div class="bg-default-200 mx-auto h-4 w-4 animate-pulse rounded"></div>
|
|
446
|
+
</td>
|
|
447
|
+
{/if}
|
|
566
448
|
{#if selectable}
|
|
567
449
|
<td class={cn(tdClasses, 'text-center')}>
|
|
568
450
|
<div class="bg-default-200 mx-auto h-4 w-4 animate-pulse rounded"></div>
|
|
@@ -598,23 +480,48 @@
|
|
|
598
480
|
{/each}
|
|
599
481
|
{:else if getPaginatedData().length === 0}
|
|
600
482
|
<tr>
|
|
601
|
-
<td
|
|
602
|
-
colspan={selectable ? columns.length + 1 : columns.length}
|
|
603
|
-
class={emptyStateClasses}
|
|
604
|
-
>
|
|
605
|
-
No data available
|
|
606
|
-
</td>
|
|
483
|
+
<td colspan={totalColumnCount} class={emptyStateClasses}> No data available </td>
|
|
607
484
|
</tr>
|
|
608
485
|
{:else}
|
|
609
486
|
{#each getPaginatedData() as row, rowIndex (rowIndex)}
|
|
610
487
|
<tr
|
|
611
|
-
class={cn(trClasses,
|
|
488
|
+
class={cn(trClasses, rowClass(row, rowIndex), {
|
|
612
489
|
'bg-primary-100': selectable && isRowSelected(row),
|
|
613
490
|
'cursor-pointer': onrowclick
|
|
614
491
|
})}
|
|
615
492
|
onclick={() => handleRowClick(row, rowIndex)}
|
|
616
493
|
aria-selected={selectable && isRowSelected(row)}
|
|
494
|
+
data-testid={buildTestId('table', 'row', testId, rowIndex)}
|
|
617
495
|
>
|
|
496
|
+
{#if expandable && expandedContent}
|
|
497
|
+
<td class={cn(tdClasses, 'w-10')}>
|
|
498
|
+
<button
|
|
499
|
+
type="button"
|
|
500
|
+
class="text-default-400 hover:text-default-600 flex items-center justify-center transition-transform"
|
|
501
|
+
onclick={(e) => {
|
|
502
|
+
e.stopPropagation();
|
|
503
|
+
toggleRowExpanded(row, rowIndex);
|
|
504
|
+
}}
|
|
505
|
+
aria-label={isRowExpanded(row, rowIndex) ? 'Collapse row' : 'Expand row'}
|
|
506
|
+
>
|
|
507
|
+
<svg
|
|
508
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
509
|
+
viewBox="0 0 20 20"
|
|
510
|
+
fill="currentColor"
|
|
511
|
+
class={cn(
|
|
512
|
+
'h-5 w-5 transition-transform',
|
|
513
|
+
isRowExpanded(row, rowIndex) && 'rotate-90'
|
|
514
|
+
)}
|
|
515
|
+
>
|
|
516
|
+
<path
|
|
517
|
+
fill-rule="evenodd"
|
|
518
|
+
d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
|
|
519
|
+
clip-rule="evenodd"
|
|
520
|
+
/>
|
|
521
|
+
</svg>
|
|
522
|
+
</button>
|
|
523
|
+
</td>
|
|
524
|
+
{/if}
|
|
618
525
|
{#if selectable}
|
|
619
526
|
<td class={cn(tdClasses, 'text-center')}>
|
|
620
527
|
<input
|
|
@@ -625,6 +532,7 @@
|
|
|
625
532
|
toggleRowSelection(row);
|
|
626
533
|
}}
|
|
627
534
|
aria-label={`Select row ${rowIndex + 1}`}
|
|
535
|
+
data-testid={buildTestId('table', 'row-select', testId, rowIndex)}
|
|
628
536
|
/>
|
|
629
537
|
</td>
|
|
630
538
|
{/if}
|
|
@@ -648,12 +556,9 @@
|
|
|
648
556
|
</td>
|
|
649
557
|
{/each}
|
|
650
558
|
</tr>
|
|
651
|
-
{#if expandedContent}
|
|
559
|
+
{#if expandedContent && (!expandable || isRowExpanded(row, rowIndex))}
|
|
652
560
|
<tr class="expandedContent-row">
|
|
653
|
-
<td
|
|
654
|
-
colspan={selectable ? columns.length + 1 : columns.length}
|
|
655
|
-
class="border-0 p-0"
|
|
656
|
-
>
|
|
561
|
+
<td colspan={totalColumnCount} class="border-0 p-0">
|
|
657
562
|
{@render expandedContent(row)}
|
|
658
563
|
</td>
|
|
659
564
|
</tr>
|
|
@@ -681,4 +586,33 @@
|
|
|
681
586
|
</div>
|
|
682
587
|
{/if}
|
|
683
588
|
</div>
|
|
589
|
+
{/snippet}
|
|
590
|
+
|
|
591
|
+
{#if hasHeader}
|
|
592
|
+
<Card>
|
|
593
|
+
{#snippet custom()}
|
|
594
|
+
<!-- Header Section -->
|
|
595
|
+
<div class="border-default-200 mb-4 border-b pb-3">
|
|
596
|
+
<div class="flex items-center justify-between">
|
|
597
|
+
<div>
|
|
598
|
+
{#if title}
|
|
599
|
+
<h2 class="text-default-900 text-lg font-semibold">{title}</h2>
|
|
600
|
+
{/if}
|
|
601
|
+
{#if subtitle}
|
|
602
|
+
<p class="text-default-500 mt-1 text-xs">{subtitle}</p>
|
|
603
|
+
{/if}
|
|
604
|
+
</div>
|
|
605
|
+
{#if headerActions}
|
|
606
|
+
<div class="flex items-center">
|
|
607
|
+
{@render headerActions()}
|
|
608
|
+
</div>
|
|
609
|
+
{/if}
|
|
610
|
+
</div>
|
|
611
|
+
</div>
|
|
612
|
+
|
|
613
|
+
{@render tableContent()}
|
|
614
|
+
{/snippet}
|
|
615
|
+
</Card>
|
|
616
|
+
{:else}
|
|
617
|
+
{@render tableContent()}
|
|
684
618
|
{/if}
|