tablero 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,12 +9,18 @@ A type-safe, framework-agnostic data table library with React bindings. Built wi
9
9
  - ⚛️ **React hooks** - `useDataTable` hook for easy React integration
10
10
  - 🎨 **Customizable UI** - Flexible, CSS-variable based styling
11
11
  - 📊 **Sorting** - Single-column sorting (extensible to multi-column)
12
- - 📄 **Pagination** - Client-side pagination with configurable page sizes
12
+ - 📄 **Pagination** - Client-side and server-side pagination support
13
13
  - 🔍 **Filtering** - Global and column-specific text filtering
14
+ - ✅ **Row Selection** - Single and multi-select with select all support
15
+ - 🌐 **URL Sync** - Synchronize table state with URL search params
16
+ - 🖥️ **Server-side Mode** - Delegate sorting, filtering, and pagination to server
14
17
  - 📌 **Sticky headers & columns** - Keep headers and first column visible while scrolling
15
18
  - 🔧 **Column resizing** - Resize columns with pointer-based interaction
19
+ - 👁️ **Column visibility** - Show/hide columns dynamically
20
+ - 🔀 **Column reordering** - Reorder columns programmatically
16
21
  - 🎭 **Custom renderers** - Customize cell, header, and row rendering
17
22
  - ♿ **Accessible** - ARIA attributes and keyboard support
23
+ - 🎛️ **Controlled/Uncontrolled** - Flexible state management patterns
18
24
 
19
25
  ## Installation
20
26
 
@@ -28,13 +34,12 @@ yarn add tablero
28
34
 
29
35
  ## Quick Start
30
36
 
31
- ### React Example
37
+ ### Basic React Example
32
38
 
33
39
  ```tsx
34
40
  import { useDataTable } from "tablero/react";
35
41
  import { DataTable } from "tablero/ui";
36
42
  import { defineColumns, col } from "tablero/core";
37
- import "tablero/dist/ui.css"; // Optional: import default styles
38
43
 
39
44
  interface User {
40
45
  id: number;
@@ -56,99 +61,723 @@ function MyTable() {
56
61
  pageSize: 10,
57
62
  });
58
63
 
59
- return <DataTable table={table} stickyHeader bordered maxHeight={400} />;
64
+ return (
65
+ <DataTable
66
+ table={table}
67
+ stickyHeader
68
+ bordered
69
+ maxHeight={400}
70
+ getRowKey={(row) => row.id}
71
+ />
72
+ );
60
73
  }
61
74
  ```
62
75
 
63
- ### Core Usage (Framework-agnostic)
76
+ ## Packages
64
77
 
65
- ```ts
66
- import {
67
- defineColumns,
68
- col,
69
- createColumns,
70
- createInitialTableState,
71
- applyFilters,
72
- applySort,
73
- getPaginatedData,
74
- } from "tablero/core";
78
+ This library is organized into three packages:
79
+
80
+ - **`tablero/core`** - Framework-agnostic core logic (state management, sorting, filtering, pagination)
81
+ - **`tablero/react`** - React hooks (`useDataTable`) and URL sync utilities
82
+ - **`tablero/ui`** - React UI components (`DataTable`, `TableHeader`, `TableBody`, `TableCell`)
83
+
84
+ ## Column Definitions
85
+
86
+ ### Basic Column Definition
87
+
88
+ ```tsx
89
+ import { defineColumns, col } from "tablero/core";
75
90
 
76
91
  const columns = defineColumns<User>()([
77
- col("name", { header: "Name", sortable: true }),
78
- col("email", { header: "Email" }),
92
+ col("name", {
93
+ header: "Name",
94
+ sortable: true,
95
+ filter: "text",
96
+ width: 200,
97
+ align: "left", // "left" | "center" | "right"
98
+ }),
79
99
  ]);
