@juspay/svelte-ui-components 2.11.0 → 2.13.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/Table/Table.svelte +244 -69
- package/dist/Table/properties.d.ts +18 -1
- package/dist/assets/sort-default.svg +4 -0
- package/package.json +3 -1
package/dist/Table/Table.svelte
CHANGED
|
@@ -1,41 +1,63 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { TableProperties } from './properties';
|
|
3
|
+
import type { JSONValue } from 'type-decoder';
|
|
4
|
+
import Button from '../Button/Button.svelte';
|
|
5
|
+
import chevronUpSvg from '../assets/chevron-up.svg?raw';
|
|
6
|
+
import chevronDownSvg from '../assets/chevron-down.svg?raw';
|
|
7
|
+
import sortDefaultSvg from '../assets/sort-default.svg?raw';
|
|
3
8
|
|
|
4
9
|
let {
|
|
5
10
|
tableTitle = '',
|
|
6
11
|
tableHeaders = [],
|
|
7
12
|
tableData = [],
|
|
13
|
+
sortable = true,
|
|
14
|
+
sortableColumns,
|
|
15
|
+
stickyHeader = false,
|
|
8
16
|
isTableScrollable = false,
|
|
9
17
|
isContentScrollable = false,
|
|
18
|
+
testId,
|
|
19
|
+
caption,
|
|
20
|
+
sortAscIcon,
|
|
21
|
+
sortDescIcon,
|
|
22
|
+
sortDefaultIcon,
|
|
23
|
+
cell,
|
|
24
|
+
empty,
|
|
25
|
+
onRowClick,
|
|
26
|
+
onSort,
|
|
10
27
|
classes
|
|
11
28
|
}: TableProperties = $props();
|
|
12
29
|
|
|
13
|
-
let
|
|
30
|
+
let sortColumn = $state<number | null>(null);
|
|
31
|
+
let sortDirection = $state<'asc' | 'desc'>('asc');
|
|
14
32
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
33
|
+
function isColumnSortable(colIndex: number): boolean {
|
|
34
|
+
if (!sortable) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (sortableColumns) {
|
|
38
|
+
return sortableColumns.includes(colIndex);
|
|
19
39
|
}
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
20
42
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (!column) {
|
|
43
|
+
let sortedTableData = $derived.by(() => {
|
|
44
|
+
if (sortColumn === null) {
|
|
24
45
|
return [...tableData];
|
|
25
46
|
}
|
|
26
|
-
|
|
47
|
+
|
|
48
|
+
const colIndex = sortColumn;
|
|
49
|
+
const direction = sortDirection;
|
|
27
50
|
|
|
28
51
|
return [...tableData].sort((a, b) => {
|
|
29
|
-
const colIndex = tableHeaders.indexOf(column);
|
|
30
52
|
const valueA = a[colIndex];
|
|
31
53
|
const valueB = b[colIndex];
|
|
32
54
|
|
|
33
55
|
if (typeof valueA === 'number' && typeof valueB === 'number') {
|
|
34
|
-
return
|
|
56
|
+
return direction === 'asc' ? valueA - valueB : valueB - valueA;
|
|
35
57
|
} else if (typeof valueA === 'string' && typeof valueB === 'string') {
|
|
36
|
-
return
|
|
58
|
+
return direction === 'asc' ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA);
|
|
37
59
|
} else if (typeof valueA === 'boolean' && typeof valueB === 'boolean') {
|
|
38
|
-
return
|
|
60
|
+
return direction === 'asc'
|
|
39
61
|
? valueA === valueB
|
|
40
62
|
? 0
|
|
41
63
|
: valueA
|
|
@@ -46,26 +68,38 @@
|
|
|
46
68
|
: valueA
|
|
47
69
|
? 1
|
|
48
70
|
: -1;
|
|
49
|
-
} else {
|
|
50
|
-
return 0;
|
|
51
71
|
}
|
|
72
|
+
return 0;
|
|
52
73
|
});
|
|
53
74
|
});
|
|
54
75
|
|
|
55
|
-
function
|
|
56
|
-
if (
|
|
57
|
-
|
|
76
|
+
function handleSort(colIndex: number) {
|
|
77
|
+
if (!isColumnSortable(colIndex)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (sortColumn === colIndex) {
|
|
82
|
+
sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
|
|
58
83
|
} else {
|
|
59
|
-
|
|
84
|
+
sortColumn = colIndex;
|
|
85
|
+
sortDirection = 'asc';
|
|
60
86
|
}
|
|
87
|
+
onSort?.(colIndex, sortDirection);
|
|
61
88
|
}
|
|
62
89
|
|
|
63
|
-
function
|
|
90
|
+
function handleRowClick(rowIndex: number, rowData: JSONValue[]) {
|
|
91
|
+
onRowClick?.(rowIndex, rowData);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function handleRowKeydown(event: KeyboardEvent, rowIndex: number, rowData: JSONValue[]) {
|
|
64
95
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
65
96
|
event.preventDefault();
|
|
66
|
-
|
|
97
|
+
onRowClick?.(rowIndex, rowData);
|
|
67
98
|
}
|
|
68
99
|
}
|
|
100
|
+
|
|
101
|
+
let isRowClickable = $derived(typeof onRowClick === 'function');
|
|
102
|
+
let isStickyHeader = $derived(stickyHeader || isTableScrollable);
|
|
69
103
|
</script>
|
|
70
104
|
|
|
71
105
|
{#if typeof tableTitle === 'string' && tableTitle.length > 0}
|
|
@@ -75,48 +109,86 @@
|
|
|
75
109
|
{/if}
|
|
76
110
|
{#if tableHeaders.length !== 0 || tableData.length !== 0}
|
|
77
111
|
<div
|
|
78
|
-
class="table-container {isTableScrollable ? 'scrollable-table' : '
|
|
79
|
-
|
|
112
|
+
class="table-container {isTableScrollable ? 'scrollable-table' : ''} {classes ?? ''}"
|
|
113
|
+
data-pw={testId}
|
|
80
114
|
>
|
|
81
115
|
<table>
|
|
116
|
+
{#if caption}
|
|
117
|
+
<caption class="sr-only">{caption}</caption>
|
|
118
|
+
{/if}
|
|
82
119
|
<thead>
|
|
83
120
|
<tr>
|
|
84
|
-
{#each tableHeaders as header (
|
|
85
|
-
<th class="table-header
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
class="sort-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
121
|
+
{#each tableHeaders as header, colIndex (colIndex)}
|
|
122
|
+
<th class="table-header" class:table-header-sticky={isStickyHeader}>
|
|
123
|
+
<span class="table-header-content">
|
|
124
|
+
{header}
|
|
125
|
+
{#if isColumnSortable(colIndex)}
|
|
126
|
+
<div class="sort-button">
|
|
127
|
+
<Button onclick={() => handleSort(colIndex)} ariaLabel="Sort by {header}">
|
|
128
|
+
{#if sortColumn === colIndex && sortDirection === 'asc'}
|
|
129
|
+
{#if typeof sortAscIcon === 'function'}
|
|
130
|
+
{@render sortAscIcon()}
|
|
131
|
+
{:else}
|
|
132
|
+
<span class="sort-icon">
|
|
133
|
+
<!-- eslint-disable svelte/no-at-html-tags -->
|
|
134
|
+
{@html chevronUpSvg}
|
|
135
|
+
</span>
|
|
136
|
+
{/if}
|
|
137
|
+
{:else if sortColumn === colIndex && sortDirection === 'desc'}
|
|
138
|
+
{#if typeof sortDescIcon === 'function'}
|
|
139
|
+
{@render sortDescIcon()}
|
|
140
|
+
{:else}
|
|
141
|
+
<span class="sort-icon">
|
|
142
|
+
<!-- eslint-disable svelte/no-at-html-tags -->
|
|
143
|
+
{@html chevronDownSvg}
|
|
144
|
+
</span>
|
|
145
|
+
{/if}
|
|
146
|
+
{:else if typeof sortDefaultIcon === 'function'}
|
|
147
|
+
{@render sortDefaultIcon()}
|
|
148
|
+
{:else}
|
|
149
|
+
<span class="sort-icon sort-icon-idle">
|
|
150
|
+
<!-- eslint-disable svelte/no-at-html-tags -->
|
|
151
|
+
{@html sortDefaultSvg}
|
|
152
|
+
</span>
|
|
153
|
+
{/if}
|
|
154
|
+
</Button>
|
|
155
|
+
</div>
|
|
156
|
+
{/if}
|
|
157
|
+
</span>
|
|
104
158
|
</th>
|
|
105
159
|
{/each}
|
|
106
160
|
</tr>
|
|
107
161
|
</thead>
|
|
108
162
|
<tbody>
|
|
109
|
-
{#
|
|
163
|
+
{#if sortedTableData.length === 0 && typeof empty === 'function'}
|
|
110
164
|
<tr>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
{cell}
|
|
115
|
-
</div>
|
|
116
|
-
</td>
|
|
117
|
-
{/each}
|
|
165
|
+
<td class="table-empty" colspan={tableHeaders.length}>
|
|
166
|
+
{@render empty()}
|
|
167
|
+
</td>
|
|
118
168
|
</tr>
|
|
119
|
-
{
|
|
169
|
+
{:else}
|
|
170
|
+
{#each sortedTableData as row, rowIndex (rowIndex)}
|
|
171
|
+
<tr
|
|
172
|
+
class="table-row"
|
|
173
|
+
class:table-row-clickable={isRowClickable}
|
|
174
|
+
onclick={isRowClickable ? () => handleRowClick(rowIndex, row) : null}
|
|
175
|
+
onkeydown={isRowClickable ? (e) => handleRowKeydown(e, rowIndex, row) : null}
|
|
176
|
+
tabindex={isRowClickable ? 0 : null}
|
|
177
|
+
>
|
|
178
|
+
{#each row as cellValue, colIndex (colIndex)}
|
|
179
|
+
<td class="table-content">
|
|
180
|
+
<div class={isContentScrollable ? 'scrollable-content' : ''}>
|
|
181
|
+
{#if typeof cell === 'function'}
|
|
182
|
+
{@render cell(cellValue, rowIndex, colIndex)}
|
|
183
|
+
{:else}
|
|
184
|
+
{cellValue}
|
|
185
|
+
{/if}
|
|
186
|
+
</div>
|
|
187
|
+
</td>
|
|
188
|
+
{/each}
|
|
189
|
+
</tr>
|
|
190
|
+
{/each}
|
|
191
|
+
{/if}
|
|
120
192
|
</tbody>
|
|
121
193
|
</table>
|
|
122
194
|
</div>
|
|
@@ -124,57 +196,160 @@
|
|
|
124
196
|
|
|
125
197
|
<style>
|
|
126
198
|
.table-title {
|
|
127
|
-
margin: var(--table-title-margin, 0px 0px
|
|
128
|
-
font-size: var(--table-tile-font-size,
|
|
199
|
+
margin: var(--table-title-margin, 0px 0px 12px 0px);
|
|
200
|
+
font-size: var(--table-title-font-size, var(--table-tile-font-size, 18px));
|
|
201
|
+
font-weight: var(--table-title-font-weight, 600);
|
|
202
|
+
color: var(--table-title-color, #111827);
|
|
129
203
|
font-family: var(--table-title-font-family);
|
|
130
204
|
padding: var(--table-title-padding);
|
|
131
205
|
}
|
|
206
|
+
|
|
132
207
|
.table-container {
|
|
133
|
-
border
|
|
134
|
-
|
|
208
|
+
border: var(--table-border, 1px solid #e5e7eb);
|
|
209
|
+
border-radius: var(--table-border-radius, 8px);
|
|
210
|
+
width: var(--table-container-width, 100%);
|
|
211
|
+
overflow: hidden;
|
|
135
212
|
}
|
|
136
213
|
|
|
137
214
|
.scrollable-table {
|
|
138
215
|
height: var(--table-container-height, 143px);
|
|
139
216
|
overflow-y: auto;
|
|
140
217
|
}
|
|
218
|
+
|
|
141
219
|
table {
|
|
142
|
-
width: var(--table-width,
|
|
220
|
+
width: var(--table-width, 100%);
|
|
143
221
|
border-collapse: var(--table-border-collapse, collapse);
|
|
144
222
|
}
|
|
223
|
+
|
|
145
224
|
.table-header,
|
|
146
225
|
.table-content {
|
|
147
|
-
border: var(--table-inner-border,
|
|
148
|
-
padding: var(--table-padding,
|
|
226
|
+
border: var(--table-inner-border, none);
|
|
227
|
+
padding: var(--table-padding, 12px 16px);
|
|
149
228
|
text-align: var(--table-text-align, left);
|
|
150
|
-
width: var(--table-column-width
|
|
229
|
+
width: var(--table-column-width);
|
|
151
230
|
word-break: break-all;
|
|
152
231
|
}
|
|
232
|
+
|
|
153
233
|
.scrollable-content {
|
|
154
234
|
overflow-y: auto;
|
|
155
235
|
height: var(--scrollable-column-height, 20px);
|
|
156
236
|
}
|
|
157
237
|
|
|
158
238
|
.table-header {
|
|
159
|
-
background-color: var(--table-header-border-bgcolor,
|
|
160
|
-
font-size: var(--table-header-font-size);
|
|
239
|
+
background-color: var(--table-header-background, var(--table-header-border-bgcolor, #f9fafb));
|
|
240
|
+
font-size: var(--table-header-font-size, 13px);
|
|
161
241
|
font-family: var(--table-header-font-family);
|
|
162
|
-
|
|
242
|
+
font-weight: var(--table-header-font-weight, 600);
|
|
243
|
+
letter-spacing: var(--table-header-letter-spacing, 0.02em);
|
|
244
|
+
text-transform: var(--table-header-text-transform);
|
|
245
|
+
color: var(--table-header-color, var(--table-header-font-color, #6b7280));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.table-header-content {
|
|
249
|
+
display: flex;
|
|
250
|
+
align-items: center;
|
|
251
|
+
gap: 4px;
|
|
163
252
|
}
|
|
164
253
|
|
|
165
254
|
.table-header-sticky {
|
|
166
255
|
position: sticky;
|
|
167
|
-
top: -
|
|
256
|
+
top: var(--table-header-sticky-top, 0);
|
|
257
|
+
z-index: 1;
|
|
168
258
|
}
|
|
169
259
|
|
|
170
260
|
.table-content {
|
|
171
|
-
background-color: var(--table-content-border-bgcolor);
|
|
172
|
-
font-size: var(--table-content-font-size);
|
|
261
|
+
background-color: var(--table-content-background, var(--table-content-border-bgcolor));
|
|
262
|
+
font-size: var(--table-content-font-size, 14px);
|
|
173
263
|
font-family: var(--table-content-font-family);
|
|
174
|
-
color: var(--table-content-font-color);
|
|
264
|
+
color: var(--table-content-color, var(--table-content-font-color, #111827));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.table-row {
|
|
268
|
+
border-bottom: var(--table-row-border, 1px solid #f3f4f6);
|
|
269
|
+
background-color: var(--table-row-background);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.table-row:last-child {
|
|
273
|
+
border-bottom: var(--table-row-last-border, none);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.table-row:nth-child(even) {
|
|
277
|
+
background-color: var(--table-row-alt-background, var(--table-row-background));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.table-row:hover {
|
|
281
|
+
background-color: var(--table-row-hover-background);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.table-row:hover > .table-content {
|
|
285
|
+
background-color: var(
|
|
286
|
+
--table-row-hover-background,
|
|
287
|
+
var(--table-content-background, var(--table-content-border-bgcolor))
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.table-row-clickable {
|
|
292
|
+
cursor: pointer;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.table-row-clickable:focus-visible {
|
|
296
|
+
outline: 2px solid var(--table-focus-outline-color, #3b82f6);
|
|
297
|
+
outline-offset: -2px;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.sort-button {
|
|
301
|
+
--button-color: transparent;
|
|
302
|
+
--button-border: none;
|
|
303
|
+
--button-padding: 2px;
|
|
304
|
+
--button-margin: 0;
|
|
305
|
+
--button-text-color: var(--table-sort-button-color, inherit);
|
|
306
|
+
--button-border-radius: 4px;
|
|
307
|
+
--button-width: fit-content;
|
|
308
|
+
--button-height: fit-content;
|
|
309
|
+
--button-hover-color: var(--table-sort-button-hover-background, rgba(0, 0, 0, 0.05));
|
|
310
|
+
--button-hover-text-color: var(
|
|
311
|
+
--table-sort-button-hover-color,
|
|
312
|
+
var(--table-sort-button-color, inherit)
|
|
313
|
+
);
|
|
314
|
+
display: inline-flex;
|
|
315
|
+
align-items: center;
|
|
316
|
+
line-height: 1;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.sort-button :global(.sort-icon) {
|
|
320
|
+
display: inline-flex;
|
|
321
|
+
align-items: center;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.sort-button :global(.sort-icon svg) {
|
|
325
|
+
width: var(--table-sort-icon-size, 14px);
|
|
326
|
+
height: var(--table-sort-icon-size, 14px);
|
|
327
|
+
display: block;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.sort-button :global(.sort-icon-idle) {
|
|
331
|
+
opacity: var(--table-sort-idle-opacity, 0.3);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.sort-button:hover :global(.sort-icon-idle) {
|
|
335
|
+
opacity: 0.6;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.table-empty {
|
|
339
|
+
padding: var(--table-empty-padding, 32px 24px);
|
|
340
|
+
text-align: center;
|
|
341
|
+
color: var(--table-empty-color, #9ca3af);
|
|
175
342
|
}
|
|
176
343
|
|
|
177
|
-
.
|
|
178
|
-
|
|
344
|
+
.sr-only {
|
|
345
|
+
position: absolute;
|
|
346
|
+
width: 1px;
|
|
347
|
+
height: 1px;
|
|
348
|
+
padding: 0;
|
|
349
|
+
overflow: hidden;
|
|
350
|
+
clip: rect(0, 0, 0, 0);
|
|
351
|
+
clip-path: inset(50%);
|
|
352
|
+
white-space: nowrap;
|
|
353
|
+
border-width: 0;
|
|
179
354
|
}
|
|
180
355
|
</style>
|
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
import type { JSONValue } from 'type-decoder';
|
|
2
|
-
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
export type SortDirection = 'asc' | 'desc';
|
|
4
|
+
export type TableProperties = OptionalTableProperties & TableEventProperties;
|
|
5
|
+
export type OptionalTableProperties = {
|
|
3
6
|
tableTitle?: string | null;
|
|
4
7
|
tableHeaders?: string[];
|
|
5
8
|
tableData?: Array<JSONValue[]>;
|
|
9
|
+
sortable?: boolean;
|
|
10
|
+
sortableColumns?: number[];
|
|
11
|
+
stickyHeader?: boolean;
|
|
6
12
|
isTableScrollable?: boolean;
|
|
7
13
|
isContentScrollable?: boolean;
|
|
14
|
+
testId?: string;
|
|
15
|
+
caption?: string;
|
|
16
|
+
sortAscIcon?: Snippet;
|
|
17
|
+
sortDescIcon?: Snippet;
|
|
18
|
+
sortDefaultIcon?: Snippet;
|
|
19
|
+
cell?: Snippet<[JSONValue, number, number]>;
|
|
20
|
+
empty?: Snippet;
|
|
8
21
|
classes?: string;
|
|
9
22
|
};
|
|
23
|
+
export type TableEventProperties = {
|
|
24
|
+
onRowClick?: (rowIndex: number, rowData: JSONValue[]) => void;
|
|
25
|
+
onSort?: (columnIndex: number, direction: SortDirection) => void;
|
|
26
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<polyline points="18 15 12 9 6 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" opacity="0.5"/>
|
|
3
|
+
<polyline points="6 17 12 23 18 17" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" opacity="0.5"/>
|
|
4
|
+
</svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/svelte-ui-components",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"description": "A themeable Svelte 5 UI component library with CSS custom property driven styling",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|
|
@@ -22,11 +22,13 @@
|
|
|
22
22
|
"scripts": {
|
|
23
23
|
"dev": "vite dev --host",
|
|
24
24
|
"build": "vite build && npm run package",
|
|
25
|
+
"build:wc": "vite build --config vite.config.wc.ts",
|
|
25
26
|
"preview": "vite preview",
|
|
26
27
|
"package": "svelte-kit sync && svelte-package && publint",
|
|
27
28
|
"prepublishOnly": "npm run package",
|
|
28
29
|
"test": "npm run test:integration && npm run test:unit",
|
|
29
30
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
31
|
+
"check:wc": "svelte-check --tsconfig ./tsconfig.wc.json --compiler-warnings \"options_missing_custom_element:ignore\"",
|
|
30
32
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
31
33
|
"lint": "prettier --check src && eslint src",
|
|
32
34
|
"format": "prettier --write src",
|