@casinogate/ui 1.0.0 → 1.0.1

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,1285 @@
1
+ # DataTable Component Guide
2
+
3
+ A powerful, flexible, and feature-rich data table component built on top of `@tanstack/table-core` for Svelte 5 applications. The DataTable provides advanced functionality including sorting, pagination, resizing, infinite scrolling, and fully customizable rendering.
4
+
5
+ ## 🎨 [View Live Examples in Storybook](https://static.casinogate.dev/components/master/?path=/story/ui-kit-datatable--primitive)
6
+
7
+ Explore interactive examples and experiment with all DataTable features in our live Storybook.
8
+
9
+ ---
10
+
11
+ ## 📚 Table of Contents
12
+
13
+ - [DataTable Component Guide](#datatable-component-guide)
14
+ - [🎨 View Live Examples in Storybook](#-view-live-examples-in-storybook)
15
+ - [📚 Table of Contents](#-table-of-contents)
16
+ - [When to Use](#when-to-use)
17
+ - [Installation \& Import](#installation--import)
18
+ - [From Package](#from-package)
19
+ - [Import the Component](#import-the-component)
20
+ - [For Advanced Use Cases (Primitives)](#for-advanced-use-cases-primitives)
21
+ - [Basic Usage](#basic-usage)
22
+ - [Minimal Example](#minimal-example)
23
+ - [Key Concepts](#key-concepts)
24
+ - [Component Architecture](#component-architecture)
25
+ - [1. Preset Component (Recommended)](#1-preset-component-recommended)
26
+ - [2. Primitive Components (Advanced)](#2-primitive-components-advanced)
27
+ - [Advanced Features](#advanced-features)
28
+ - [Sorting](#sorting)
29
+ - [Pagination](#pagination)
30
+ - [Column Resizing](#column-resizing)
31
+ - [Infinite Scrolling](#infinite-scrolling)
32
+ - [Custom Cell Rendering](#custom-cell-rendering)
33
+ - [Simple String/HTML Rendering](#simple-stringhtml-rendering)
34
+ - [Using `renderComponent` for Svelte Components](#using-rendercomponent-for-svelte-components)
35
+ - [Using `renderSnippet` for Inline Templates](#using-rendersnippet-for-inline-templates)
36
+ - [Comparison: When to Use Each Approach](#comparison-when-to-use-each-approach)
37
+ - [Practical Examples with renderComponent](#practical-examples-with-rendercomponent)
38
+ - [Interactive Checkbox Column](#interactive-checkbox-column)
39
+ - [Avatar with Fallback](#avatar-with-fallback)
40
+ - [Dropdown Actions Menu](#dropdown-actions-menu)
41
+ - [Loading States](#loading-states)
42
+ - [Empty States](#empty-states)
43
+ - [API Reference](#api-reference)
44
+ - [DataTable Props](#datatable-props)
45
+ - [createTable Options](#createtable-options)
46
+ - [Column Definition](#column-definition)
47
+ - [Table Utilities](#table-utilities)
48
+ - [TypeScript Support](#typescript-support)
49
+ - [Typing Your Data](#typing-your-data)
50
+ - [Extend Column Meta](#extend-column-meta)
51
+ - [Styling \& Theming](#styling--theming)
52
+ - [Using Variants](#using-variants)
53
+ - [Custom Styling](#custom-styling)
54
+ - [Cell-Level Styling](#cell-level-styling)
55
+ - [Tailwind Integration](#tailwind-integration)
56
+ - [Common Patterns](#common-patterns)
57
+ - [Server-Side Pagination](#server-side-pagination)
58
+ - [Row Click Handling](#row-click-handling)
59
+ - [Conditional Row Styling](#conditional-row-styling)
60
+ - [Searchable Table](#searchable-table)
61
+ - [Troubleshooting](#troubleshooting)
62
+ - [Common Issues](#common-issues)
63
+ - [❌ Data not updating when changed](#-data-not-updating-when-changed)
64
+ - [❌ "Cannot read property 'getHeaderGroups' of undefined"](#-cannot-read-property-getheadergroups-of-undefined)
65
+ - [❌ Sorting not working](#-sorting-not-working)
66
+ - [❌ Column resize handles not visible](#-column-resize-handles-not-visible)
67
+ - [❌ Performance issues with large datasets](#-performance-issues-with-large-datasets)
68
+ - [Debugging Tips](#debugging-tips)
69
+ - [Accessibility](#accessibility)
70
+ - [Best Practices](#best-practices)
71
+ - [Further Resources](#further-resources)
72
+ - [Quick Reference](#quick-reference)
73
+ - [Minimal Setup](#minimal-setup)
74
+ - [With Pagination](#with-pagination)
75
+ - [With Sorting](#with-sorting)
76
+ - [With Resizing](#with-resizing)
77
+
78
+ ---
79
+
80
+ ## When to Use
81
+
82
+ Use the DataTable component when you need to:
83
+
84
+ - Display tabular data with consistent styling
85
+ - Implement sorting, filtering, or pagination
86
+ - Handle large datasets efficiently
87
+ - Provide column resizing capabilities
88
+ - Implement infinite scrolling for real-time data
89
+ - Customize cell rendering with complex components
90
+ - Maintain responsive table layouts
91
+
92
+ ---
93
+
94
+ ## Installation & Import
95
+
96
+ ### From Package
97
+
98
+ If using the published package:
99
+
100
+ ```bash
101
+ pnpm add @casinogate/ui
102
+ ```
103
+
104
+ ### Import the Component
105
+
106
+ ```typescript
107
+ import { DataTable, createTable, rowModels } from '@casinogate/ui';
108
+ import type { ColumnDef } from '@casinogate/ui';
109
+
110
+ // Import styles (required)
111
+ import '@casinogate/ui/styles.css';
112
+ ```
113
+
114
+ ### For Advanced Use Cases (Primitives)
115
+
116
+ For complete control over table structure:
117
+
118
+ ```typescript
119
+ import { DataTablePrimitive } from '@casinogate/ui';
120
+
121
+ const { Root, Table, Header, Head, Body, Row, Cell, SortButton, ResizeHandler, FlexRender } = DataTablePrimitive;
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Basic Usage
127
+
128
+ ### Minimal Example
129
+
130
+ Here's the simplest way to create a functional data table:
131
+
132
+ ```svelte
133
+ <script lang="ts">
134
+ import { DataTable, createTable, rowModels } from '@casinogate/ui';
135
+ import type { ColumnDef } from '@casinogate/ui';
136
+
137
+ type User = {
138
+ id: number;
139
+ name: string;
140
+ email: string;
141
+ };
142
+
143
+ const data: User[] = [
144
+ { id: 1, name: 'John Doe', email: 'john@example.com' },
145
+ { id: 2, name: 'Jane Smith', email: 'jane@example.com' },
146
+ ];
147
+
148
+ const columns: ColumnDef<User>[] = [
149
+ {
150
+ accessorKey: 'id',
151
+ header: 'ID',
152
+ },
153
+ {
154
+ accessorKey: 'name',
155
+ header: 'Name',
156
+ },
157
+ {
158
+ accessorKey: 'email',
159
+ header: 'Email',
160
+ },
161
+ ];
162
+
163
+ const table = createTable({
164
+ get data() {
165
+ return data;
166
+ },
167
+ columns,
168
+ getCoreRowModel: rowModels.coreRowModel(),
169
+ });
170
+ </script>
171
+
172
+ <DataTable {table} />
173
+ ```
174
+
175
+ ### Key Concepts
176
+
177
+ 1. **Define your data type** - Use TypeScript interfaces for type safety
178
+ 2. **Create column definitions** - Specify which fields to display and how
179
+ 3. **Initialize the table** - Use `createTable` with your data and columns
180
+ 4. **Render the component** - Pass the table instance to `<DataTable>`
181
+
182
+ ---
183
+
184
+ ## Component Architecture
185
+
186
+ The DataTable is available in two forms:
187
+
188
+ ### 1. Preset Component (Recommended)
189
+
190
+ The high-level `<DataTable>` component with built-in features:
191
+
192
+ - Automatic header and body rendering
193
+ - Built-in loading states with spinner
194
+ - Empty state handling
195
+ - Sorting button integration
196
+ - Column resize handlers
197
+
198
+ **Use when:** You want a fully-functional table quickly.
199
+
200
+ ### 2. Primitive Components (Advanced)
201
+
202
+ Fine-grained control with individual components:
203
+
204
+ - `DataTablePrimitive.Root` - Container with context
205
+ - `DataTablePrimitive.Table` - Table wrapper
206
+ - `DataTablePrimitive.Header` - Table header section
207
+ - `DataTablePrimitive.Head` - Header cell
208
+ - `DataTablePrimitive.Body` - Table body section
209
+ - `DataTablePrimitive.Row` - Table row
210
+ - `DataTablePrimitive.Cell` - Table cell
211
+ - `DataTablePrimitive.SortButton` - Sorting control
212
+ - `DataTablePrimitive.ResizeHandler` - Column resize handle
213
+ - `DataTablePrimitive.FlexRender` - Content renderer
214
+
215
+ **Use when:** You need complete control over markup and behavior.
216
+
217
+ ---
218
+
219
+ ## Advanced Features
220
+
221
+ ### Sorting
222
+
223
+ Enable sorting on specific columns:
224
+
225
+ ```typescript
226
+ const columns: ColumnDef<User>[] = [
227
+ {
228
+ accessorKey: 'name',
229
+ header: 'Name',
230
+ enableSorting: true, // Enable sorting for this column
231
+ },
232
+ {
233
+ accessorKey: 'email',
234
+ header: 'Email',
235
+ enableSorting: true,
236
+ },
237
+ ];
238
+
239
+ const table = createTable({
240
+ get data() {
241
+ return data;
242
+ },
243
+ columns,
244
+ getCoreRowModel: rowModels.coreRowModel(),
245
+ getSortedRowModel: rowModels.sortedRowModel(), // Add sorting model
246
+ });
247
+ ```
248
+
249
+ The sort button appears automatically when `enableSorting: true` and using the preset `<DataTable>` component.
250
+
251
+ ### Pagination
252
+
253
+ Implement client-side pagination:
254
+
255
+ ```svelte
256
+ <script lang="ts">
257
+ import { DataTable, createTable, rowModels, usePaginationState } from '@casinogate/ui';
258
+ import { Pagination } from '@casinogate/ui';
259
+
260
+ // Create pagination state manager
261
+ const paginationState = usePaginationState();
262
+
263
+ const table = createTable({
264
+ get data() {
265
+ return data;
266
+ },
267
+ columns,
268
+ getCoreRowModel: rowModels.coreRowModel(),
269
+ getPaginationRowModel: rowModels.paginationRowModel(), // Add pagination
270
+
271
+ state: {
272
+ get pagination() {
273
+ return paginationState.value;
274
+ },
275
+ },
276
+
277
+ onPaginationChange: paginationState.updater,
278
+ });
279
+ </script>
280
+
281
+ <DataTable {table} />
282
+
283
+ <Pagination.Basic
284
+ bind:page={() => table.getState().pagination.pageIndex + 1, (value) => table.setPageIndex(value - 1)}
285
+ count={data.length}
286
+ perPage={table.getState().pagination.pageSize}
287
+ siblingCount={1}
288
+ />
289
+ ```
290
+
291
+ ### Column Resizing
292
+
293
+ Allow users to resize columns:
294
+
295
+ ```typescript
296
+ import { useResizeState } from '@casinogate/ui';
297
+
298
+ const resizeState = useResizeState();
299
+
300
+ const table = createTable({
301
+ get data() {
302
+ return data;
303
+ },
304
+ columns,
305
+ getCoreRowModel: rowModels.coreRowModel(),
306
+
307
+ columnResizeMode: 'onChange', // Enable resize mode
308
+
309
+ state: {
310
+ get columnSizing() {
311
+ return resizeState.value;
312
+ },
313
+ },
314
+
315
+ onColumnSizingChange: resizeState.updater,
316
+ });
317
+ ```
318
+
319
+ ```svelte
320
+ <!-- Enable resizable prop on the component -->
321
+ <DataTable {table} resizable />
322
+ ```
323
+
324
+ ### Infinite Scrolling
325
+
326
+ Load more data as the user scrolls:
327
+
328
+ ```svelte
329
+ <script lang="ts">
330
+ let data = $state<Item[]>([]);
331
+ let page = $state(1);
332
+ let isLoading = $state(false);
333
+
334
+ async function loadMore() {
335
+ isLoading = true;
336
+ const response = await fetch(`/api/data?page=${page}`);
337
+ const newData = await response.json();
338
+ data = [...data, ...newData];
339
+ page++;
340
+ isLoading = false;
341
+ }
342
+
343
+ const table = createTable({
344
+ get data() {
345
+ return data;
346
+ },
347
+ columns,
348
+ getCoreRowModel: rowModels.coreRowModel(),
349
+
350
+ features: {
351
+ onLastRowReached: () => {
352
+ if (!isLoading) {
353
+ loadMore();
354
+ }
355
+ },
356
+ get isLoading() {
357
+ return isLoading;
358
+ },
359
+ },
360
+ });
361
+
362
+ $effect(() => {
363
+ loadMore(); // Initial load
364
+ });
365
+ </script>
366
+
367
+ <DataTable {table} class="h-[400px]" />
368
+ ```
369
+
370
+ ### Custom Cell Rendering
371
+
372
+ Customize how cells display data using three different approaches:
373
+
374
+ #### Simple String/HTML Rendering
375
+
376
+ Return a string or HTML directly from cell/header functions:
377
+
378
+ ```typescript
379
+ const columns: ColumnDef<User>[] = [
380
+ {
381
+ accessorKey: 'name',
382
+ header: 'Name',
383
+ cell: (info) => {
384
+ const value = info.getValue();
385
+ return `👤 ${value}`;
386
+ },
387
+ },
388
+ {
389
+ accessorKey: 'status',
390
+ header: 'Status',
391
+ cell: (info) => {
392
+ const status = info.getValue();
393
+ const color = status === 'active' ? 'green' : 'gray';
394
+ return `<span class="text-${color}-600">${status}</span>`;
395
+ },
396
+ },
397
+ ];
398
+ ```
399
+
400
+ #### Using `renderComponent` for Svelte Components
401
+
402
+ The `renderComponent` helper allows you to render full Svelte components in headers and cells:
403
+
404
+ ```typescript
405
+ import { renderComponent } from '@casinogate/ui';
406
+ import SortButton from './sort-button.svelte';
407
+ import StatusBadge from './status-badge.svelte';
408
+ import ActionMenu from './action-menu.svelte';
409
+
410
+ const columns: ColumnDef<User>[] = [
411
+ {
412
+ accessorKey: 'name',
413
+ header: ({ column }) =>
414
+ renderComponent(SortButton, {
415
+ label: 'Name',
416
+ onclick: () => column.toggleSorting(),
417
+ }),
418
+ cell: (info) => info.getValue(),
419
+ },
420
+ {
421
+ accessorKey: 'status',
422
+ header: 'Status',
423
+ cell: ({ row }) =>
424
+ renderComponent(StatusBadge, {
425
+ status: row.original.status,
426
+ variant: row.original.isPremium ? 'premium' : 'default',
427
+ }),
428
+ },
429
+ {
430
+ id: 'actions',
431
+ header: 'Actions',
432
+ cell: ({ row }) =>
433
+ renderComponent(ActionMenu, {
434
+ userId: row.original.id,
435
+ onEdit: () => handleEdit(row.original),
436
+ onDelete: () => handleDelete(row.original),
437
+ }),
438
+ enableSorting: false,
439
+ },
440
+ ];
441
+ ```
442
+
443
+ **Key Benefits:**
444
+
445
+ - Pass reactive props to components
446
+ - Full component lifecycle and state management
447
+ - Event handlers work naturally
448
+ - Type-safe props with TypeScript
449
+
450
+ #### Using `renderSnippet` for Inline Templates
451
+
452
+ The `renderSnippet` helper works with Svelte's `createRawSnippet` to render inline templates:
453
+
454
+ ```typescript
455
+ <script lang="ts">
456
+ import { createRawSnippet } from 'svelte';
457
+ import { renderSnippet, createTable, rowModels } from '@casinogate/ui';
458
+ import type { ColumnDef } from '@casinogate/ui';
459
+
460
+ type Payment = {
461
+ id: string;
462
+ amount: number;
463
+ status: 'Pending' | 'Processing' | 'Success' | 'Failed';
464
+ email: string;
465
+ };
466
+
467
+ // Create a snippet for status cell
468
+ const statusSnippet = createRawSnippet<[{ status: string }]>((getStatus) => {
469
+ const { status } = getStatus();
470
+ return {
471
+ render: () => `<div class="capitalize">${status}</div>`,
472
+ };
473
+ });
474
+
475
+ // Create a snippet for amount with formatting
476
+ const amountSnippet = createRawSnippet<[{ amount: number }]>((getAmount) => {
477
+ const { amount } = getAmount();
478
+ const formatter = new Intl.NumberFormat('en-US', {
479
+ style: 'currency',
480
+ currency: 'USD',
481
+ });
482
+ const formatted = formatter.format(amount);
483
+ return {
484
+ render: () => `<div class="text-right font-medium">${formatted}</div>`,
485
+ };
486
+ });
487
+
488
+ const columns: ColumnDef<Payment>[] = [
489
+ {
490
+ accessorKey: 'status',
491
+ header: 'Status',
492
+ cell: ({ row }) =>
493
+ renderSnippet(statusSnippet, {
494
+ status: row.original.status,
495
+ }),
496
+ },
497
+ {
498
+ accessorKey: 'amount',
499
+ header: () => {
500
+ const headerSnippet = createRawSnippet(() => {
501
+ return {
502
+ render: () => `<div class="text-right">Amount</div>`,
503
+ };
504
+ });
505
+ return renderSnippet(headerSnippet);
506
+ },
507
+ cell: ({ row }) =>
508
+ renderSnippet(amountSnippet, {
509
+ amount: row.original.amount,
510
+ }),
511
+ },
512
+ {
513
+ accessorKey: 'email',
514
+ header: 'Email',
515
+ cell: ({ row }) => {
516
+ // Inline snippet creation
517
+ const emailSnippet = createRawSnippet<[{ email: string }]>((getEmail) => {
518
+ const { email } = getEmail();
519
+ return {
520
+ render: () => `<div class="lowercase">${email}</div>`,
521
+ };
522
+ });
523
+
524
+ return renderSnippet(emailSnippet, {
525
+ email: row.original.email,
526
+ });
527
+ },
528
+ },
529
+ ];
530
+
531
+ const table = createTable({
532
+ get data() {
533
+ return payments;
534
+ },
535
+ columns,
536
+ getCoreRowModel: rowModels.coreRowModel(),
537
+ });
538
+ </script>
539
+ ```
540
+
541
+ **Key Benefits:**
542
+
543
+ - Lightweight alternative to full components
544
+ - Inline template rendering
545
+ - Access to formatting logic
546
+ - Can pass data parameters
547
+
548
+ #### Comparison: When to Use Each Approach
549
+
550
+ | Approach | Best For | Pros | Cons |
551
+ | ------------------- | ----------------------------------------------------- | ----------------------------------------- | ----------------------------- |
552
+ | **String/HTML** | Simple text, icons, basic styling | Fastest, simplest | No reactivity, no components |
553
+ | **renderComponent** | Interactive elements, complex UI, reusable components | Full Svelte features, type-safe, reusable | Slightly more overhead |
554
+ | **renderSnippet** | Formatted text, inline templates, simple markup | Lightweight, inline logic, type-safe | Limited to template rendering |
555
+
556
+ ### Practical Examples with renderComponent
557
+
558
+ #### Interactive Checkbox Column
559
+
560
+ ```typescript
561
+ import { renderComponent } from '@casinogate/ui';
562
+ import Checkbox from './checkbox.svelte';
563
+
564
+ const columns: ColumnDef<User>[] = [
565
+ {
566
+ id: 'select',
567
+ header: ({ table }) =>
568
+ renderComponent(Checkbox, {
569
+ checked: table.getIsAllPageRowsSelected(),
570
+ indeterminate: table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected(),
571
+ onCheckedChange: (value) => table.toggleAllPageRowsSelected(!!value),
572
+ 'aria-label': 'Select all',
573
+ }),
574
+ cell: ({ row }) =>
575
+ renderComponent(Checkbox, {
576
+ checked: row.getIsSelected(),
577
+ onCheckedChange: (value) => row.toggleSelected(!!value),
578
+ 'aria-label': 'Select row',
579
+ }),
580
+ enableSorting: false,
581
+ enableHiding: false,
582
+ },
583
+ // ... other columns
584
+ ];
585
+ ```
586
+
587
+ #### Avatar with Fallback
588
+
589
+ ```typescript
590
+ // avatar-cell.svelte
591
+ <script lang="ts">
592
+ let { src, name }: { src?: string; name: string } = $props();
593
+
594
+ const initials = $derived(
595
+ name
596
+ .split(' ')
597
+ .map((n) => n[0])
598
+ .join('')
599
+ .toUpperCase()
600
+ );
601
+ </script>
602
+
603
+ <div class="flex items-center gap-2">
604
+ {#if src}
605
+ <img src={src} alt={name} class="h-8 w-8 rounded-full" />
606
+ {:else}
607
+ <div class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-200">
608
+ {initials}
609
+ </div>
610
+ {/if}
611
+ <span>{name}</span>
612
+ </div>
613
+ ```
614
+
615
+ ```typescript
616
+ // In your columns definition
617
+ import { renderComponent } from '@casinogate/ui';
618
+ import AvatarCell from './avatar-cell.svelte';
619
+
620
+ const columns: ColumnDef<User>[] = [
621
+ {
622
+ accessorKey: 'name',
623
+ header: 'User',
624
+ cell: ({ row }) =>
625
+ renderComponent(AvatarCell, {
626
+ src: row.original.avatar,
627
+ name: row.original.name,
628
+ }),
629
+ },
630
+ ];
631
+ ```
632
+
633
+ #### Dropdown Actions Menu
634
+
635
+ ```typescript
636
+ // actions-menu.svelte
637
+ <script lang="ts">
638
+ import * as DropdownMenu from '$lib/components/dropdown-menu';
639
+ import { MoreHorizontal, Edit, Trash, Copy } from 'lucide-svelte';
640
+
641
+ let {
642
+ userId,
643
+ onEdit,
644
+ onDelete,
645
+ onDuplicate
646
+ }: {
647
+ userId: string;
648
+ onEdit: () => void;
649
+ onDelete: () => void;
650
+ onDuplicate: () => void;
651
+ } = $props();
652
+ </script>
653
+
654
+ <DropdownMenu.Root>
655
+ <DropdownMenu.Trigger>
656
+ <button class="rounded p-2 hover:bg-gray-100">
657
+ <MoreHorizontal class="h-4 w-4" />
658
+ </button>
659
+ </DropdownMenu.Trigger>
660
+ <DropdownMenu.Content>
661
+ <DropdownMenu.Item onclick={onEdit}>
662
+ <Edit class="mr-2 h-4 w-4" />
663
+ Edit
664
+ </DropdownMenu.Item>
665
+ <DropdownMenu.Item onclick={onDuplicate}>
666
+ <Copy class="mr-2 h-4 w-4" />
667
+ Duplicate
668
+ </DropdownMenu.Item>
669
+ <DropdownMenu.Separator />
670
+ <DropdownMenu.Item onclick={onDelete} class="text-red-600">
671
+ <Trash class="mr-2 h-4 w-4" />
672
+ Delete
673
+ </DropdownMenu.Item>
674
+ </DropdownMenu.Content>
675
+ </DropdownMenu.Root>
676
+ ```
677
+
678
+ ```typescript
679
+ import { renderComponent } from '@casinogate/ui';
680
+ import ActionsMenu from './actions-menu.svelte';
681
+
682
+ const columns: ColumnDef<User>[] = [
683
+ // ... other columns
684
+ {
685
+ id: 'actions',
686
+ header: 'Actions',
687
+ cell: ({ row }) =>
688
+ renderComponent(ActionsMenu, {
689
+ userId: row.original.id,
690
+ onEdit: () => openEditModal(row.original),
691
+ onDelete: () => confirmDelete(row.original.id),
692
+ onDuplicate: () => duplicateUser(row.original),
693
+ }),
694
+ enableHiding: false,
695
+ },
696
+ ];
697
+ ```
698
+
699
+ ### Loading States
700
+
701
+ The preset DataTable automatically handles loading states:
702
+
703
+ ```typescript
704
+ const table = createTable({
705
+ // ... other options
706
+ features: {
707
+ get isLoading() {
708
+ return isDataLoading;
709
+ },
710
+ },
711
+ });
712
+ ```
713
+
714
+ Loading indicators appear:
715
+
716
+ - **Initial load**: Full spinner overlay
717
+ - **Infinite scroll**: Spinner at bottom during data fetch
718
+
719
+ ### Empty States
720
+
721
+ Customize the message when no data is available:
722
+
723
+ ```svelte
724
+ <DataTable {table}>
725
+ {#snippet empty()}
726
+ <div class="py-8 text-center">
727
+ <p>No users found.</p>
728
+ <button onclick={loadData}>Retry</button>
729
+ </div>
730
+ {/snippet}
731
+ </DataTable>
732
+ ```
733
+
734
+ ---
735
+
736
+ ## API Reference
737
+
738
+ ### DataTable Props
739
+
740
+ | Prop | Type | Default | Description |
741
+ | ----------- | -------------------------------- | --------------- | ----------------------------------------- |
742
+ | `table` | `CreateTableReturn<TData>` | **Required** | Table instance created with `createTable` |
743
+ | `variant` | `'primary' \| 'secondary'` | `'primary'` | Visual style variant |
744
+ | `stripped` | `boolean` | `true` | Alternating row background colors |
745
+ | `align` | `'start' \| 'center' \| 'end'` | `'start'` | Text alignment in cells |
746
+ | `rounded` | `'none' \| 'sm' \| 'md' \| 'lg'` | `'sm'` | Border radius |
747
+ | `resizable` | `boolean` | `false` | Enable column resizing |
748
+ | `class` | `string` | `undefined` | Additional CSS classes |
749
+ | `empty` | `Snippet` | Default message | Custom empty state content |
750
+ | `loading` | `Snippet` | Default spinner | Custom loading indicator |
751
+
752
+ ### createTable Options
753
+
754
+ Core configuration for table behavior:
755
+
756
+ ```typescript
757
+ interface TableOptions<TData> {
758
+ // Required
759
+ data: TData[] | (() => TData[]);
760
+ columns: ColumnDef<TData>[];
761
+ getCoreRowModel: () => RowModel<TData>;
762
+
763
+ // Optional - Row Models
764
+ getSortedRowModel?: () => RowModel<TData>;
765
+ getPaginationRowModel?: () => RowModel<TData>;
766
+ getFilteredRowModel?: () => RowModel<TData>;
767
+
768
+ // Optional - State Management
769
+ state?: Partial<TableState>;
770
+ onStateChange?: (updater: Updater<TableState>) => void;
771
+ onPaginationChange?: (updater: Updater<PaginationState>) => void;
772
+ onSortingChange?: (updater: Updater<SortingState>) => void;
773
+ onColumnSizingChange?: (updater: Updater<ColumnSizingState>) => void;
774
+
775
+ // Optional - Column Sizing
776
+ columnResizeMode?: 'onChange' | 'onEnd';
777
+ defaultColumn?: Partial<ColumnDef<TData>>;
778
+
779
+ // Optional - Custom Features
780
+ features?: {
781
+ onLastRowReached?: () => void;
782
+ isLoading?: boolean;
783
+ };
784
+
785
+ // Optional - Meta Data
786
+ meta?: Record<string, any>;
787
+ }
788
+ ```
789
+
790
+ ### Column Definition
791
+
792
+ Define how each column behaves:
793
+
794
+ ```typescript
795
+ interface ColumnDef<TData> {
796
+ // Identifier
797
+ accessorKey: keyof TData; // For simple property access
798
+ id?: string; // Custom identifier
799
+
800
+ // Display
801
+ header: string | ((props: HeaderContext) => any);
802
+ cell?: (props: CellContext) => any;
803
+ footer?: string | ((props: HeaderContext) => any);
804
+
805
+ // Behavior
806
+ enableSorting?: boolean; // Default: true
807
+ enableResizing?: boolean; // Default: true
808
+ enableHiding?: boolean; // Default: true
809
+
810
+ // Sizing
811
+ size?: number; // Default width
812
+ minSize?: number; // Minimum width
813
+ maxSize?: number; // Maximum width
814
+
815
+ // Metadata
816
+ meta?: Record<string, any>;
817
+ }
818
+ ```
819
+
820
+ ### Table Utilities
821
+
822
+ State management helpers:
823
+
824
+ ```typescript
825
+ // Pagination State
826
+ const paginationState = usePaginationState();
827
+ // Returns: { value: PaginationState, updater: (updater) => void }
828
+
829
+ // Resize State
830
+ const resizeState = useResizeState();
831
+ // Returns: { value: ColumnSizingState, updater: (updater) => void }
832
+
833
+ // Row Selection State (if needed)
834
+ const selectionState = useRowSelectionState();
835
+ // Returns: { value: RowSelectionState, updater: (updater) => void }
836
+
837
+ // Row Models
838
+ rowModels.coreRowModel(); // Required base model
839
+ rowModels.sortedRowModel(); // For sorting
840
+ rowModels.paginationRowModel(); // For pagination
841
+ rowModels.filteredRowModel(); // For filtering
842
+ ```
843
+
844
+ ---
845
+
846
+ ## TypeScript Support
847
+
848
+ ### Typing Your Data
849
+
850
+ ```typescript
851
+ // Define your data structure
852
+ interface User {
853
+ id: number;
854
+ firstName: string;
855
+ lastName: string;
856
+ email: string;
857
+ role: 'admin' | 'user';
858
+ }
859
+
860
+ // Type your columns
861
+ const columns: ColumnDef<User>[] = [
862
+ {
863
+ accessorKey: 'firstName', // ✅ TypeScript validates this exists
864
+ header: 'First Name',
865
+ },
866
+ {
867
+ accessorKey: 'invalidKey', // ❌ TypeScript error!
868
+ header: 'Invalid',
869
+ },
870
+ ];
871
+
872
+ // Type your table
873
+ const table = createTable<User>({
874
+ get data() {
875
+ return users;
876
+ },
877
+ columns,
878
+ getCoreRowModel: rowModels.coreRowModel(),
879
+ });
880
+ ```
881
+
882
+ ### Extend Column Meta
883
+
884
+ Add custom metadata to columns:
885
+
886
+ ```typescript
887
+ declare module '@tanstack/table-core' {
888
+ interface ColumnMeta<TData extends RowData, TValue> {
889
+ className?: string;
890
+ headerClassName?: string;
891
+ tooltip?: string;
892
+ }
893
+ }
894
+
895
+ const columns: ColumnDef<User>[] = [
896
+ {
897
+ accessorKey: 'email',
898
+ header: 'Email',
899
+ meta: {
900
+ className: 'font-mono',
901
+ tooltip: 'User email address',
902
+ },
903
+ },
904
+ ];
905
+ ```
906
+
907
+ ---
908
+
909
+ ## Styling & Theming
910
+
911
+ ### Using Variants
912
+
913
+ Built-in style variants:
914
+
915
+ ```svelte
916
+ <!-- Primary variant (default) -->
917
+ <DataTable {table} variant="primary" />
918
+
919
+ <!-- Secondary variant -->
920
+ <DataTable {table} variant="secondary" />
921
+ ```
922
+
923
+ ### Custom Styling
924
+
925
+ Apply custom classes:
926
+
927
+ ```svelte
928
+ <DataTable {table} class="border-2 shadow-lg" rounded="lg" stripped={false} />
929
+ ```
930
+
931
+ ### Cell-Level Styling
932
+
933
+ Style individual cells using column meta:
934
+
935
+ ```typescript
936
+ const columns: ColumnDef<User>[] = [
937
+ {
938
+ accessorKey: 'status',
939
+ header: 'Status',
940
+ cell: (info) => {
941
+ const status = info.getValue();
942
+ const className = status === 'active' ? 'text-green-600 font-semibold' : 'text-gray-400';
943
+ return `<span class="${className}">${status}</span>`;
944
+ },
945
+ },
946
+ ];
947
+ ```
948
+
949
+ ### Tailwind Integration
950
+
951
+ The DataTable uses Tailwind CSS with the `cgui:` prefix:
952
+
953
+ ```css
954
+ /* Custom styles in your global CSS */
955
+ .cgui\\:custom-table-row:hover {
956
+ background-color: theme('colors.blue.50');
957
+ }
958
+ ```
959
+
960
+ ---
961
+
962
+ ## Common Patterns
963
+
964
+ ### Server-Side Pagination
965
+
966
+ ```svelte
967
+ <script lang="ts">
968
+ let data = $state<Item[]>([]);
969
+ let totalCount = $state(0);
970
+ let currentPage = $state(1);
971
+ const pageSize = 20;
972
+
973
+ async function fetchData(page: number) {
974
+ const response = await fetch(`/api/items?page=${page}&limit=${pageSize}`);
975
+ const result = await response.json();
976
+ data = result.items;
977
+ totalCount = result.total;
978
+ }
979
+
980
+ const paginationState = usePaginationState();
981
+
982
+ const table = createTable({
983
+ get data() {
984
+ return data;
985
+ },
986
+ columns,
987
+ getCoreRowModel: rowModels.coreRowModel(),
988
+ manualPagination: true, // Disable client-side pagination
989
+ pageCount: Math.ceil(totalCount / pageSize),
990
+
991
+ state: {
992
+ get pagination() {
993
+ return paginationState.value;
994
+ },
995
+ },
996
+
997
+ onPaginationChange: (updater) => {
998
+ paginationState.updater(updater);
999
+ const newState = typeof updater === 'function' ? updater(paginationState.value) : updater;
1000
+ fetchData(newState.pageIndex + 1);
1001
+ },
1002
+ });
1003
+
1004
+ $effect(() => {
1005
+ fetchData(currentPage);
1006
+ });
1007
+ </script>
1008
+
1009
+ <DataTable {table} />
1010
+ ```
1011
+
1012
+ ### Row Click Handling
1013
+
1014
+ ```svelte
1015
+ <script lang="ts">
1016
+ import { DataTablePrimitive } from '@casinogate/ui';
1017
+
1018
+ function handleRowClick(row: any) {
1019
+ console.log('Clicked row:', row.original);
1020
+ // Navigate, open modal, etc.
1021
+ }
1022
+ </script>
1023
+
1024
+ <DataTablePrimitive.Root {table}>
1025
+ <DataTablePrimitive.Table>
1026
+ <DataTablePrimitive.Header>
1027
+ <!-- Header rows -->
1028
+ </DataTablePrimitive.Header>
1029
+
1030
+ <DataTablePrimitive.Body>
1031
+ {#each table.getRowModel().rows as row}
1032
+ <DataTablePrimitive.Row onclick={() => handleRowClick(row)} class="cursor-pointer hover:bg-gray-50">
1033
+ {#each row.getVisibleCells() as cell}
1034
+ <DataTablePrimitive.Cell {cell}>
1035
+ <DataTablePrimitive.FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
1036
+ </DataTablePrimitive.Cell>
1037
+ {/each}
1038
+ </DataTablePrimitive.Row>
1039
+ {/each}
1040
+ </DataTablePrimitive.Body>
1041
+ </DataTablePrimitive.Table>
1042
+ </DataTablePrimitive.Root>
1043
+ ```
1044
+
1045
+ ### Conditional Row Styling
1046
+
1047
+ ```typescript
1048
+ const columns: ColumnDef<User>[] = [
1049
+ {
1050
+ accessorKey: 'name',
1051
+ header: 'Name',
1052
+ cell: (info) => {
1053
+ const row = info.row.original;
1054
+ if (row.isVIP) {
1055
+ return `<span class="text-gold-600 font-bold">⭐ ${info.getValue()}</span>`;
1056
+ }
1057
+ return info.getValue();
1058
+ },
1059
+ },
1060
+ ];
1061
+ ```
1062
+
1063
+ ### Searchable Table
1064
+
1065
+ ```svelte
1066
+ <script lang="ts">
1067
+ import { rowModels } from '@casinogate/ui';
1068
+
1069
+ let searchQuery = $state('');
1070
+
1071
+ const filteredData = $derived(
1072
+ data.filter((item) =>
1073
+ Object.values(item).some((value) => String(value).toLowerCase().includes(searchQuery.toLowerCase()))
1074
+ )
1075
+ );
1076
+
1077
+ const table = createTable({
1078
+ get data() {
1079
+ return filteredData;
1080
+ },
1081
+ columns,
1082
+ getCoreRowModel: rowModels.coreRowModel(),
1083
+ });
1084
+ </script>
1085
+
1086
+ <input type="search" bind:value={searchQuery} placeholder="Search..." />
1087
+
1088
+ <DataTable {table} />
1089
+ ```
1090
+
1091
+ ---
1092
+
1093
+ ## Troubleshooting
1094
+
1095
+ ### Common Issues
1096
+
1097
+ #### ❌ Data not updating when changed
1098
+
1099
+ **Problem:** Table doesn't reflect data changes.
1100
+
1101
+ **Solution:** Use a getter function for reactive data:
1102
+
1103
+ ```typescript
1104
+ // ❌ Wrong
1105
+ const table = createTable({
1106
+ data: myData,
1107
+ // ...
1108
+ });
1109
+
1110
+ // ✅ Correct
1111
+ const table = createTable({
1112
+ get data() {
1113
+ return myData;
1114
+ },
1115
+ // ...
1116
+ });
1117
+ ```
1118
+
1119
+ #### ❌ "Cannot read property 'getHeaderGroups' of undefined"
1120
+
1121
+ **Problem:** Table instance not properly initialized.
1122
+
1123
+ **Solution:** Ensure `getCoreRowModel` is provided:
1124
+
1125
+ ```typescript
1126
+ const table = createTable({
1127
+ get data() {
1128
+ return data;
1129
+ },
1130
+ columns,
1131
+ getCoreRowModel: rowModels.coreRowModel(), // Required!
1132
+ });
1133
+ ```
1134
+
1135
+ #### ❌ Sorting not working
1136
+
1137
+ **Problem:** Sort button appears but doesn't sort.
1138
+
1139
+ **Solution:** Add the sorted row model:
1140
+
1141
+ ```typescript
1142
+ const table = createTable({
1143
+ // ... other options
1144
+ getSortedRowModel: rowModels.sortedRowModel(), // Add this
1145
+ });
1146
+ ```
1147
+
1148
+ #### ❌ Column resize handles not visible
1149
+
1150
+ **Problem:** Resize functionality enabled but handles don't appear.
1151
+
1152
+ **Solution:** Ensure `resizable` prop is set:
1153
+
1154
+ ```svelte
1155
+ <DataTable {table} resizable />
1156
+ ```
1157
+
1158
+ #### ❌ Performance issues with large datasets
1159
+
1160
+ **Problem:** Table is slow with 1000+ rows.
1161
+
1162
+ **Solutions:**
1163
+
1164
+ 1. Implement pagination to limit visible rows
1165
+ 2. Use virtualization for extremely large datasets
1166
+ 3. Consider server-side pagination for very large datasets
1167
+ 4. Memoize expensive cell renderers
1168
+
1169
+ ```typescript
1170
+ // Use pagination to improve performance
1171
+ const table = createTable({
1172
+ get data() {
1173
+ return largeDataset;
1174
+ },
1175
+ columns,
1176
+ getCoreRowModel: rowModels.coreRowModel(),
1177
+ getPaginationRowModel: rowModels.paginationRowModel(),
1178
+ initialState: {
1179
+ pagination: {
1180
+ pageSize: 50, // Show 50 rows at a time
1181
+ },
1182
+ },
1183
+ });
1184
+ ```
1185
+
1186
+ ### Debugging Tips
1187
+
1188
+ Enable verbose logging:
1189
+
1190
+ ```typescript
1191
+ const table = createTable({
1192
+ get data() {
1193
+ return data;
1194
+ },
1195
+ columns,
1196
+ getCoreRowModel: rowModels.coreRowModel(),
1197
+ debugTable: true, // Log table state changes
1198
+ debugHeaders: true, // Log header information
1199
+ debugColumns: true, // Log column information
1200
+ });
1201
+ ```
1202
+
1203
+ ---
1204
+
1205
+ ## Accessibility
1206
+
1207
+ The DataTable component includes built-in accessibility features:
1208
+
1209
+ - **Semantic HTML**: Uses proper `<table>`, `<thead>`, `<tbody>`, `<tr>`, `<th>`, `<td>` elements
1210
+ - **Keyboard Navigation**: Sort buttons are keyboard-accessible
1211
+ - **ARIA Attributes**: Sort buttons include `aria-sort` attributes
1212
+ - **Screen Reader Support**: Table structure is properly announced
1213
+
1214
+ ### Best Practices
1215
+
1216
+ 1. **Always provide meaningful headers**:
1217
+
1218
+ ```typescript
1219
+ const columns: ColumnDef<User>[] = [
1220
+ { accessorKey: 'id', header: 'User ID' }, // ✅ Clear header
1221
+ { accessorKey: 'id', header: '' }, // ❌ Empty header
1222
+ ];
1223
+ ```
1224
+
1225
+ 2. **Use descriptive labels for actions**:
1226
+
1227
+ ```svelte
1228
+ <button aria-label="Edit user {user.name}">Edit</button>
1229
+ ```
1230
+
1231
+ 3. **Ensure sufficient color contrast** for text and backgrounds
1232
+
1233
+ ---
1234
+
1235
+ ## Further Resources
1236
+
1237
+ - **Live Examples**: [Storybook](https://static.casinogate.dev/components/master/?path=/story/ui-kit-datatable)
1238
+ - **TanStack Table Docs**: [tanstack.com/table](https://tanstack.com/table/latest)
1239
+ - **Component Source**: [GitHub](../../)
1240
+ - **Report Issues**: Contact your team lead or file an issue
1241
+
1242
+ ---
1243
+
1244
+ ## Quick Reference
1245
+
1246
+ ### Minimal Setup
1247
+
1248
+ ```typescript
1249
+ import { DataTable, createTable, rowModels } from '@casinogate/ui';
1250
+ const table = createTable({
1251
+ get data() {
1252
+ return data;
1253
+ },
1254
+ columns,
1255
+ getCoreRowModel: rowModels.coreRowModel(),
1256
+ });
1257
+ ```
1258
+
1259
+ ### With Pagination
1260
+
1261
+ ```typescript
1262
+ import { usePaginationState } from '@casinogate/ui';
1263
+ const paginationState = usePaginationState();
1264
+ // Add: getPaginationRowModel, state: { get pagination() { return paginationState.value; } }, onPaginationChange: paginationState.updater
1265
+ ```
1266
+
1267
+ ### With Sorting
1268
+
1269
+ ```typescript
1270
+ // Add: getSortedRowModel: rowModels.sortedRowModel()
1271
+ // In columns: enableSorting: true
1272
+ ```
1273
+
1274
+ ### With Resizing
1275
+
1276
+ ```typescript
1277
+ import { useResizeState } from '@casinogate/ui';
1278
+ const resizeState = useResizeState();
1279
+ // Add: columnResizeMode: 'onChange', state: { get columnSizing() { return resizeState.value; } }, onColumnSizingChange: resizeState.updater
1280
+ // Component: <DataTable {table} resizable />
1281
+ ```
1282
+
1283
+ ---
1284
+
1285
+ **Need help?** Check the [Storybook examples](https://static.casinogate.dev/components/master/?path=/story/ui-kit-datatable) or reach out to the UI team.