100
+ ```
80
101
 
81
- const runtimeColumns = createColumns(columns);
82
- const state = createInitialTableState(columns.map((c) => c.id));
102
+ ### Column Options
83
103
 
84
- // Apply filters
85
- const filtered = applyFilters(data, state.filtering, getValue);
104
+ - `header` - Column header text
105
+ - `sortable` - Enable sorting for this column
106
+ - `filter` - Filter type: `"text"` | `"none"` (default: `"none"`)
107
+ - `width` - Column width in pixels
108
+ - `minWidth` - Minimum column width
109
+ - `maxWidth` - Maximum column width
110
+ - `align` - Text alignment: `"left"` | `"center"` | `"right"`
86
111
 
87
- // Apply sorting
88
- const sorted = applySort(filtered, state.sorting, getValue);
112
+ ### Custom Accessor
89
113
 
90
- // Paginate
91
- const paginated = getPaginatedData(sorted, state.pagination);
114
+ ```tsx
115
+ import { colWithAccessor } from "tablero/core";
116
+
117
+ colWithAccessor("fullName", (user) => `${user.firstName} ${user.lastName}`, {
118
+ header: "Full Name",
119
+ sortable: true,
120
+ });
92
121
  ```
93
122
 
94
- ## Packages
123
+ ## Sorting
95
124
 
96
- This library is organized into three packages:
125
+ ### Basic Sorting
97
126
 
98
- - **`tablero/core`** - Framework-agnostic core logic (state management, sorting, filtering, pagination)
99
- - **`tablero/react`** - React hooks (`useDataTable`)
100
- - **`tablero/ui`** - React UI components (`DataTable`, `TableHeader`, `TableBody`, `TableCell`)
127
+ ```tsx
128
+ const table = useDataTable({
129
+ data: users,
130
+ columns,
131
+ });
132
+
133
+ // Access sort state
134
+ table.sorting.state; // { columnId: string | null, direction: "asc" | "desc" | null }
135
+
136
+ // Sort handlers
137
+ table.sorting.toggle("name"); // Toggle sort for column
138
+ table.sorting.set("name", "asc"); // Set explicit sort
139
+ table.sorting.clear(); // Clear sorting
140
+ ```
141
+
142
+ ### Initial Sort State
143
+
144
+ ```tsx
145
+ const table = useDataTable({
146
+ data: users,
147
+ columns,
148
+ state: {
149
+ sorting: { columnId: "name", direction: "asc" },
150
+ },
151
+ });
152
+ ```
153
+
154
+ ## Filtering
155
+
156
+ ### Global Filter
157
+
158
+ ```tsx
159
+ const table = useDataTable({
160
+ data: users,
161
+ columns,
162
+ });
163
+
164
+ // Set global filter
165
+ table.filtering.setGlobalFilter("search term");
166
+
167
+ // Access filter state
168
+ table.filtering.state.globalFilter; // string
169
+ ```
170
+
171
+ ### Column Filters
172
+
173
+ ```tsx
174
+ // Set column-specific filter
175
+ table.filtering.setColumnFilter("email", "example.com");
176
+
177
+ // Clear column filter
178
+ table.filtering.clearColumnFilter("email");
179
+
180
+ // Clear all filters
181
+ table.filtering.clearAllFilters();
182
+
183
+ // Access filter state
184
+ table.filtering.state.columnFilters; // Record<string, string>
185
+ ```
186
+
187
+ ### Filtering Example
188
+
189
+ ```tsx
190
+ function FilteredTable() {
191
+ const table = useDataTable({
192
+ data: users,
193
+ columns,
194
+ });
195
+
196
+ return (
197
+ <div>
198
+ <input
199
+ type="text"
200
+ value={table.filtering.state.globalFilter}
201
+ onChange={(e) => table.filtering.setGlobalFilter(e.target.value)}
202
+ placeholder="Search all columns..."
203
+ />
204
+ <DataTable table={table} />
205
+ </div>
206
+ );
207
+ }
208
+ ```
209
+
210
+ ## Pagination
211
+
212
+ ### Basic Pagination
213
+
214
+ ```tsx
215
+ const table = useDataTable({
216
+ data: users,
217
+ columns,
218
+ pageSize: 10, // Default: 10
219
+ });
220
+
221
+ // Access pagination state
222
+ table.pageIndex; // Current page (0-based)
223
+ table.pageSize; // Items per page
224
+ table.pageCount; // Total pages
225
+ table.hasNextPage; // boolean
226
+ table.hasPreviousPage; // boolean
227
+
228
+ // Pagination handlers
229
+ table.pagination.nextPage();
230
+ table.pagination.previousPage();
231
+ table.pagination.goToPage(2);
232
+ table.pagination.setPageSize(20);
233
+ ```
234
+
235
+ ### Pagination Controls Example
236
+
237
+ ```tsx
238
+ <div>
239
+ <button
240
+ onClick={() => table.pagination.previousPage()}
241
+ disabled={!table.hasPreviousPage}
242
+ >
243
+ Previous
244
+ </button>
245
+ <span>
246
+ Page {table.pageIndex + 1} of {table.pageCount}
247
+ </span>
248
+ <button
249
+ onClick={() => table.pagination.nextPage()}
250
+ disabled={!table.hasNextPage}
251
+ >
252
+ Next
253
+ </button>
254
+ <select
255
+ value={table.pageSize}
256
+ onChange={(e) => table.pagination.setPageSize(Number(e.target.value))}
257
+ >
258
+ <option value={10}>10 per page</option>
259
+ <option value={20}>20 per page</option>
260
+ <option value={50}>50 per page</option>
261
+ </select>
262
+ </div>
263
+ ```
264
+
265
+ ## Row Selection
266
+
267
+ ### Basic Selection
101
268
 
102
- ## API Documentation
269
+ ```tsx
270
+ const table = useDataTable({
271
+ data: users,
272
+ columns,
273
+ getRowKey: (row) => row.id, // Required for selection
274
+ selection: {
275
+ enabled: true,
276
+ mode: "multi", // or "single"
277
+ },
278
+ });
279
+
280
+ // Access selection state
281
+ table.selection.selectedRowIds; // Set<string | number>
282
+ table.selection.selectedCount; // number
283
+ table.selection.isAllSelected; // boolean (all rows on current page)
284
+ table.selection.isIndeterminate; // boolean (some rows selected)
285
+
286
+ // Selection handlers
287
+ table.selection.select(rowId);
288
+ table.selection.deselect(rowId);
289
+ table.selection.toggle(rowId);
290
+ table.selection.selectAll(); // Select all rows on current page
291
+ table.selection.deselectAll(); // Deselect all rows on current page
292
+ table.selection.clear(); // Clear all selections
293
+ ```
294
+
295
+ ### Selection Example
296
+
297
+ ```tsx
298
+ function SelectableTable() {
299
+ const table = useDataTable({
300
+ data: users,
301
+ columns,
302
+ getRowKey: (row) => row.id,
303
+ selection: {
304
+ enabled: true,
305
+ mode: "multi",
306
+ },
307
+ });
103
308
 
104
- ### Core API
309
+ return (
310
+ <div>
311
+ <p>Selected: {table.selection.selectedCount} rows</p>
312
+ <DataTable table={table} />
313
+ {table.selection.selectedCount > 0 && (
314
+ <button onClick={() => table.selection.clear()}>Clear Selection</button>
315
+ )}
316
+ </div>
317
+ );
318
+ }
319
+ ```
105
320
 
106
- See the [core package documentation](./packages/core/README.md) for detailed API documentation.
321
+ ## Server-Side Mode
107
322
 
108
- ### React Hook
323
+ When using server-side data fetching, disable client-side transformations:
109
324
 
110
325
  ```tsx
