@makolabs/ripple 0.0.1-dev.8 → 0.0.1-dev.80

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.
Files changed (77) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/storage/BaseAdapter.d.ts +20 -0
  3. package/dist/adapters/storage/BaseAdapter.js +171 -0
  4. package/dist/adapters/storage/S3Adapter.d.ts +21 -0
  5. package/dist/adapters/storage/S3Adapter.js +194 -0
  6. package/dist/adapters/storage/index.d.ts +3 -0
  7. package/dist/adapters/storage/index.js +3 -0
  8. package/dist/adapters/storage/types.d.ts +102 -0
  9. package/dist/adapters/storage/types.js +4 -0
  10. package/dist/charts/Chart.svelte +59 -47
  11. package/dist/charts/Chart.svelte.d.ts +1 -1
  12. package/dist/drawer/drawer.js +3 -3
  13. package/dist/elements/accordion/Accordion.svelte +98 -0
  14. package/dist/elements/accordion/Accordion.svelte.d.ts +4 -0
  15. package/dist/elements/accordion/accordion.d.ts +227 -0
  16. package/dist/elements/accordion/accordion.js +138 -0
  17. package/dist/elements/alert/Alert.svelte +7 -3
  18. package/dist/elements/dropdown/Dropdown.svelte +74 -107
  19. package/dist/elements/dropdown/Select.svelte +81 -62
  20. package/dist/elements/dropdown/dropdown.js +1 -1
  21. package/dist/elements/dropdown/select.js +8 -8
  22. package/dist/elements/file-upload/FileUpload.svelte +17 -95
  23. package/dist/elements/file-upload/FilesPreview.svelte +93 -0
  24. package/dist/elements/file-upload/FilesPreview.svelte.d.ts +4 -0
  25. package/dist/elements/progress/Progress.svelte +83 -25
  26. package/dist/file-browser/FileBrowser.svelte +837 -0
  27. package/dist/file-browser/FileBrowser.svelte.d.ts +14 -0
  28. package/dist/file-browser/index.d.ts +1 -0
  29. package/dist/file-browser/index.js +1 -0
  30. package/dist/filters/CompactFilters.svelte +147 -0
  31. package/dist/filters/CompactFilters.svelte.d.ts +4 -0
  32. package/dist/filters/index.d.ts +1 -0
  33. package/dist/filters/index.js +1 -0
  34. package/dist/forms/Checkbox.svelte +2 -2
  35. package/dist/forms/DateRange.svelte +21 -21
  36. package/dist/forms/Input.svelte +3 -3
  37. package/dist/forms/NumberInput.svelte +1 -1
  38. package/dist/forms/RadioInputs.svelte +3 -3
  39. package/dist/forms/Tags.svelte +5 -5
  40. package/dist/forms/Toggle.svelte +3 -3
  41. package/dist/forms/slider.js +4 -4
  42. package/dist/index.d.ts +254 -143
  43. package/dist/index.js +19 -2
  44. package/dist/layout/card/MetricCard.svelte +64 -0
  45. package/dist/layout/card/MetricCard.svelte.d.ts +4 -0
  46. package/dist/layout/card/StatsCard.svelte +4 -3
  47. package/dist/layout/card/StatsCard.svelte.d.ts +1 -1
  48. package/dist/layout/card/metric-card.d.ts +49 -0
  49. package/dist/layout/card/metric-card.js +10 -0
  50. package/dist/layout/card/stats-card.d.ts +0 -15
  51. package/dist/layout/card/stats-card.js +1 -1
  52. package/dist/layout/sidebar/NavGroup.svelte +8 -9
  53. package/dist/layout/sidebar/NavItem.svelte +2 -2
  54. package/dist/layout/sidebar/Sidebar.svelte +102 -49
  55. package/dist/layout/table/Table.svelte +464 -87
  56. package/dist/layout/table/Table.svelte.d.ts +1 -1
  57. package/dist/layout/table/table.d.ts +0 -47
  58. package/dist/layout/table/table.js +0 -8
  59. package/dist/layout/tabs/Tab.svelte +9 -6
  60. package/dist/layout/tabs/Tab.svelte.d.ts +1 -1
  61. package/dist/layout/tabs/TabContent.svelte +1 -2
  62. package/dist/layout/tabs/TabContent.svelte.d.ts +1 -1
  63. package/dist/layout/tabs/TabGroup.svelte +10 -5
  64. package/dist/layout/tabs/TabGroup.svelte.d.ts +2 -2
  65. package/dist/layout/tabs/tabs.d.ts +61 -76
  66. package/dist/layout/tabs/tabs.js +170 -28
  67. package/dist/modal/Modal.svelte +3 -3
  68. package/dist/modal/modal.js +3 -3
  69. package/dist/utils/Portal.svelte +108 -0
  70. package/dist/utils/Portal.svelte.d.ts +8 -0
  71. package/dist/utils/dateUtils.d.ts +7 -0
  72. package/dist/utils/dateUtils.js +26 -0
  73. package/dist/variants.d.ts +11 -1
  74. package/dist/variants.js +17 -0
  75. package/package.json +2 -2
  76. package/dist/header/pageheaders.d.ts +0 -10
  77. package/dist/header/pageheaders.js +0 -1
