@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.
|