111
326
  const table = useDataTable({
112
- data: TData[],
113
- columns: ColumnDef<TData>[],
114
- pageSize?: number,
115
- state?: TableStateHandler | UncontrolledTableState,
327
+ data: apiResponse, // Data already filtered/sorted/paginated by server
328
+ columns,
329
+ serverMode: {
330
+ pagination: true, // Server handles pagination
331
+ sorting: true, // Server handles sorting
332
+ filtering: true, // Server handles filtering
333
+ },
116
334
  });
117
335
  ```
118
336
 
119
- ### DataTable Component
337
+ ### Server-Side with Controlled State
338
+
339
+ ```tsx
340
+ function ServerSideTable() {
341
+ const [tableState, setTableState] = useState({
342
+ pagination: { pageIndex: 0, pageSize: 10 },
343
+ sorting: { columnId: null, direction: null },
344
+ filtering: { globalFilter: "", columnFilters: {} },
345
+ });
346
+
347
+ const table = useDataTable({
348
+ data: apiData,
349
+ columns,
350
+ serverMode: {
351
+ pagination: true,
352
+ sorting: true,
353
+ filtering: true,
354
+ },
355
+ state: {
356
+ pagination: tableState.pagination,
357
+ sorting: tableState.sorting,
358
+ filtering: tableState.filtering,
359
+ onStateChange: (updates) => {
360
+ setTableState((prev) => ({ ...prev, ...updates }));
361
+ // Fetch new data from API with updated state
362
+ fetchData(updates);
363
+ },
364
+ },
365
+ });
366
+
367
+ return <DataTable table={table} />;
368
+ }
369
+ ```
370
+
371
+ ## URL Synchronization
372
+
373
+ ### Basic URL Sync (Browser History API)
374
+
375
+ ```tsx
376
+ const table = useDataTable({
377
+ data: users,
378
+ columns,
379
+ urlSync: {
380
+ enabled: true,
381
+ features: {
382
+ pagination: true,
383
+ sorting: true,
384
+ filtering: true,
385
+ },
386
+ debounceMs: 300, // Optional: debounce URL updates
387
+ },
388
+ });
389
+ ```
390
+
391
+ ### Next.js App Router
392
+
393
+ ```tsx
394
+ import { useSearchParams, useRouter, usePathname } from "next/navigation";
395
+ import { useDataTable, createNextAppRouterAdapter } from "tablero/react";
396
+
397
+ function NextJsTable() {
398
+ const searchParams = useSearchParams();
399
+ const router = useRouter();
400
+ const pathname = usePathname();
401
+
402
+ const table = useDataTable({
403
+ data: users,
404
+ columns,
405
+ urlSync: {
406
+ enabled: true,
407
+ routerAdapter: createNextAppRouterAdapter(searchParams, router, pathname),
408
+ paramNames: {
409
+ page: "p",
410
+ sortColumn: "sort",
411
+ sortDir: "dir",
412
+ globalFilter: "q",
413
+ },
414
+ },
415
+ });
416
+
417
+ return <DataTable table={table} />;
418
+ }
419
+ ```
420
+
421
+ ### Custom Parameter Names
422
+
423
+ ```tsx
424
+ urlSync: {
425
+ enabled: true,
426
+ paramNames: {
427
+ page: "page",
428
+ pageSize: "size",
429
+ sortColumn: "sort",
430
+ sortDir: "direction",
431
+ globalFilter: "search",
432
+ columnFilterPrefix: "filter_",
433
+ },
434
+ }
435
+ ```
436
+
437
+ ### URL Format
438
+
439
+ The URL will look like:
440
+
441
+ ```
442
+ ?page=1&pageSize=10&sort=name&sortDir=asc&q=search&filter_email=example.com
443
+ ```
444
+
445
+ ## Column Management
446
+
447
+ ### Column Visibility
448
+
449
+ ```tsx
450
+ // Toggle column visibility
451
+ table.columnManagement.toggleVisibility("email");
452
+
453
+ // Set column visibility
454
+ table.columnManagement.setVisibility("email", false);
455
+
456
+ // Access visibility state
457
+ table.state.columnVisibility; // Record<string, boolean>
458
+ ```
459
+
460
+ ### Column Reordering
461
+
462
+ ```tsx
463
+ // Reorder columns
464
+ table.columnManagement.reorder(["name", "email", "age"]);
465
+
466
+ // Access column order
467
+ table.state.columnOrder; // string[]
468
+ ```
469
+
470
+ ## Custom Renderers
471
+
472
+ ### Custom Cell Renderer
473
+
474
+ ```tsx
475
+ <DataTable
476
+ table={table}
477
+ renderCell={(value, row, column) => {
478
+ if (column.id === "active") {
479
+ return <span>{value ? "✓" : "✗"}</span>;
480
+ }
481
+ return <span>{value}</span>;
482
+ }}
483
+ />
484
+ ```
485
+
486
+ ### Custom Header Renderer
487
+
488
+ ```tsx
489
+ <DataTable
490
+ table={table}
491
+ renderHeader={(column, sortState) => {
492
+ return (
493
+ <div>
494
+ {column.header}
495
+ {sortState.columnId === column.id && (
496
+ <span>{sortState.direction === "asc" ? "↑" : "↓"}</span>
497
+ )}
498
+ </div>
499
+ );
500
+ }}
501
+ />
502
+ ```
503
+
504
+ ### Custom Row Renderer
120
505
 
121
506
  ```tsx
