@shotleybuilder/svelte-table-kit 0.1.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.
@@ -0,0 +1,838 @@
1
+ <script>import { writable } from "svelte/store";
2
+ import {
3
+ createSvelteTable,
4
+ getCoreRowModel,
5
+ getPaginationRowModel,
6
+ getSortedRowModel,
7
+ getFilteredRowModel,
8
+ getGroupedRowModel,
9
+ getExpandedRowModel,
10
+ flexRender
11
+ } from "@tanstack/svelte-table";
12
+ import {
13
+ loadColumnVisibility,
14
+ saveColumnVisibility,
15
+ loadColumnSizing,
16
+ saveColumnSizing,
17
+ loadColumnFilters,
18
+ saveColumnFilters,
19
+ loadColumnOrder,
20
+ saveColumnOrder,
21
+ isBrowser
22
+ } from "./stores/persistence";
23
+ import { applyFilters } from "./utils/filters";
24
+ import FilterBar from "./components/FilterBar.svelte";
25
+ import GroupBar from "./components/GroupBar.svelte";
26
+ export let data = [];
27
+ export let columns = [];
28
+ export let storageKey = "table-kit";
29
+ export let persistState = true;
30
+ export let features = {
31
+ columnVisibility: true,
32
+ columnResizing: true,
33
+ columnReordering: true,
34
+ filtering: true,
35
+ sorting: true,
36
+ pagination: true
37
+ };
38
+ export let onRowClick = void 0;
39
+ export let onRowSelect = void 0;
40
+ export let onStateChange = void 0;
41
+ let sorting = writable([]);
42
+ let columnVisibility = writable(
43
+ persistState && storageKey ? loadColumnVisibility(storageKey) : {}
44
+ );
45
+ let columnSizing = writable(
46
+ persistState && storageKey ? loadColumnSizing(storageKey) : {}
47
+ );
48
+ let columnFilters = writable(
49
+ persistState && storageKey ? loadColumnFilters(storageKey) : []
50
+ );
51
+ let columnOrder = writable(
52
+ persistState && storageKey ? loadColumnOrder(storageKey) : []
53
+ );
54
+ let filterConditions = writable([]);
55
+ let filterLogic = writable("and");
56
+ let grouping = writable([]);
57
+ let expanded = writable(true);
58
+ $: filteredData = applyFilters(data, $filterConditions, $filterLogic);
59
+ $: if (persistState && storageKey && isBrowser) {
60
+ saveColumnVisibility(storageKey, $columnVisibility);
61
+ saveColumnSizing(storageKey, $columnSizing);
62
+ saveColumnFilters(storageKey, $columnFilters);
63
+ saveColumnOrder(storageKey, $columnOrder);
64
+ }
65
+ let showColumnPicker = false;
66
+ let draggedColumnId = null;
67
+ const options = writable({
68
+ data: filteredData,
69
+ columns,
70
+ columnResizeMode: "onChange",
71
+ enableColumnResizing: features.columnResizing !== false,
72
+ enableGrouping: features.grouping !== false,
73
+ state: {
74
+ sorting: $sorting,
75
+ columnVisibility: $columnVisibility,
76
+ columnSizing: $columnSizing,
77
+ columnFilters: $columnFilters,
78
+ columnOrder: $columnOrder,
79
+ grouping: $grouping,
80
+ expanded: $expanded
81
+ },
82
+ onSortingChange: (updater) => {
83
+ if (updater instanceof Function) {
84
+ sorting.update(updater);
85
+ } else {
86
+ sorting.set(updater);
87
+ }
88
+ },
89
+ onColumnVisibilityChange: (updater) => {
90
+ if (updater instanceof Function) {
91
+ columnVisibility.update(updater);
92
+ } else {
93
+ columnVisibility.set(updater);
94
+ }
95
+ },
96
+ onColumnSizingChange: (updater) => {
97
+ if (updater instanceof Function) {
98
+ columnSizing.update(updater);
99
+ } else {
100
+ columnSizing.set(updater);
101
+ }
102
+ },
103
+ onColumnFiltersChange: (updater) => {
104
+ if (updater instanceof Function) {
105
+ columnFilters.update(updater);
106
+ } else {
107
+ columnFilters.set(updater);
108
+ }
109
+ },
110
+ onColumnOrderChange: (updater) => {
111
+ if (updater instanceof Function) {
112
+ columnOrder.update(updater);
113
+ } else {
114
+ columnOrder.set(updater);
115
+ }
116
+ },
117
+ onGroupingChange: (updater) => {
118
+ if (updater instanceof Function) {
119
+ grouping.update(updater);
120
+ } else {
121
+ grouping.set(updater);
122
+ }
123
+ },
124
+ onExpandedChange: (updater) => {
125
+ if (updater instanceof Function) {
126
+ expanded.update(updater);
127
+ } else {
128
+ expanded.set(updater);
129
+ }
130
+ },
131
+ getCoreRowModel: getCoreRowModel(),
132
+ ...features.sorting !== false && { getSortedRowModel: getSortedRowModel() },
133
+ ...features.filtering !== false && { getFilteredRowModel: getFilteredRowModel() },
134
+ ...features.grouping !== false && { getGroupedRowModel: getGroupedRowModel() },
135
+ ...features.grouping !== false && { getExpandedRowModel: getExpandedRowModel() },
136
+ ...features.pagination !== false && { getPaginationRowModel: getPaginationRowModel() }
137
+ });
138
+ $: options.update((old) => ({
139
+ ...old,
140
+ data: filteredData,
141
+ state: {
142
+ sorting: $sorting,
143
+ columnVisibility: $columnVisibility,
144
+ columnSizing: $columnSizing,
145
+ columnFilters: $columnFilters,
146
+ columnOrder: $columnOrder,
147
+ grouping: $grouping,
148
+ expanded: $expanded
149
+ }
150
+ }));
151
+ const table = createSvelteTable(options);
152
+ function toggleAllColumns(show) {
153
+ $table.getAllLeafColumns().forEach((column) => {
154
+ column.toggleVisibility(show);
155
+ });
156
+ }
157
+ function handleDragStart(columnId) {
158
+ if (features.columnReordering === false) return;
159
+ draggedColumnId = columnId;
160
+ }
161
+ function handleDragOver(event) {
162
+ if (features.columnReordering === false) return;
163
+ event.preventDefault();
164
+ }
165
+ function handleDrop(targetColumnId) {
166
+ if (features.columnReordering === false) return;
167
+ if (!draggedColumnId || draggedColumnId === targetColumnId) {
168
+ draggedColumnId = null;
169
+ return;
170
+ }
171
+ const oldIndex = $columnOrder.indexOf(draggedColumnId);
172
+ const newIndex = $columnOrder.indexOf(targetColumnId);
173
+ if (oldIndex !== -1 && newIndex !== -1) {
174
+ const newColumnOrder = [...$columnOrder];
175
+ const [movedColumn] = newColumnOrder.splice(oldIndex, 1);
176
+ newColumnOrder.splice(newIndex, 0, movedColumn);
177
+ columnOrder.set(newColumnOrder);
178
+ }
179
+ draggedColumnId = null;
180
+ }
181
+ $: if ($columnOrder.length === 0 && columns.length > 0) {
182
+ columnOrder.set(columns.map((col) => col.accessorKey || col.id));
183
+ }
184
+ $: hasActiveFilters = $filterConditions.length > 0;
185
+ $: if (onStateChange) {
186
+ onStateChange({
187
+ columnVisibility: $columnVisibility,
188
+ columnOrder: $columnOrder,
189
+ columnSizing: $columnSizing,
190
+ columnFilters: $columnFilters,
191
+ sorting: $sorting,
192
+ pagination: $table.getState().pagination
193
+ });
194
+ }
195
+ </script>
196
+
197
+ <div class="table-kit-container">
198
+ <!-- Filters and Controls -->
199
+ {#if features.filtering !== false || features.grouping !== false || features.columnVisibility !== false}
200
+ <div class="table-kit-toolbar">
201
+ <!-- Filter Controls -->
202
+ {#if features.filtering !== false}
203
+ <div class="table-kit-filters">
204
+ <FilterBar
205
+ {columns}
206
+ conditions={$filterConditions}
207
+ onConditionsChange={(newConditions) => filterConditions.set(newConditions)}
208
+ logic={$filterLogic}
209
+ onLogicChange={(newLogic) => filterLogic.set(newLogic)}
210
+ />
211
+ </div>
212
+ {/if}
213
+
214
+ <!-- Group Controls -->
215
+ {#if features.grouping !== false}
216
+ <div class="table-kit-groups">
217
+ <GroupBar
218
+ {columns}
219
+ grouping={$grouping}
220
+ onGroupingChange={(newGrouping) => grouping.set(newGrouping)}
221
+ />
222
+ </div>
223
+ {/if}
224
+
225
+ <!-- Column Picker -->
226
+ {#if features.columnVisibility !== false}
227
+ <div class="table-kit-column-picker">
228
+ <div class="relative">
229
+ <button
230
+ on:click={() => (showColumnPicker = !showColumnPicker)}
231
+ class="column-picker-btn"
232
+ >
233
+ <svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
234
+ <path
235
+ stroke-linecap="round"
236
+ stroke-linejoin="round"
237
+ stroke-width="2"
238
+ d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"
239
+ />
240
+ </svg>
241
+ Columns
242
+ </button>
243
+
244
+ {#if showColumnPicker}
245
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
246
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
247
+ <div class="backdrop" on:click={() => (showColumnPicker = false)} />
248
+ <div class="column-picker-dropdown">
249
+ <div class="dropdown-header">
250
+ <span>Toggle Columns</span>
251
+ <div class="header-actions">
252
+ <button on:click={() => toggleAllColumns(true)} class="text-btn">
253
+ Show All
254
+ </button>
255
+ <span class="separator">|</span>
256
+ <button on:click={() => toggleAllColumns(false)} class="text-btn">
257
+ Hide All
258
+ </button>
259
+ </div>
260
+ </div>
261
+ <div class="column-list">
262
+ {#each $table.getAllLeafColumns() as column}
263
+ <label class="column-item">
264
+ <input
265
+ type="checkbox"
266
+ checked={column.getIsVisible()}
267
+ on:change={() => column.toggleVisibility()}
268
+ />
269
+ <span>{column.columnDef.header}</span>
270
+ </label>
271
+ {/each}
272
+ </div>
273
+ </div>
274
+ {/if}
275
+ </div>
276
+ </div>
277
+ {/if}
278
+ </div>
279
+ {/if}
280
+
281
+ <!-- Table -->
282
+ {#if data.length === 0}
283
+ <div class="table-kit-empty">
284
+ <slot name="empty">
285
+ <p>No data available.</p>
286
+ </slot>
287
+ </div>
288
+ {:else}
289
+ <div class="table-kit-scroll">
290
+ <table class="table-kit-table">
291
+ <thead>
292
+ {#each $table.getHeaderGroups() as headerGroup}
293
+ <tr>
294
+ {#each headerGroup.headers as header}
295
+ <th
296
+ draggable={features.columnReordering !== false}
297
+ on:dragstart={() => handleDragStart(header.column.id)}
298
+ on:dragover={handleDragOver}
299
+ on:drop|preventDefault={() => handleDrop(header.column.id)}
300
+ class:dragging={draggedColumnId === header.column.id}
301
+ style="width: {header.getSize()}px; cursor: {features.columnReordering !==
302
+ false
303
+ ? 'grab'
304
+ : 'default'};"
305
+ >
306
+ {#if !header.isPlaceholder}
307
+ <div class="th-content">
308
+ <button
309
+ class="sort-btn"
310
+ class:sortable={header.column.getCanSort()}
311
+ on:click={header.column.getToggleSortingHandler()}
312
+ >
313
+ <svelte:component
314
+ this={flexRender(header.column.columnDef.header, header.getContext())}
315
+ />
316
+ {#if features.sorting !== false && header.column.getCanSort()}
317
+ <span class="sort-icon">
318
+ {{
319
+ asc: '↑',
320
+ desc: '↓'
321
+ }[header.column.getIsSorted()] ?? '↕'}
322
+ </span>
323
+ {/if}
324
+ </button>
325
+ </div>
326
+ <!-- Resize Handle -->
327
+ {#if features.columnResizing !== false && header.column.getCanResize()}
328
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
329
+ <div
330
+ on:mousedown={header.getResizeHandler()}
331
+ on:touchstart={header.getResizeHandler()}
332
+ class="resize-handle"
333
+ class:resizing={header.column.getIsResizing()}
334
+ />
335
+ {/if}
336
+ {/if}
337
+ </th>
338
+ {/each}
339
+ </tr>
340
+ {/each}
341
+ </thead>
342
+ <tbody>
343
+ {#each $table.getRowModel().rows as row}
344
+ <tr
345
+ class:clickable={onRowClick !== undefined && !row.getIsGrouped()}
346
+ class:group-row={row.getIsGrouped()}
347
+ on:click={() => onRowClick && !row.getIsGrouped() && onRowClick(row.original)}
348
+ >
349
+ {#each row.getVisibleCells() as cell}
350
+ <td style="padding-left: {row.depth * 2}rem">
351
+ {#if cell.getIsGrouped()}
352
+ <!-- Grouping column - show expand/collapse button -->
353
+ <div class="group-cell">
354
+ <button
355
+ class="expand-btn"
356
+ on:click|stopPropagation={() => row.toggleExpanded()}
357
+ >
358
+ {#if row.getIsExpanded()}
359
+ <svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
360
+ <path
361
+ stroke-linecap="round"
362
+ stroke-linejoin="round"
363
+ stroke-width="2"
364
+ d="M19 9l-7 7-7-7"
365
+ />
366
+ </svg>
367
+ {:else}
368
+ <svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
369
+ <path
370
+ stroke-linecap="round"
371
+ stroke-linejoin="round"
372
+ stroke-width="2"
373
+ d="M9 5l7 7-7 7"
374
+ />
375
+ </svg>
376
+ {/if}
377
+ </button>
378
+ <strong>
379
+ <svelte:component
380
+ this={flexRender(cell.column.columnDef.cell, cell.getContext())}
381
+ />
382
+ </strong>
383
+ <span class="group-count">({row.subRows.length})</span>
384
+ </div>
385
+ {:else if cell.getIsAggregated()}
386
+ <!-- Aggregated cell - show computed value -->
387
+ <slot name="cell" {cell} column={cell.column.id}>
388
+ <svelte:component
389
+ this={flexRender(
390
+ cell.column.columnDef.aggregatedCell ?? cell.column.columnDef.cell,
391
+ cell.getContext()
392
+ )}
393
+ />
394
+ </slot>
395
+ {:else if cell.getIsPlaceholder()}
396
+ <!-- Placeholder cell - empty -->
397
+ {:else}
398
+ <!-- Normal cell -->
399
+ <slot name="cell" {cell} column={cell.column.id}>
400
+ <svelte:component
401
+ this={flexRender(cell.column.columnDef.cell, cell.getContext())}
402
+ />
403
+ </slot>
404
+ {/if}
405
+ </td>
406
+ {/each}
407
+ </tr>
408
+ {/each}
409
+ </tbody>
410
+ </table>
411
+ </div>
412
+
413
+ <!-- Pagination -->
414
+ {#if features.pagination !== false}
415
+ <div class="table-kit-pagination">
416
+ <div class="pagination-info">
417
+ <p>
418
+ Showing
419
+ <span class="font-medium">
420
+ {$table.getState().pagination.pageIndex * $table.getState().pagination.pageSize + 1}
421
+ </span>
422
+ to
423
+ <span class="font-medium">
424
+ {Math.min(
425
+ ($table.getState().pagination.pageIndex + 1) * $table.getState().pagination.pageSize,
426
+ $table.getFilteredRowModel().rows.length
427
+ )}
428
+ </span>
429
+ of
430
+ <span class="font-medium">{$table.getFilteredRowModel().rows.length}</span>
431
+ results
432
+ </p>
433
+ </div>
434
+ <div class="pagination-controls">
435
+ <button
436
+ on:click={() => $table.previousPage()}
437
+ disabled={!$table.getCanPreviousPage()}
438
+ class="pagination-btn"
439
+ >
440
+ <svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
441
+ <path
442
+ stroke-linecap="round"
443
+ stroke-linejoin="round"
444
+ stroke-width="2"
445
+ d="M15 19l-7-7 7-7"
446
+ />
447
+ </svg>
448
+ </button>
449
+ <span class="page-indicator">
450
+ Page {$table.getState().pagination.pageIndex + 1} of {$table.getPageCount()}
451
+ </span>
452
+ <button
453
+ on:click={() => $table.nextPage()}
454
+ disabled={!$table.getCanNextPage()}
455
+ class="pagination-btn"
456
+ >
457
+ <svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
458
+ <path
459
+ stroke-linecap="round"
460
+ stroke-linejoin="round"
461
+ stroke-width="2"
462
+ d="M9 5l7 7-7 7"
463
+ />
464
+ </svg>
465
+ </button>
466
+ </div>
467
+ </div>
468
+ {/if}
469
+ {/if}
470
+ </div>
471
+
472
+ <style>
473
+ /* Container */
474
+ .table-kit-container {
475
+ width: 100%;
476
+ }
477
+
478
+ /* Toolbar */
479
+ .table-kit-toolbar {
480
+ display: flex;
481
+ align-items: flex-end;
482
+ justify-content: space-between;
483
+ gap: 1rem;
484
+ margin-bottom: 1rem;
485
+ }
486
+
487
+ .table-kit-filters {
488
+ flex: 1;
489
+ }
490
+
491
+ .clear-filters-btn {
492
+ font-size: 0.75rem;
493
+ color: #4f46e5;
494
+ cursor: pointer;
495
+ background: none;
496
+ border: none;
497
+ padding: 0;
498
+ margin-bottom: 0.25rem;
499
+ }
500
+
501
+ .clear-filters-btn:hover {
502
+ color: #4338ca;
503
+ }
504
+
505
+ /* Column Picker */
506
+ .table-kit-column-picker {
507
+ flex-shrink: 0;
508
+ }
509
+
510
+ .relative {
511
+ position: relative;
512
+ }
513
+
514
+ .column-picker-btn {
515
+ display: inline-flex;
516
+ align-items: center;
517
+ gap: 0.5rem;
518
+ padding: 0.5rem 1rem;
519
+ font-size: 0.875rem;
520
+ font-weight: 500;
521
+ color: #374151;
522
+ background: white;
523
+ border: 1px solid #d1d5db;
524
+ border-radius: 0.375rem;
525
+ cursor: pointer;
526
+ }
527
+
528
+ .column-picker-btn:hover {
529
+ background: #f9fafb;
530
+ }
531
+
532
+ .icon {
533
+ width: 1rem;
534
+ height: 1rem;
535
+ }
536
+
537
+ .backdrop {
538
+ position: fixed;
539
+ inset: 0;
540
+ z-index: 10;
541
+ }
542
+
543
+ .column-picker-dropdown {
544
+ position: absolute;
545
+ right: 0;
546
+ z-index: 20;
547
+ margin-top: 0.5rem;
548
+ width: 14rem;
549
+ border-radius: 0.375rem;
550
+ background: white;
551
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
552
+ border: 1px solid rgba(0, 0, 0, 0.05);
553
+ }
554
+
555
+ .dropdown-header {
556
+ display: flex;
557
+ align-items: center;
558
+ justify-content: space-between;
559
+ padding: 0.5rem 1rem;
560
+ border-bottom: 1px solid #e5e7eb;
561
+ }
562
+
563
+ .dropdown-header span {
564
+ font-size: 0.875rem;
565
+ font-weight: 500;
566
+ color: #111827;
567
+ }
568
+
569
+ .header-actions {
570
+ display: flex;
571
+ gap: 0.25rem;
572
+ align-items: center;
573
+ }
574
+
575
+ .text-btn {
576
+ font-size: 0.75rem;
577
+ color: #4f46e5;
578
+ cursor: pointer;
579
+ background: none;
580
+ border: none;
581
+ padding: 0;
582
+ }
583
+
584
+ .text-btn:hover {
585
+ color: #4338ca;
586
+ }
587
+
588
+ .separator {
589
+ color: #d1d5db;
590
+ }
591
+
592
+ .column-list {
593
+ max-height: 16rem;
594
+ overflow-y: auto;
595
+ }
596
+
597
+ .column-item {
598
+ display: flex;
599
+ align-items: center;
600
+ padding: 0.5rem 1rem;
601
+ font-size: 0.875rem;
602
+ color: #374151;
603
+ cursor: pointer;
604
+ }
605
+
606
+ .column-item:hover {
607
+ background: #f9fafb;
608
+ }
609
+
610
+ .column-item input {
611
+ margin-right: 0.75rem;
612
+ }
613
+
614
+ /* Table */
615
+ .table-kit-empty {
616
+ padding: 2rem 1rem;
617
+ text-align: center;
618
+ color: #6b7280;
619
+ background: white;
620
+ border: 1px solid #e5e7eb;
621
+ border-radius: 0.5rem;
622
+ }
623
+
624
+ .table-kit-scroll {
625
+ overflow-x: auto;
626
+ background: white;
627
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
628
+ border-radius: 0.5rem;
629
+ }
630
+
631
+ .table-kit-table {
632
+ width: 100%;
633
+ border-collapse: collapse;
634
+ }
635
+
636
+ thead {
637
+ background: #f9fafb;
638
+ }
639
+
640
+ th {
641
+ position: relative;
642
+ padding: 0.75rem 1.5rem;
643
+ text-align: left;
644
+ font-size: 0.75rem;
645
+ font-weight: 500;
646
+ color: #6b7280;
647
+ text-transform: uppercase;
648
+ letter-spacing: 0.05em;
649
+ transition: opacity 0.2s;
650
+ }
651
+
652
+ th.dragging {
653
+ opacity: 0.5;
654
+ }
655
+
656
+ .th-content {
657
+ display: flex;
658
+ align-items: center;
659
+ gap: 0.25rem;
660
+ }
661
+
662
+ .sort-btn {
663
+ display: flex;
664
+ align-items: center;
665
+ gap: 0.25rem;
666
+ background: none;
667
+ border: none;
668
+ padding: 0;
669
+ font-size: inherit;
670
+ font-weight: inherit;
671
+ color: inherit;
672
+ text-transform: inherit;
673
+ letter-spacing: inherit;
674
+ }
675
+
676
+ .sort-btn.sortable {
677
+ cursor: pointer;
678
+ }
679
+
680
+ .sort-btn.sortable:hover {
681
+ color: #374151;
682
+ }
683
+
684
+ .sort-icon {
685
+ color: #9ca3af;
686
+ }
687
+
688
+ .resize-handle {
689
+ position: absolute;
690
+ top: 0;
691
+ right: 0;
692
+ height: 100%;
693
+ width: 0.25rem;
694
+ cursor: col-resize;
695
+ user-select: none;
696
+ touch-action: none;
697
+ background: transparent;
698
+ opacity: 0;
699
+ transition: opacity 0.2s, background 0.2s;
700
+ }
701
+
702
+ .resize-handle:hover,
703
+ .resize-handle.resizing {
704
+ background: #4f46e5;
705
+ opacity: 1;
706
+ }
707
+
708
+ tbody tr {
709
+ border-bottom: 1px solid #e5e7eb;
710
+ }
711
+
712
+ tbody tr:hover {
713
+ background: #f9fafb;
714
+ }
715
+
716
+ tbody tr.clickable {
717
+ cursor: pointer;
718
+ }
719
+
720
+ td {
721
+ padding: 1rem 1.5rem;
722
+ font-size: 0.875rem;
723
+ color: #111827;
724
+ }
725
+
726
+ /* Group Rows */
727
+ tbody tr.group-row {
728
+ background: #f9fafb;
729
+ font-weight: 500;
730
+ }
731
+
732
+ tbody tr.group-row:hover {
733
+ background: #f3f4f6;
734
+ }
735
+
736
+ .group-cell {
737
+ display: flex;
738
+ align-items: center;
739
+ gap: 0.5rem;
740
+ }
741
+
742
+ .expand-btn {
743
+ display: inline-flex;
744
+ align-items: center;
745
+ justify-content: center;
746
+ padding: 0.25rem;
747
+ background: none;
748
+ border: none;
749
+ cursor: pointer;
750
+ color: #6b7280;
751
+ border-radius: 0.25rem;
752
+ transition: all 0.2s;
753
+ }
754
+
755
+ .expand-btn:hover {
756
+ background: #e5e7eb;
757
+ color: #374151;
758
+ }
759
+
760
+ .group-count {
761
+ font-size: 0.75rem;
762
+ font-weight: 400;
763
+ color: #6b7280;
764
+ }
765
+
766
+ /* Pagination */
767
+ .table-kit-pagination {
768
+ display: flex;
769
+ align-items: center;
770
+ justify-content: space-between;
771
+ padding: 0.75rem 1rem;
772
+ background: white;
773
+ border-top: 1px solid #e5e7eb;
774
+ border-bottom-left-radius: 0.5rem;
775
+ border-bottom-right-radius: 0.5rem;
776
+ }
777
+
778
+ .pagination-info p {
779
+ font-size: 0.875rem;
780
+ color: #374151;
781
+ margin: 0;
782
+ }
783
+
784
+ .pagination-info .font-medium {
785
+ font-weight: 500;
786
+ }
787
+
788
+ .pagination-controls {
789
+ display: flex;
790
+ align-items: center;
791
+ gap: 1rem;
792
+ border-radius: 0.375rem;
793
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
794
+ }
795
+
796
+ .pagination-btn {
797
+ display: inline-flex;
798
+ align-items: center;
799
+ padding: 0.5rem;
800
+ font-size: 0.875rem;
801
+ font-weight: 500;
802
+ color: #6b7280;
803
+ background: white;
804
+ border: 1px solid #d1d5db;
805
+ cursor: pointer;
806
+ }
807
+
808
+ .pagination-btn:first-child {
809
+ border-top-left-radius: 0.375rem;
810
+ border-bottom-left-radius: 0.375rem;
811
+ }
812
+
813
+ .pagination-btn:last-child {
814
+ border-top-right-radius: 0.375rem;
815
+ border-bottom-right-radius: 0.375rem;
816
+ }
817
+
818
+ .pagination-btn:hover:not(:disabled) {
819
+ background: #f9fafb;
820
+ }
821
+
822
+ .pagination-btn:disabled {
823
+ opacity: 0.5;
824
+ cursor: not-allowed;
825
+ }
826
+
827
+ .page-indicator {
828
+ display: inline-flex;
829
+ align-items: center;
830
+ padding: 0.5rem 1rem;
831
+ font-size: 0.875rem;
832
+ font-weight: 500;
833
+ color: #374151;
834
+ background: white;
835
+ border-top: 1px solid #d1d5db;
836
+ border-bottom: 1px solid #d1d5db;
837
+ }
838
+ </style>