@@ -1,20 +1,23 @@
1
1
  <script lang="ts">
2
2
  import { cn } from '../../helper/cls.js';
3
- import { table, type TableProps, type SortDirection, type SortState } from './table.js';
3
+ import { table } from './table.js';
4
+ import type { TableProps, SortDirection, SortState } from '../../index.js';
4
5
 
5
6
  let {
6
7
  data = [],
7
8
  columns = [],
8
- color = 'default',
9
- size = 'base',
10
9
  bordered = true,
11
10
  striped = false,
12
11
  pageSize = 10,
12
+ currentPage: externalCurrentPage,
13
+ totalItems,
13
14
  selectable = false,
14
15
  selected = $bindable([]),
15
- onrowclick = () => {},
16
+ onrowclick,
16
17
  onsort = () => {},
17
18
  onselect = () => {},
19
+ onpagechange,
20
+ onpagesizechange,
18
21
  class: classname = '',
19
22
  wrapperclass: wrapperClass = '',
20
23
  tableclass: tableClass = '',
@@ -24,56 +27,93 @@
24
27
  thclass: thClass = '',
25
28
  tdclass: tdClass = '',
26
29
  footerclass: footerClass = '',
30
+ paginationclass: paginationClass = '',
27
31
  rowclass = () => '',
28
- loading = false
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'
29
40
  }: TableProps<any> = $props();
30
41
 
31
42
  let sortColumn = $state('');
32
43
  let sortDirection = $state<SortDirection>(null);
33
- let currentPage = $state(1);
44
+ let internalCurrentPage = $state(externalCurrentPage || 1);
45
+ let internalPageSize = $state(pageSize);
34
46
 
35
- // Pagination is automatically determined by pageSize
36
- const pagination = $derived(pageSize > 0 && data.length > pageSize);
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));
37
64
 
38
65
  const {
39
- base,
40
- wrapper,
66
+ base: baseClass,
67
+ wrapper: wrapperBaseClass,
41
68
  table: tableBaseClass,
42
- thead,
43
- tbody,
44
- tr,
45
- th,
46
- td,
47
- footer,
48
- pagination: paginationClass,
49
- emptyState,
50
- sortButton,
51
- sortIcon
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
52
79
  } = $derived(
53
80
  table({
54
- size,
55
- color,
56
81
  bordered,
57
82
  striped
58
83
  })
59
84
  );
60
85
 
61
- const baseClasses = $derived(cn(base(), classname));
62
- const wrapperClasses = $derived(cn(wrapper(), wrapperClass));
86
+ const baseClasses = $derived(cn(baseClass(), classname));
87
+ const wrapperClasses = $derived(cn(wrapperBaseClass(), wrapperClass));
63
88
  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());
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());
71
97
 