122
507
  <DataTable
123
508
  table={table}
124
- bordered={true}
125
- stickyHeader={false}
126
- stickyFirstColumn={false}
127
- enableResizing={false}
128
- maxHeight?: number | string
509
+ renderRow={(row, index, cells) => {
510
+ return <tr className={row.active ? "active-row" : ""}>{cells}</tr>;
511
+ }}
512
+ />
513
+ ```
514
+
515
+ ## UI Features
516
+
517
+ ### Sticky Header
518
+
519
+ ```tsx
520
+ <DataTable table={table} stickyHeader maxHeight={400} />
521
+ ```
522
+
523
+ ### Sticky First Column
524
+
525
+ ```tsx
526
+ <DataTable table={table} stickyFirstColumn />
527
+ ```
528
+
529
+ ### Column Resizing
530
+
531
+ ```tsx
532
+ <DataTable table={table} enableResizing />
533
+ ```
534
+
535
+ ### Borders
536
+
537
+ ```tsx
538
+ <DataTable
539
+ table={table}
540
+ bordered // Default: true
541
+ />
542
+ ```
543
+
544
+ ### Loading and Error States
545
+
546
+ ```tsx
547
+ <DataTable
548
+ table={table}
549
+ isLoading={loading}
550
+ error={error}
129
551
  slots={{
130
- loader?: React.ComponentType,
131
- empty?: React.ComponentType<{ columns: Column[] }>,
132
- error?: React.ComponentType<{ error: Error | string }>,
552
+ loader: () => <div>Loading...</div>,
553
+ empty: ({ columns }) => <div>No data available</div>,
554
+ error: ({ error }) => <div>Error: {error}</div>,
133
555
  }}
