@makolabs/ripple 0.0.1-dev.3 → 0.0.1-dev.5
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 +229 -96
- package/dist/button/Button.svelte +46 -0
- package/dist/button/Button.svelte.d.ts +4 -0
- package/dist/button/button.d.ts +136 -0
- package/dist/button/button.js +167 -0
- package/dist/button/index.d.ts +1 -0
- package/dist/button/index.js +1 -0
- package/dist/drawer/Drawer.svelte +213 -0
- package/dist/drawer/Drawer.svelte.d.ts +4 -0
- package/dist/drawer/drawer.d.ts +177 -0
- package/dist/drawer/drawer.js +80 -0
- package/dist/drawer/index.d.ts +2 -0
- package/dist/drawer/index.js +1 -0
- package/dist/elements/badge/Badge.svelte +35 -0
- package/dist/elements/badge/Badge.svelte.d.ts +4 -0
- package/dist/elements/badge/badge.d.ts +193 -0
- package/dist/elements/badge/badge.js +65 -0
- package/dist/elements/badge/index.d.ts +2 -0
- package/dist/elements/badge/index.js +2 -0
- package/dist/elements/dropdown/Dropdown.svelte +272 -0
- package/dist/elements/dropdown/Dropdown.svelte.d.ts +4 -0
- package/dist/elements/dropdown/Select.svelte +230 -0
- package/dist/elements/dropdown/Select.svelte.d.ts +4 -0
- package/dist/elements/dropdown/dropdown.d.ts +274 -0
- package/dist/elements/dropdown/dropdown.js +89 -0
- package/dist/elements/dropdown/index.d.ts +3 -0
- package/dist/elements/dropdown/index.js +2 -0
- package/dist/elements/dropdown/select.d.ts +220 -0
- package/dist/elements/dropdown/select.js +74 -0
- package/dist/header/Breadcrumbs.svelte +72 -0
- package/dist/header/Breadcrumbs.svelte.d.ts +4 -0
- package/dist/header/PageHeader.svelte +30 -0
- package/dist/header/PageHeader.svelte.d.ts +4 -0
- package/dist/header/breadcrumbs.d.ts +220 -0
- package/dist/header/breadcrumbs.js +81 -0
- package/dist/header/index.d.ts +4 -0
- package/dist/header/index.js +2 -0
- package/dist/header/pageheaders.d.ts +10 -0
- package/dist/header/pageheaders.js +1 -0
- package/dist/helper/cls.d.ts +1 -0
- package/dist/helper/cls.js +4 -0
- package/dist/helper/nav.svelte.d.ts +6 -0
- package/dist/helper/nav.svelte.js +23 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.js +18 -1
- package/dist/layout/card/Card.svelte +44 -0
- package/dist/layout/card/Card.svelte.d.ts +4 -0
- package/dist/layout/card/StatsCard.svelte +236 -0
- package/dist/layout/card/StatsCard.svelte.d.ts +4 -0
- package/dist/layout/card/card.d.ts +139 -0
- package/dist/layout/card/card.js +50 -0
- package/dist/layout/card/index.d.ts +4 -0
- package/dist/layout/card/index.js +2 -0
- package/dist/layout/card/stats-card.d.ts +208 -0
- package/dist/layout/card/stats-card.js +73 -0
- package/dist/layout/index.d.ts +5 -1
- package/dist/layout/index.js +5 -1
- package/dist/layout/navbar/Navbar.svelte +206 -0
- package/dist/layout/navbar/Navbar.svelte.d.ts +4 -0
- package/dist/layout/navbar/index.d.ts +2 -0
- package/dist/layout/navbar/index.js +2 -0
- package/dist/layout/navbar/navbar.d.ts +228 -0
- package/dist/layout/navbar/navbar.js +98 -0
- package/dist/layout/sidebar/NavGroup.svelte +101 -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 +145 -0
- package/dist/layout/sidebar/Sidebar.svelte.d.ts +4 -0
- package/dist/layout/sidebar/index.d.ts +2 -0
- package/dist/layout/sidebar/index.js +1 -0
- package/dist/layout/sidebar/sidebar.d.ts +46 -0
- package/dist/layout/sidebar/sidebar.js +1 -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 +413 -0
- package/dist/layout/table/Table.svelte.d.ts +4 -0
- package/dist/layout/table/index.d.ts +3 -0
- package/dist/layout/table/index.js +2 -0
- package/dist/layout/table/table.d.ts +303 -0
- package/dist/layout/table/table.js +149 -0
- package/dist/layout/tabs/Tab.svelte +57 -0
- package/dist/layout/tabs/Tab.svelte.d.ts +4 -0
- package/dist/layout/tabs/TabContent.svelte +31 -0
- package/dist/layout/tabs/TabContent.svelte.d.ts +4 -0
- package/dist/layout/tabs/TabGroup.svelte +57 -0
- package/dist/layout/tabs/TabGroup.svelte.d.ts +4 -0
- package/dist/layout/tabs/index.d.ts +3 -0
- package/dist/layout/tabs/index.js +3 -0
- package/dist/layout/tabs/tabs.d.ts +155 -0
- package/dist/layout/tabs/tabs.js +156 -0
- package/dist/modal/Modal.svelte +206 -0
- package/dist/modal/Modal.svelte.d.ts +4 -0
- package/dist/modal/index.d.ts +1 -0
- package/dist/modal/index.js +1 -0
- package/dist/modal/modal.d.ts +234 -0
- package/dist/modal/modal.js +81 -0
- package/dist/types/variants.d.ts +21 -0
- package/dist/types/variants.js +19 -0
- package/package.json +100 -102
- package/dist/layout/Card.svelte +0 -179
- package/dist/layout/Card.svelte.d.ts +0 -208
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import type { StatusType, KeyType, DataRow } from './table.js';
|
|
3
|
+
|
|
4
|
+
export { DateCell, Currency, Percentage, PhoneNumber, Email, Status, Time };
|
|
5
|
+
|
|
6
|
+
function getStatusClass(status: string): KeyType {
|
|
7
|
+
const base = 'inline-flex px-2 py-1 text-xs font-medium rounded-full';
|
|
8
|
+
const classes: Record<StatusType, string> = {
|
|
9
|
+
active: `${base} bg-success-100 text-success-700`,
|
|
10
|
+
inactive: `${base} bg-default-100 text-default-700`,
|
|
11
|
+
pending: `${base} bg-warning-100 text-warning-700`,
|
|
12
|
+
error: `${base} bg-danger-100 text-danger-700`,
|
|
13
|
+
default: `${base} bg-default-100 text-default-700`
|
|
14
|
+
};
|
|
15
|
+
return classes[status as StatusType] || classes.default;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatPhoneNumber(phoneNumber: string | number | null | undefined): KeyType | null {
|
|
19
|
+
if (phoneNumber === undefined || phoneNumber === null) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const clean = String(phoneNumber).replace(/\D/g, '');
|
|
23
|
+
const match = clean.match(/^(\d{3})(\d{3})(\d{4})$/);
|
|
24
|
+
return match ? `(${match[1]}) ${match[2]}-${match[3]}` : String(phoneNumber);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function formatTime(time: string | number | Date): KeyType {
|
|
28
|
+
return new Date(time).toLocaleTimeString('en-US', {
|
|
29
|
+
hour: '2-digit',
|
|
30
|
+
minute: '2-digit'
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function formatCurrency(value: number): KeyType {
|
|
35
|
+
return new Intl.NumberFormat('en-US', {
|
|
36
|
+
style: 'currency',
|
|
37
|
+
currency: 'USD',
|
|
38
|
+
minimumFractionDigits: 2,
|
|
39
|
+
maximumFractionDigits: 2
|
|
40
|
+
}).format(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function formatPercentage(value: number): KeyType {
|
|
44
|
+
return new Intl.NumberFormat('en-US', {
|
|
45
|
+
style: 'percent',
|
|
46
|
+
minimumFractionDigits: 2,
|
|
47
|
+
maximumFractionDigits: 2
|
|
48
|
+
}).format(value / 100);
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
{#snippet DateCell(row: DataRow, key: KeyType)}
|
|
53
|
+
{#if row[key] !== undefined && row[key] !== null}
|
|
54
|
+
{new Date(row[key]).toLocaleDateString('en-US')}
|
|
55
|
+
{:else}
|
|
56
|
+
<span class="text-default-300">—</span>
|
|
57
|
+
{/if}
|
|
58
|
+
{/snippet}
|
|
59
|
+
|
|
60
|
+
{#snippet Currency(row: DataRow, key: KeyType)}
|
|
61
|
+
{#if row[key] !== undefined && row[key] !== null}
|
|
62
|
+
{formatCurrency(row[key])}
|
|
63
|
+
{:else}
|
|
64
|
+
<span class="text-default-300">—</span>
|
|
65
|
+
{/if}
|
|
66
|
+
{/snippet}
|
|
67
|
+
|
|
68
|
+
{#snippet Percentage(row: DataRow, key: KeyType)}
|
|
69
|
+
{#if row[key] !== undefined && row[key] !== null}
|
|
70
|
+
{formatPercentage(row[key])}
|
|
71
|
+
{:else}
|
|
72
|
+
<span class="text-default-300">—</span>
|
|
73
|
+
{/if}
|
|
74
|
+
{/snippet}
|
|
75
|
+
|
|
76
|
+
{#snippet PhoneNumber(row: DataRow, key: KeyType)}
|
|
77
|
+
{#if row[key] !== undefined && row[key] !== null}
|
|
78
|
+
{formatPhoneNumber(row[key])}
|
|
79
|
+
{:else}
|
|
80
|
+
<span class="text-default-300">—</span>
|
|
81
|
+
{/if}
|
|
82
|
+
{/snippet}
|
|
83
|
+
|
|
84
|
+
{#snippet Email(row: DataRow, key: KeyType)}
|
|
85
|
+
{#if row[key] !== undefined && row[key] !== null}
|
|
86
|
+
<a href="mailto:{row[key]}" class="text-primary-600 hover:underline">
|
|
87
|
+
{row[key]}
|
|
88
|
+
</a>
|
|
89
|
+
{:else}
|
|
90
|
+
<span class="text-default-300">—</span>
|
|
91
|
+
{/if}
|
|
92
|
+
{/snippet}
|
|
93
|
+
|
|
94
|
+
{#snippet Status(row: DataRow, key: KeyType)}
|
|
95
|
+
{#if row[key] !== undefined && row[key] !== null}
|
|
96
|
+
{@const status = String(row[key]).toLowerCase()}
|
|
97
|
+
<span class={getStatusClass(status)}>
|
|
98
|
+
{row[key]}
|
|
99
|
+
</span>
|
|
100
|
+
{:else}
|
|
101
|
+
<span class="text-default-300">—</span>
|
|
102
|
+
{/if}
|
|
103
|
+
{/snippet}
|
|
104
|
+
|
|
105
|
+
{#snippet Time(row: DataRow, key: KeyType)}
|
|
106
|
+
{#if row[key] !== undefined && row[key] !== null}
|
|
107
|
+
{formatTime(row[key])}
|
|
108
|
+
{:else}
|
|
109
|
+
<span class="text-default-300">—</span>
|
|
110
|
+
{/if}
|
|
111
|
+
{/snippet}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { KeyType, DataRow } from './table.js';
|
|
2
|
+
export { DateCell, Currency, Percentage, PhoneNumber, Email, Status, Time };
|
|
3
|
+
declare const DateCell: (row: DataRow, key: KeyType) => ReturnType<import("svelte").Snippet>;
|
|
4
|
+
declare const Currency: (row: DataRow, key: KeyType) => ReturnType<import("svelte").Snippet>;
|
|
5
|
+
declare const Percentage: (row: DataRow, key: KeyType) => ReturnType<import("svelte").Snippet>;
|
|
6
|
+
declare const PhoneNumber: (row: DataRow, key: KeyType) => ReturnType<import("svelte").Snippet>;
|
|
7
|
+
declare const Email: (row: DataRow, key: KeyType) => ReturnType<import("svelte").Snippet>;
|
|
8
|
+
declare const Status: (row: DataRow, key: KeyType) => ReturnType<import("svelte").Snippet>;
|
|
9
|
+
declare const Time: (row: DataRow, key: KeyType) => ReturnType<import("svelte").Snippet>;
|
|
10
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
11
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
12
|
+
$$bindings?: Bindings;
|
|
13
|
+
} & Exports;
|
|
14
|
+
(internal: unknown, props: {
|
|
15
|
+
$$events?: Events;
|
|
16
|
+
$$slots?: Slots;
|
|
17
|
+
}): Exports & {
|
|
18
|
+
$set?: any;
|
|
19
|
+
$on?: any;
|
|
20
|
+
};
|
|
21
|
+
z_$$bindings?: Bindings;
|
|
22
|
+
}
|
|
23
|
+
declare const Cells: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
24
|
+
[evt: string]: CustomEvent<any>;
|
|
25
|
+
}, {}, {}, string>;
|
|
26
|
+
type Cells = InstanceType<typeof Cells>;
|
|
27
|
+
export default Cells;
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../../helper/cls.js';
|
|
3
|
+
import { table, type TableProps, type SortDirection, type SortState } from './table.js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
data = [],
|
|
7
|
+
columns = [],
|
|
8
|
+
color = 'default',
|
|
9
|
+
size = 'base',
|
|
10
|
+
bordered = true,
|
|
11
|
+
striped = false,
|
|
12
|
+
pageSize = 10,
|
|
13
|
+
selectable = false,
|
|
14
|
+
selected = $bindable([]),
|
|
15
|
+
onrowclick = () => {},
|
|
16
|
+
onsort = () => {},
|
|
17
|
+
onselect = () => {},
|
|
18
|
+
class: classname = '',
|
|
19
|
+
wrapperclass: wrapperClass = '',
|
|
20
|
+
tableclass: tableClass = '',
|
|
21
|
+
theadclass: theadClass = '',
|
|
22
|
+
tbodyclass: tbodyClass = '',
|
|
23
|
+
trclass: trClass = '',
|
|
24
|
+
thclass: thClass = '',
|
|
25
|
+
tdclass: tdClass = '',
|
|
26
|
+
footerclass: footerClass = '',
|
|
27
|
+
rowclass = () => '',
|
|
28
|
+
loading = false
|
|
29
|
+
}: TableProps<any> = $props();
|
|
30
|
+
|
|
31
|
+
let sortColumn = $state('');
|
|
32
|
+
let sortDirection = $state<SortDirection>(null);
|
|
33
|
+
let currentPage = $state(1);
|
|
34
|
+
|
|
35
|
+
// Pagination is automatically determined by pageSize
|
|
36
|
+
const pagination = $derived(pageSize > 0 && data.length > pageSize);
|
|
37
|
+
|
|
38
|
+
const {
|
|
39
|
+
base,
|
|
40
|
+
wrapper,
|
|
41
|
+
table: tableBaseClass,
|
|
42
|
+
thead,
|
|
43
|
+
tbody,
|
|
44
|
+
tr,
|
|
45
|
+
th,
|
|
46
|
+
td,
|
|
47
|
+
footer,
|
|
48
|
+
pagination: paginationClass,
|
|
49
|
+
emptyState,
|
|
50
|
+
sortButton,
|
|
51
|
+
sortIcon
|
|
52
|
+
} = $derived(
|
|
53
|
+
table({
|
|
54
|
+
size,
|
|
55
|
+
color,
|
|
56
|
+
bordered,
|
|
57
|
+
striped
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const baseClasses = $derived(cn(base(), classname));
|
|
62
|
+
const wrapperClasses = $derived(cn(wrapper(), wrapperClass));
|
|
63
|
+
const tableClasses = $derived(cn(tableBaseClass(), tableClass));
|
|
64
|
+
const theadClasses = $derived(cn(thead(), theadClass));
|
|
65
|
+
const tbodyClasses = $derived(cn(tbody(), tbodyClass));
|
|
66
|
+
const trClasses = $derived(cn(tr(), trClass));
|
|
67
|
+
const thClasses = $derived(cn(th(), thClass));
|
|
68
|
+
const tdClasses = $derived(cn(td(), tdClass));
|
|
69
|
+
const footerClasses = $derived(cn(footer(), footerClass));
|
|
70
|
+
const emptyStateClasses = $derived(emptyState());
|
|
71
|
+
|
|
72
|
+
// Handle pagination
|
|
73
|
+
const totalPages = $derived(Math.ceil(data.length / pageSize));
|
|
74
|
+
const paginatedData = $derived(
|
|
75
|
+
pagination ? data.slice((currentPage - 1) * pageSize, currentPage * pageSize) : data
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Handle sorting
|
|
79
|
+
function toggleSort(column: string) {
|
|
80
|
+
const columnDef = columns.find((col) => (col.sortKey || col.key) === column);
|
|
81
|
+
if (!columnDef?.sortable) return;
|
|
82
|
+
|
|
83
|
+
if (sortColumn === column) {
|
|
84
|
+
// Cycle through: asc -> desc -> null
|
|
85
|
+
if (sortDirection === 'asc') {
|
|
86
|
+
sortDirection = 'desc';
|
|
87
|
+
} else if (sortDirection === 'desc') {
|
|
88
|
+
sortDirection = null;
|
|
89
|
+
sortColumn = '';
|
|
90
|
+
} else {
|
|
91
|
+
sortDirection = 'asc';
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
sortColumn = column;
|
|
95
|
+
sortDirection = 'asc';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const newSortState: SortState = { column: sortColumn, direction: sortDirection };
|
|
99
|
+
onsort(newSortState);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function toggleRowSelection(row: any) {
|
|
103
|
+
if (!selectable) return;
|
|
104
|
+
|
|
105
|
+
const index = selected.findIndex((r) => r === row);
|
|
106
|
+
if (index === -1) {
|
|
107
|
+
selected = [...selected, row];
|
|
108
|
+
} else {
|
|
109
|
+
selected = selected.filter((r) => r !== row);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
onselect(selected);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isRowSelected(row: any) {
|
|
116
|
+
return selected.includes(row);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function handleRowClick(row: any, index: number) {
|
|
120
|
+
onrowclick(row, index);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function nextPage() {
|
|
124
|
+
if (currentPage < totalPages) {
|
|
125
|
+
currentPage++;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function prevPage() {
|
|
130
|
+
if (currentPage > 1) {
|
|
131
|
+
currentPage--;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function goToPage(page: number) {
|
|
136
|
+
if (page >= 1 && page <= totalPages) {
|
|
137
|
+
currentPage = page;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
</script>
|
|
141
|
+
|
|
142
|
+
<div class={baseClasses}>
|
|
143
|
+
<div class={wrapperClasses}>
|
|
144
|
+
<table class={tableClasses}>
|
|
145
|
+
<thead class={theadClasses}>
|
|
146
|
+
<tr>
|
|
147
|
+
{#if selectable}
|
|
148
|
+
<th class={thClasses}>
|
|
149
|
+
<input
|
|
150
|
+
type="checkbox"
|
|
151
|
+
onchange={() => {
|
|
152
|
+
if (selected.length === data.length) {
|
|
153
|
+
selected = [];
|
|
154
|
+
} else {
|
|
155
|
+
selected = [...data];
|
|
156
|
+
}
|
|
157
|
+
onselect(selected);
|
|
158
|
+
}}
|
|
159
|
+
checked={selected.length === data.length && data.length > 0}
|
|
160
|
+
aria-label="Select all rows"
|
|
161
|
+
/>
|
|
162
|
+
</th>
|
|
163
|
+
{/if}
|
|
164
|
+
|
|
165
|
+
{#each columns as column (column.key)}
|
|
166
|
+
<th
|
|
167
|
+
class={cn(
|
|
168
|
+
thClasses,
|
|
169
|
+
column.align === 'center' && 'text-center',
|
|
170
|
+
column.align === 'right' && 'text-right',
|
|
171
|
+
column.class
|
|
172
|
+
)}
|
|
173
|
+
style={column.width ? `width: ${column.width}` : undefined}
|
|
174
|
+
>
|
|
175
|
+
{#if column.sortable}
|
|
176
|
+
<button
|
|
177
|
+
type="button"
|
|
178
|
+
class={sortButton()}
|
|
179
|
+
onclick={() => toggleSort(column.sortKey || column.key)}
|
|
180
|
+
aria-label={`Sort by ${column.header}`}
|
|
181
|
+
>
|
|
182
|
+
{column.header}
|
|
183
|
+
<span class={sortIcon()}>
|
|
184
|
+
{#if sortColumn === (column.sortKey || column.key)}
|
|
185
|
+
{#if sortDirection === 'asc'}
|
|
186
|
+
<svg
|
|
187
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
188
|
+
viewBox="0 0 20 20"
|
|
189
|
+
fill="currentColor"
|
|
190
|
+
class="h-4 w-4"
|
|
191
|
+
>
|
|
192
|
+
<path
|
|
193
|
+
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"
|
|
194
|
+
/>
|
|
195
|
+
</svg>
|
|
196
|
+
{:else if sortDirection === 'desc'}
|
|
197
|
+
<svg
|
|
198
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
199
|
+
viewBox="0 0 20 20"
|
|
200
|
+
fill="currentColor"
|
|
201
|
+
class="h-4 w-4"
|
|
202
|
+
>
|
|
203
|
+
<path
|
|
204
|
+
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"
|
|
205
|
+
/>
|
|
206
|
+
</svg>
|
|
207
|
+
{/if}
|
|
208
|
+
{:else}
|
|
209
|
+
<svg
|
|
210
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
211
|
+
fill="none"
|
|
212
|
+
viewBox="0 0 24 24"
|
|
213
|
+
stroke-width="1.5"
|
|
214
|
+
stroke="currentColor"
|
|
215
|
+
class="h-4 w-4 opacity-40"
|
|
216
|
+
>
|
|
217
|
+
<path
|
|
218
|
+
stroke-linecap="round"
|
|
219
|
+
stroke-linejoin="round"
|
|
220
|
+
d="M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9"
|
|
221
|
+
/>
|
|
222
|
+
</svg>
|
|
223
|
+
{/if}
|
|
224
|
+
</span>
|
|
225
|
+
</button>
|
|
226
|
+
{:else}
|
|
227
|
+
{column.header}
|
|
228
|
+
{/if}
|
|
229
|
+
</th>
|
|
230
|
+
{/each}
|
|
231
|
+
</tr>
|
|
232
|
+
</thead>
|
|
233
|
+
|
|
234
|
+
<tbody class={tbodyClasses}>
|
|
235
|
+
{#if loading}
|
|
236
|
+
<tr>
|
|
237
|
+
<td
|
|
238
|
+
colspan={selectable ? columns.length + 1 : columns.length}
|
|
239
|
+
class={cn(tdClasses, 'py-8 text-center')}
|
|
240
|
+
>
|
|
241
|
+
<div class="flex justify-center">
|
|
242
|
+
<svg
|
|
243
|
+
class="text-default-500 h-6 w-6 animate-spin"
|
|
244
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
245
|
+
fill="none"
|
|
246
|
+
viewBox="0 0 24 24"
|
|
247
|
+
>
|
|
248
|
+
<circle
|
|
249
|
+
class="opacity-25"
|
|
250
|
+
cx="12"
|
|
251
|
+
cy="12"
|
|
252
|
+
r="10"
|
|
253
|
+
stroke="currentColor"
|
|
254
|
+
stroke-width="4"
|
|
255
|
+
></circle>
|
|
256
|
+
<path
|
|
257
|
+
class="opacity-75"
|
|
258
|
+
fill="currentColor"
|
|
259
|
+
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"
|
|
260
|
+
></path>
|
|
261
|
+
</svg>
|
|
262
|
+
</div>
|
|
263
|
+
</td>
|
|
264
|
+
</tr>
|
|
265
|
+
{:else if paginatedData.length === 0}
|
|
266
|
+
<tr>
|
|
267
|
+
<td
|
|
268
|
+
colspan={selectable ? columns.length + 1 : columns.length}
|
|
269
|
+
class={emptyStateClasses}
|
|
270
|
+
>
|
|
271
|
+
No data available
|
|
272
|
+
</td>
|
|
273
|
+
</tr>
|
|
274
|
+
{:else}
|
|
275
|
+
{#each paginatedData as row, rowIndex (rowIndex)}
|
|
276
|
+
<tr
|
|
277
|
+
class={cn(
|
|
278
|
+
trClasses,
|
|
279
|
+
rowclass(row, rowIndex),
|
|
280
|
+
selectable && isRowSelected(row) && 'bg-primary-100'
|
|
281
|
+
)}
|
|
282
|
+
onclick={() => handleRowClick(row, rowIndex)}
|
|
283
|
+
aria-selected={selectable && isRowSelected(row)}
|
|
284
|
+
>
|
|
285
|
+
{#if selectable}
|
|
286
|
+
<td class={tdClasses}>
|
|
287
|
+
<input
|
|
288
|
+
type="checkbox"
|
|
289
|
+
checked={isRowSelected(row)}
|
|
290
|
+
onclick={(e) => {
|
|
291
|
+
e.stopPropagation(); // Prevent row click
|
|
292
|
+
toggleRowSelection(row);
|
|
293
|
+
}}
|
|
294
|
+
aria-label={`Select row ${rowIndex + 1}`}
|
|
295
|
+
/>
|
|
296
|
+
</td>
|
|
297
|
+
{/if}
|
|
298
|
+
|
|
299
|
+
{#each columns as column (column.key)}
|
|
300
|
+
<td
|
|
301
|
+
class={cn(
|
|
302
|
+
tdClasses,
|
|
303
|
+
column.align === 'center' && 'text-center',
|
|
304
|
+
column.align === 'right' && 'text-right',
|
|
305
|
+
column.class
|
|
306
|
+
)}
|
|
307
|
+
>
|
|
308
|
+
{#if column.cell}
|
|
309
|
+
{@render column.cell(row, column.key, rowIndex)}
|
|
310
|
+
{:else if row[column.key] === undefined || row[column.key] === null}
|
|
311
|
+
<span class="text-default-300">—</span>
|
|
312
|
+
{:else}
|
|
313
|
+
{row[column.key]}
|
|
314
|
+
{/if}
|
|
315
|
+
</td>
|
|
316
|
+
{/each}
|
|
317
|
+
</tr>
|
|
318
|
+
{/each}
|
|
319
|
+
{/if}
|
|
320
|
+
</tbody>
|
|
321
|
+
</table>
|
|
322
|
+
</div>
|
|
323
|
+
|
|
324
|
+
{#if pagination && totalPages > 1}
|
|
325
|
+
<div class={footerClasses}>
|
|
326
|
+
<div class={paginationClass}>
|
|
327
|
+
<div class="flex items-center gap-2">
|
|
328
|
+
<span class="text-default-500 text-sm">
|
|
329
|
+
Showing {Math.min((currentPage - 1) * pageSize + 1, data.length)}
|
|
330
|
+
to {Math.min(currentPage * pageSize, data.length)} of {data.length} entries
|
|
331
|
+
</span>
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
<div class="flex items-center gap-1">
|
|
335
|
+
<button
|
|
336
|
+
type="button"
|
|
337
|
+
class={cn(
|
|
338
|
+
'relative inline-flex items-center rounded-md px-2 py-1 text-sm font-medium',
|
|
339
|
+
currentPage === 1
|
|
340
|
+
? 'text-default-300 cursor-not-allowed'
|
|
341
|
+
: 'text-default-700 hover:bg-default-100'
|
|
342
|
+
)}
|
|
343
|
+
onclick={prevPage}
|
|
344
|
+
disabled={currentPage === 1}
|
|
345
|
+
aria-label="Previous page"
|
|
346
|
+
>
|
|
347
|
+
<svg
|
|
348
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
349
|
+
viewBox="0 0 20 20"
|
|
350
|
+
fill="currentColor"
|
|
351
|
+
class="h-5 w-5"
|
|
352
|
+
>
|
|
353
|
+
<path
|
|
354
|
+
d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z"
|
|
355
|
+
/>
|
|
356
|
+
</svg>
|
|
357
|
+
</button>
|
|
358
|
+
|
|
359
|
+
<!--eslint-disable-next-line @typescript-eslint/no-unused-vars-->
|
|
360
|
+
{#each Array(Math.min(5, totalPages)) as _, i (i)}
|
|
361
|
+
{@const pageNum =
|
|
362
|
+
currentPage <= 3
|
|
363
|
+
? i + 1
|
|
364
|
+
: currentPage >= totalPages - 2
|
|
365
|
+
? totalPages - 4 + i
|
|
366
|
+
: currentPage - 2 + i}
|
|
367
|
+
|
|
368
|
+
{#if pageNum > 0 && pageNum <= totalPages}
|
|
369
|
+
<button
|
|
370
|
+
type="button"
|
|
371
|
+
class={cn(
|
|
372
|
+
'relative inline-flex items-center rounded-md px-3 py-1 text-sm font-medium',
|
|
373
|
+
currentPage === pageNum
|
|
374
|
+
? 'bg-primary-100 text-primary-700'
|
|
375
|
+
: 'text-default-700 hover:bg-default-100'
|
|
376
|
+
)}
|
|
377
|
+
onclick={() => goToPage(pageNum)}
|
|
378
|
+
aria-label={`Page ${pageNum}`}
|
|
379
|
+
aria-current={currentPage === pageNum ? 'page' : undefined}
|
|
380
|
+
>
|
|
381
|
+
{pageNum}
|
|
382
|
+
</button>
|
|
383
|
+
{/if}
|
|
384
|
+
{/each}
|
|
385
|
+
|
|
386
|
+
<button
|
|
387
|
+
type="button"
|
|
388
|
+
class={cn(
|
|
389
|
+
'relative inline-flex items-center rounded-md px-2 py-1 text-sm font-medium',
|
|
390
|
+
currentPage === totalPages
|
|
391
|
+
? 'text-default-300 cursor-not-allowed'
|
|
392
|
+
: 'text-default-700 hover:bg-default-100'
|
|
393
|
+
)}
|
|
394
|
+
onclick={nextPage}
|
|
395
|
+
disabled={currentPage === totalPages}
|
|
396
|
+
aria-label="Next page"
|
|
397
|
+
>
|
|
398
|
+
<svg
|
|
399
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
400
|
+
viewBox="0 0 20 20"
|
|
401
|
+
fill="currentColor"
|
|
402
|
+
class="h-5 w-5"
|
|
403
|
+
>
|
|
404
|
+
<path
|
|
405
|
+
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"
|
|
406
|
+
/>
|
|
407
|
+
</svg>
|
|
408
|
+
</button>
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
</div>
|
|
412
|
+
{/if}
|
|
413
|
+
</div>
|