72
98
  // 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
- );
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
+ }
77
117
 
78
118
  // Handle sorting
79
119
  function toggleSort(column: string) {
@@ -117,35 +157,271 @@
117
157
  }
118
158
 
119
159
  function handleRowClick(row: any, index: number) {
120
- onrowclick(row, index);
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
+ }
121
175
  }
122
176
 
123
177
  function nextPage() {
124
- if (currentPage < totalPages) {
125
- currentPage++;
178
+ if (internalCurrentPage < totalPages) {
179
+ internalCurrentPage++;
180
+ onpagechange?.(internalCurrentPage);
126
181
  }
127
182
  }
128
183
 
129
184
  function prevPage() {
130
- if (currentPage > 1) {
131
- currentPage--;
185
+ if (internalCurrentPage > 1) {
186
+ internalCurrentPage--;
187
+ onpagechange?.(internalCurrentPage);
132
188
  }
133
189
  }
134
190
 
135
191
  function goToPage(page: number) {
136
- if (page >= 1 && page <= totalPages) {
137
- currentPage = page;
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);
138
227
  }
228
+
229
+ return pages;
139
230
  }
140
231
  </script>
141
232
 
142
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
+
143
419
  <div class={wrapperClasses}>
144
420
  <table class={tableClasses}>
145
421
  <thead class={theadClasses}>
146
422
  <tr>
147
423
  {#if selectable}
148
- <th class={thClasses}>
424
+ <th class={cn(thClasses, 'text-center')}>
149
425
  <input
150
426
  type="checkbox"
151
427
  onchange={() => {
@@ -175,12 +451,12 @@
175
451
  {#if column.sortable}
176
452
  <button
177
453
  type="button"
178
- class={sortButton()}
454
+ class={sortButtonBaseClass()}
179
455
  onclick={() => toggleSort(column.sortKey || column.key)}
180
456
  aria-label={`Sort by ${column.header}`}
181
457
  >
182
458
  {column.header}
183
- <span class={sortIcon()}>
459
+ <span class={sortIconBaseClass()}>
184
460
  {#if sortColumn === (column.sortKey || column.key)}
185
461
  {#if sortDirection === 'asc'}
186
462
  <svg
@@ -262,7 +538,7 @@
262
538
  </div>
263
539
  </td>
264
540
  </tr>
265
- {:else if paginatedData.length === 0}
541
+ {:else if getPaginatedData().length === 0}
266
542
  <tr>
267
543
  <td
268
544
  colspan={selectable ? columns.length + 1 : columns.length}
@@ -272,18 +548,17 @@
272
548
  </td>
273
549
  </tr>
274
550
  {:else}
275
- {#each paginatedData as row, rowIndex (rowIndex)}
551
+ {#each getPaginatedData() as row, rowIndex}
276
552
  <tr
277
- class={cn(
278
- trClasses,
279
- rowclass(row, rowIndex),
280
- selectable && isRowSelected(row) && 'bg-primary-100'
281
- )}
553
+ class={cn(trClasses, rowclass(row, rowIndex), {
554
+ 'bg-primary-100': selectable && isRowSelected(row),
555
+ 'cursor-pointer': onrowclick
556
+ })}
282
557
  onclick={() => handleRowClick(row, rowIndex)}
283
558
  aria-selected={selectable && isRowSelected(row)}
284
559
  >
285
560
  {#if selectable}
286
- <td class={tdClasses}>
561
+ <td class={cn(tdClasses, 'text-center')}>
287
562
  <input
288
563
  type="checkbox"
289
564
  checked={isRowSelected(row)}
@@ -315,97 +590,199 @@
315
590
  </td>
316
591
  {/each}
317
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}
318
600
  {/each}
319
601
  {/if}
320
602
  </tbody>
321
603
  </table>
322
604
  </div>
323
605
 
324
- {#if pagination && totalPages > 1}
606
+ {#if showPaginationControls && (paginationPosition === 'bottom' || paginationPosition === 'both')}
325
607
  <div class={footerClasses}>
326
- <div class={paginationClass}>
608
+ <div class={paginationClasses}>
327
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}
328
626
  <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
627
+ Showing {Math.min(
628
+ (internalCurrentPage - 1) * internalPageSize + 1,
629
+ effectiveTotalItems
630
+ )}
631
+ to {Math.min(internalCurrentPage * internalPageSize, effectiveTotalItems)} of {effectiveTotalItems}
632
+ entries
331
633
  </span>
332
634
  </div>
333
635
 
334
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 -->
335
671
  <button
336
672
  type="button"
337
673
  class={cn(
338
674
  'relative inline-flex items-center rounded-md px-2 py-1 text-sm font-medium',
339
- currentPage === 1
675
+ internalCurrentPage === 1
340
676
  ? 'text-default-300 cursor-not-allowed'
341
677
  : 'text-default-700 hover:bg-default-100'
342
678
  )}
343
679
  onclick={prevPage}
344
- disabled={currentPage === 1}
680
+ disabled={internalCurrentPage === 1}
345
681
  aria-label="Previous page"
346
682
  >
683
+ <!-- Chevron Left SVG -->
347
684
  <svg
348
685
  xmlns="http://www.w3.org/2000/svg"
349
- viewBox="0 0 20 20"
350
- fill="currentColor"
351
- class="h-5 w-5"
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"
352
695
  >
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
- />
696
+ <path d="m15 18-6-6 6-6"></path>
356
697
  </svg>
357
698
  </button>
358
699
 
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}
700
+ <!-- Page numbers -->
701
+ {#if paginationTemplate === 'full'}
702
+ {#each getPageNumbers() as pageNum}
369
703
  <button
370
704
  type="button"
371
705
  class={cn(
372
706
  'relative inline-flex items-center rounded-md px-3 py-1 text-sm font-medium',
373
- currentPage === pageNum
707
+ internalCurrentPage === pageNum
374
708
  ? 'bg-primary-100 text-primary-700'
375
709
  : 'text-default-700 hover:bg-default-100'
376
710
  )}
377
711
  onclick={() => goToPage(pageNum)}
378
712
  aria-label={`Page ${pageNum}`}
379
- aria-current={currentPage === pageNum ? 'page' : undefined}
713
+ aria-current={internalCurrentPage === pageNum ? 'page' : undefined}
380
714
  >
381
715
  {pageNum}
382
716
  </button>
383
- {/if}
384
- {/each}
717
+ {/each}
718
+ {:else}
719
+ <span class="text-default-500 px-2 text-sm">
720
+ Page {internalCurrentPage} of {totalPages}
721
+ </span>
722
+ {/if}
385
723
 
724
+ <!-- Next page button -->
386
725
  <button
387
726
  type="button"
388
727
  class={cn(
389
728
  'relative inline-flex items-center rounded-md px-2 py-1 text-sm font-medium',
390
- currentPage === totalPages
729
+ internalCurrentPage === totalPages
391
730
  ? 'text-default-300 cursor-not-allowed'
392
731
  : 'text-default-700 hover:bg-default-100'
393
732
  )}
394
733
  onclick={nextPage}
395
- disabled={currentPage === totalPages}
734
+ disabled={internalCurrentPage === totalPages}
396
735
  aria-label="Next page"
397
736
  >
737
+ <!-- Chevron Right SVG -->
398
738
  <svg
399
739
  xmlns="http://www.w3.org/2000/svg"
400
- viewBox="0 0 20 20"
401
- fill="currentColor"
402
- class="h-5 w-5"
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"
403
749
  >
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
- />
750
+ <path d="m9 18 6-6-6-6"></path>
407
751
  </svg>
408
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}
409
786
  </div>
410
787
  </div>
411
788
  </div>