134
- renderCell?: (value, row, column) => React.ReactNode
135
- renderHeader?: (column, sortState) => React.ReactNode
136
- renderRow?: (row, index, cells) => React.ReactNode
137
556
  />
138
557
  ```
139
558
 
559
+ ## State Management
560
+
561
+ ### Uncontrolled (Default)
562
+
563
+ ```tsx
564
+ const table = useDataTable({
565
+ data: users,
566
+ columns,
567
+ });
568
+ // All state managed internally
569
+ ```
570
+
571
+ ### Fully Controlled
572
+
573
+ ```tsx
574
+ const [tableState, setTableState] = useState(
575
+ createInitialTableState(columnIds)
576
+ );
577
+
578
+ const table = useDataTable({
579
+ data: users,
580
+ columns,
581
+ state: {
582
+ state: tableState,
583
+ setState: setTableState,
584
+ },
585
+ });
586
+ ```
587
+
588
+ ### Per-State Control
589
+
590
+ ```tsx
591
+ const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
592
+ const [sorting, setSorting] = useState({ columnId: null, direction: null });
593
+
594
+ const table = useDataTable({
595
+ data: users,
596
+ columns,
597
+ state: {
598
+ pagination,
599
+ sorting,
600
+ onPaginationChange: setPagination,
601
+ onSortingChange: setSorting,
602
+ },
603
+ });
604
+ ```
605
+
606
+ ## API Reference
607
+
608
+ ### `useDataTable` Hook
609
+
610
+ ```tsx
611
+ interface UseDataTableOptions<TData> {
612
+ data: TData[];
613
+ columns: readonly ColumnDef<TData>[];
614
+ pageSize?: number;
615
+ state?: TableStateHandler | UncontrolledTableState | PerStateControl;
616
+ serverMode?: {
617
+ pagination?: boolean;
618
+ sorting?: boolean;
619
+ filtering?: boolean;
620
+ };
621
+ selection?: {
622
+ enabled?: boolean;
623
+ mode?: "single" | "multi";
624
+ initialSelectedRowIds?: (string | number)[];
625
+ };
626
+ getRowKey?: (row: TData, index: number) => string | number;
627
+ urlSync?: {
628
+ enabled?: boolean;
629
+ paramNames?: UrlParamNames;
630
+ debounceMs?: number;
631
+ features?: {
632
+ pagination?: boolean;
633
+ sorting?: boolean;
634
+ filtering?: boolean;
635
+ };
636
+ routerAdapter?: RouterAdapter;
637
+ };
638
+ }
639
+ ```
640
+
641
+ ### `DataTable` Component
642
+
643
+ ```tsx
644
+ interface DataTableProps<TData> {
645
+ table: TableInstance<TData>;
646
+ slots?: {
647
+ loader?: React.ComponentType;
648
+ empty?: React.ComponentType<{ columns: Column<TData>[] }>;
649
+ error?: React.ComponentType<{ error: Error | string }>;
650
+ };
651
+ renderCell?: (
652
+ value: unknown,
653
+ row: TData,
654
+ column: Column<TData>
655
+ ) => React.ReactNode;
656
+ renderHeader?: (
657
+ column: Column<TData>,
658
+ sortState: SortState
659
+ ) => React.ReactNode;
660
+ renderRow?: (
661
+ row: TData,
662
+ index: number,
663
+ cells: React.ReactNode[]
664
+ ) => React.ReactNode;
665
+ getRowKey?: (row: TData, index: number) => string | number;
666
+ stickyHeader?: boolean;
667
+ stickyFirstColumn?: boolean;
668
+ enableResizing?: boolean;
669
+ maxHeight?: number | string;
670
+ bordered?: boolean;
671
+ className?: string;
672
+ isLoading?: boolean;
673
+ error?: Error | string | null;
674
+ }
675
+ ```
676
+
677
+ ### `TableInstance` API
678
+
679
+ ```tsx
680
+ interface TableInstance<TData> {
681
+ // State
682
+ state: TableState;
683
+ columns: Column<TData>[];
684
+ visibleColumns: Column<TData>[];
685
+
686
+ // Data
687
+ data: TData[];
688
+ filteredData: TData[];
689
+ sortedData: TData[];
690
+ paginatedData: TData[];
691
+
692
+ // Pagination
693
+ pageIndex: number;
694
+ pageSize: number;
695
+ pageCount: number;
696
+ hasNextPage: boolean;
697
+ hasPreviousPage: boolean;
698
+
699
+ // Handlers
700
+ sorting: {
701
+ state: SortState;
702
+ toggle: (columnId: string) => void;
703
+ set: (columnId: string | null, direction: "asc" | "desc" | null) => void;
704
+ clear: () => void;
705
+ };
706
+
707
+ pagination: {
708
+ state: PaginationState;
709
+ nextPage: () => void;
710
+ previousPage: () => void;
711
+ goToPage: (pageIndex: number) => void;
712
+ setPageSize: (pageSize: number) => void;
713
+ };
714
+
715
+ filtering: {
716
+ state: FilterState;
717
+ setGlobalFilter: (filter: string) => void;
718
+ setColumnFilter: (columnId: string, filter: string) => void;
719
+ clearColumnFilter: (columnId: string) => void;
720
+ clearAllFilters: () => void;
721
+ };
722
+
723
+ columnManagement: {
724
+ toggleVisibility: (columnId: string) => void;
725
+ setVisibility: (columnId: string, visible: boolean) => void;
726
+ reorder: (columnOrder: string[]) => void;
727
+ };
728
+
729
+ selection: {
730
+ state: SelectionState;
731
+ enabled: boolean;
732
+ mode: "single" | "multi";
733
+ selectedRowIds: Set<string | number>;
734
+ selectedCount: number;
735
+ isSelected: (rowId: string | number) => boolean;
736
+ select: (rowId: string | number) => void;
737
+ deselect: (rowId: string | number) => void;
738
+ toggle: (rowId: string | number) => void;
739
+ selectAll: () => void;
740
+ deselectAll: () => void;
741
+ clear: () => void;
742
+ isAllSelected: boolean;
743
+ isIndeterminate: boolean;
744
+ };
745
+ }
746
+ ```
747
+
140
748
  ## Styling
141
749
 
142
- The library uses CSS variables for easy theming. Import the default styles or create your own:
750
+ The library uses CSS variables for easy theming. Import default styles or create your own:
143
751
 
144
752
  ```css
145
753
  :root {
146
- --tablero-bg: #ffffff;
147
- --tablero-header-bg: #f9fafb;
148
- --tablero-border-color: #e5e7eb;
149
- --tablero-border-width: 1px;
150
- --tablero-hover-bg: #f3f4f6;
151
- --tablero-text-color: #111827;
754
+ --table-x-bg: #ffffff;
755
+ --table-x-header-bg: #f9fafb;
756
+ --table-x-sticky-bg: #ffffff;
757
+ --table-x-border-color: #e5e7eb;
758
+ --table-x-border-width: 1px;
759
+ --table-x-hover-bg: #f3f4f6;
760
+ --table-x-text-color: #111827;
761
+ }
762
+ ```
763
+
764
+ ### Custom Styles
765
+
766
+ ```css
767
+ .table-x-header-cell {
768
+ background-color: var(--table-x-header-bg, #f9fafb);
769
+ border: var(--table-x-border-width, 1px) solid var(
770
+ --table-x-border-color,
771
+ #e5e7eb
772
+ );
773
+ }
774
+
775
+ .table-x-cell {
776
+ padding: 12px;
777
+ }
778
+
779
+ .table-x-checkbox {
780
+ cursor: pointer;
152
781
  }
153
782
  ```
154
783
 
@@ -162,8 +791,58 @@ const columns = defineColumns<User>()([
162
791
  col("name", { ... }), // ✅ Type-safe
163
792
  col("invalid", { ... }), // ❌ Type error
164
793
  ]);
794
+
795
+ // Row data is typed
796
+ const table = useDataTable({
797
+ data: users, // TData inferred from columns
798
+ columns,
799
+ });
800
+
801
+ // Access typed data
802
+ table.paginatedData.forEach((user) => {
803
+ user.name; // ✅ Type-safe
804
+ user.invalid; // ❌ Type error
805
+ });
165
806
  ```
166
807
 
808
+ ## Core Usage (Framework-agnostic)
809
+
810
+ ```tsx
811
+ import {
812
+ defineColumns,
813
+ col,
814
+ createColumns,
815
+ createInitialTableState,
816
+ applyFilters,
817
+ applySort,
818
+ getPaginatedData,
819
+ } from "tablero/core";
820
+
821
+ const columns = defineColumns<User>()([
822
+ col("name", { header: "Name", sortable: true }),
823
+ col("email", { header: "Email" }),
824
+ ]);
825
+
826
+ const runtimeColumns = createColumns(columns);
827
+ const state = createInitialTableState(columns.map((c) => c.id));
828
+
829
+ // Apply filters
830
+ const filtered = applyFilters(data, state.filtering, getValue);
831
+
832
+ // Apply sorting
833
+ const sorted = applySort(filtered, state.sorting, getValue);
834
+
835
+ // Paginate
836
+ const paginated = getPaginatedData(sorted, state.pagination);
837
+ ```
838
+
839
+ ## Examples
840
+
841
+ See the [examples](./examples/) directory for complete working examples:
842
+
843
+ - `react-example.tsx` - Full-featured React example with all features
844
+ - `basic-example.ts` - Core usage example
845
+
167
846
  ## License
168
847
 
169
848
  MIT