bolt-table 0.1.40 → 0.1.42

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
@@ -5,40 +5,34 @@ A high-performance, zero-dependency\* React table component. Only the rows visib
5
5
  [![npm version](https://img.shields.io/npm/v/bolt-table)](https://www.npmjs.com/package/bolt-table)
6
6
  [![license](https://img.shields.io/npm/l/bolt-table)](./LICENSE)
7
7
  [![github](https://img.shields.io/badge/GitHub-Source-181717?logo=github)](https://github.com/venkateshwebdev/Bolt-Table)
8
- [![website](https://img.shields.io/badge/Website-Live_Demo-blue?logo=vercel)](https://bolt-table.vercel.app/)
8
+ [![website](https://img.shields.io/badge/Docs_&_Examples-bolt--table.vercel.app-blue?logo=vercel)](https://bolt-table.vercel.app/)
9
9
 
10
10
  ---
11
11
 
12
12
  ## Features
13
13
 
14
- - **Row virtualization** only visible rows are rendered, powered by TanStack Virtual
15
- - **Horizontal virtualization** — optionally render only visible columns (great for 100+ column tables)
16
- - **Dynamic row heights** — auto-measure row heights from content instead of using a fixed `rowHeight`
17
- - **Drag to reorder columns** custom zero-dependency drag-and-drop (no @dnd-kit needed)
18
- - **Column pinning** pin columns to the left or right edge via right-click
19
- - **Column resizing** drag the right edge of any header to resize
20
- - **Column hiding** hide/show columns via the right-click context menu
21
- - **Column picker** built-in checklist panel to toggle multiple columns on/off at once
22
- - **Column persistence** optional localStorage save/restore of column order, widths, visibility, and pinned state
23
- - **Global search** a search bar above the table that filters all rows across every column
24
- - **Sorting** client-side or server-side, with custom comparators per column
25
- - **Filtering** — client-side or server-side, with custom filter functions per column
26
- - **Pagination** client-side slice or server-side with full control
27
- - **Row selection** checkbox or radio, with select-all, indeterminate state, and disabled rows
28
- - **Expandable rows** auto-measured content panels below each row, controlled or uncontrolled
29
- - **Shimmer loading** animated skeleton rows on initial load and infinite scroll append
30
- - **Infinite scroll** `onEndReached` callback with configurable threshold
31
- - **Empty state** custom renderer or default "No data" message
32
- - **Auto height** table shrinks/grows to fit rows, capped at 10 rows by default
33
- - **Row pinning** pin rows to the top or bottom of the table, sticky during vertical scroll
34
- - **Cell context menu** — right-click (or long-press on mobile) any cell to pin rows or copy values
35
- - **Right-click context menu** — sort, filter, pin, hide, plus custom items
36
- - **Mobile-friendly context menus** — long-press (touch-and-hold) triggers context menus on touch devices
37
- - **Nested / grouped columns** — group related columns under a shared header spanning multiple columns
38
- - **Duplicate key safety** — automatically deduplicates row keys when data contains rows with the same ID
39
- - **Theme-agnostic** — works in light and dark mode out of the box, no CSS variables needed
40
- - **Editable cells** — right-click any cell on an `editable` column to inline-edit via the context menu
41
- - **Custom icons** — override any built-in icon via the `icons` prop
14
+ - Row & column virtualization
15
+ - Dynamic row heights
16
+ - Drag-to-reorder columns & rows
17
+ - Column pinning, resizing, hiding, picker & persistence
18
+ - Row pinning (sticky top/bottom)
19
+ - Global search & filter builder
20
+ - Sorting (multi-sort with Shift+click)
21
+ - Filtering (text, date range, number range)
22
+ - Pagination (client or server-side)
23
+ - Row selection (checkbox / radio)
24
+ - Expandable rows & master-detail
25
+ - Conditional formatting
26
+ - Row grouping with aggregations
27
+ - Tree data (hierarchical rows)
28
+ - Editable cells (text, number, select, date, toggle)
29
+ - Shimmer loading & infinite scroll
30
+ - AI mode (natural-language queries, insights, charts)
31
+ - Status bar, toolbar customization, custom icons
32
+ - Theme support (auto / light / dark)
33
+ - Mobile-friendly context menus (long-press)
34
+ - Nested / grouped columns
35
+ - Zero-config styling (inline CSS, no imports needed)
42
36
 
43
37
  ---
44
38
 
@@ -48,923 +42,6 @@ A high-performance, zero-dependency\* React table component. Only the rows visib
48
42
  npm install bolt-table @tanstack/react-virtual
49
43
  ```
50
44
 
51
- That's it. No other peer dependencies.
52
-
53
- ---
54
-
55
- ## Quick start
56
-
57
- ```tsx
58
- import { BoltTable, ColumnType } from 'bolt-table';
59
-
60
- interface User {
61
- id: string;
62
- name: string;
63
- email: string;
64
- age: number;
65
- }
66
-
67
- const columns: ColumnType<User>[] = [
68
- { key: 'name', dataIndex: 'name', title: 'Name', width: 200 },
69
- { key: 'email', dataIndex: 'email', title: 'Email', width: 280 },
70
- { key: 'age', dataIndex: 'age', title: 'Age', width: 80 },
71
- ];
72
-
73
- const data: User[] = [
74
- { id: '1', name: 'Alice', email: 'alice@example.com', age: 28 },
75
- { id: '2', name: 'Bob', email: 'bob@example.com', age: 34 },
76
- { id: '3', name: 'Charlie', email: 'charlie@example.com', age: 22 },
77
- ];
78
-
79
- export default function App() {
80
- return (
81
- <BoltTable<User>
82
- columns={columns}
83
- data={data}
84
- rowKey="id"
85
- />
86
- );
87
- }
88
- ```
89
-
90
- ---
91
-
92
- ## Next.js (App Router)
93
-
94
- BoltTable uses browser APIs and must be wrapped in a client boundary:
95
-
96
- ```tsx
97
- 'use client';
98
- import { BoltTable } from 'bolt-table';
99
- ```
100
-
101
- ---
102
-
103
- ## Styling
104
-
105
- BoltTable uses **inline CSS styles** for all defaults — no Tailwind, no CSS variables, no external stylesheets required. It works out of the box in any React project, light or dark mode.
106
-
107
- You can customize everything via the `styles` and `classNames` props, including headers, cells, rows, pinned regions, and pagination. If your project uses Tailwind, you can pass Tailwind classes through `classNames` and they'll be applied on top of the inline defaults.
108
-
109
- ### Custom icons
110
-
111
- All built-in icons are inline SVGs. Override any icon via the `icons` prop:
112
-
113
- ```tsx
114
- import type { BoltTableIcons } from 'bolt-table';
115
-
116
- <BoltTable
117
- icons={{
118
- gripVertical: <MyGripIcon size={12} />,
119
- sortAsc: <MySortUpIcon size={12} />,
120
- chevronsLeft: <MyFirstPageIcon size={12} />,
121
- search: <MySearchIcon size={14} />,
122
- columns: <MyColumnsIcon size={14} />,
123
- close: <MyXIcon size={12} />,
124
- }}
125
- />
126
- ```
127
-
128
- Available icon keys: `gripVertical`, `sortAsc`, `sortDesc`, `filter`, `filterClear`, `pin`, `pinOff`, `eyeOff`, `chevronDown`, `chevronLeft`, `chevronRight`, `chevronsLeft`, `chevronsRight`, `copy`, `edit`, `search`, `columns`, `close`.
129
-
130
- ---
131
-
132
- ## Props
133
-
134
- ### `BoltTable`
135
-
136
- | Prop | Type | Default | Description |
137
- |------|------|---------|-------------|
138
- | `columns` | `ColumnType<T>[]` | — | Column definitions (required) |
139
- | `data` | `T[]` | — | Row data array (required) |
140
- | `rowKey` | `string \| (record: T) => string` | `'id'` | Unique row identifier. Duplicate keys are handled automatically |
141
- | `rowHeight` | `number` | `40` | Base height of each row in pixels |
142
- | `expandedRowHeight` | `number` | `200` | Estimated height for expanded rows |
143
- | `maxExpandedRowHeight` | `number` | — | Max height for expanded row panels (makes them scrollable) |
144
- | `accentColor` | `string` | `'#1890ff'` | Color used for sort icons, selected rows, resize line, etc. |
145
- | `className` | `string` | `''` | Class name for the outer wrapper |
146
- | `classNames` | `ClassNamesTypes` | `{}` | Granular class overrides per table region |
147
- | `styles` | `StylesTypes` | `{}` | Inline style overrides per table region |
148
- | `icons` | `BoltTableIcons` | — | Custom icon overrides for built-in SVG icons |
149
- | `gripIcon` | `ReactNode` | — | Custom drag grip icon (deprecated, use `icons.gripVertical`) |
150
- | `hideGripIcon` | `boolean` | `false` | Hide the drag grip icon from all headers |
151
- | `pagination` | `PaginationType \| false` | — | Pagination config, or `false` to disable |
152
- | `onPaginationChange` | `(page, pageSize) => void` | — | Called when page or page size changes |
153
- | `onColumnResize` | `(columnKey, newWidth) => void` | — | Called when a column is resized |
154
- | `onColumnOrderChange` | `(newOrder) => void` | — | Called when columns are reordered |
155
- | `onColumnPin` | `(columnKey, pinned) => void` | — | Called when a column is pinned/unpinned |
156
- | `onColumnHide` | `(columnKey, hidden) => void` | — | Called when a column is hidden/shown |
157
- | `rowSelection` | `RowSelectionConfig<T>` | — | Row selection config |
158
- | `rowPinning` | `RowPinningConfig` | — | Row pinning config (`{ top?: Key[], bottom?: Key[] }`) |
159
- | `onRowPin` | `(rowKey, pinned) => void` | — | Called when a row is pinned/unpinned via cell context menu |
160
- | `expandable` | `ExpandableConfig<T>` | — | Expandable row config |
161
- | `onEndReached` | `() => void` | — | Called when scrolled near the bottom (infinite scroll) |
162
- | `onEndReachedThreshold` | `number` | `5` | Rows from end to trigger `onEndReached` |
163
- | `isLoading` | `boolean` | `false` | Shows shimmer skeleton rows when `true` |
164
- | `onSortChange` | `(columnKey, direction) => void` | — | Server-side sort handler (disables local sort) |
165
- | `onFilterChange` | `(filters) => void` | — | Server-side filter handler (disables local filter) |
166
- | `columnContextMenuItems` | `ColumnContextMenuItem[]` | — | Custom items appended to the header context menu |
167
- | `autoHeight` | `boolean` | `true` | Auto-size table height to content (capped at 10 rows) |
168
- | `layoutLoading` | `boolean` | `false` | Show full skeleton layout (headers + rows) |
169
- | `emptyRenderer` | `ReactNode` | — | Custom empty state content |
170
- | `rowClassName` | `(record, index) => string` | — | Returns a CSS class name for conditional row styling |
171
- | `rowStyle` | `(record, index) => CSSProperties` | — | Returns inline styles for conditional row styling |
172
- | `disabledFilters` | `boolean` | `false` | Removes the filter option from all header context menus |
173
- | `onCopy` | `(text, columnKey, record, rowIndex) => void` | — | Called after a cell value is copied to the clipboard |
174
- | `keepPinnedRowsAcrossPages` | `boolean` | `false` | Pinned rows remain visible after navigating to a different page |
175
- | `onEdit` | `(value, record, dataIndex, rowIndex) => void` | — | Called when a user finishes editing an editable cell |
176
- | `onRowClick` | `(record, index, event) => void` | — | Called when a row is clicked; adds pointer cursor to all cells |
177
- | `enableColumnVirtualization` | `boolean` | `false` | Only render columns visible in the viewport. Recommended for 100+ column tables |
178
- | `enableDynamicRowHeight` | `boolean` | `false` | Measure each row's actual content height instead of using a fixed `rowHeight` |
179
- | `columnPersistence` | `ColumnPersistenceConfig \| false` | `false` | Save column order, widths, visibility, and pinned state to localStorage |
180
- | `showColumnSettings` | `boolean` | `true` | Show the column picker button (checklist panel to toggle columns on/off) |
181
- | `hideGlobalSearch` | `boolean` | `false` | Hide the global search input above the table |
182
- | `globalSearchValue` | `string` | — | Controlled global search value |
183
- | `onGlobalSearchChange` | `(value: string) => void` | — | Called when the global search input changes |
184
- | `rowDragEnabled` | `boolean` | `false` | Show a drag grip handle on each row for drag-and-drop reordering |
185
- | `onRowReorder` | `(fromIndex, toIndex) => void` | — | Called when the user drops a row into a new position |
186
- | `aiMode` | `boolean` | `false` | Enable the AI assistant button in the toolbar |
187
- | `aiConfig` | `BoltTableAIConfig` | — | AI provider configuration (API key, model, etc.) |
188
- | `onAIQuery` | `(query, context) => Promise<AIResponse>` | — | Custom AI query handler (overrides built-in AI) |
189
- | `onAIResponse` | `(response: AIResponse) => void` | — | Called after AI applies operations |
190
- | `aiPlaceholder` | `string` | `"Ask AI anything..."` | Placeholder text for the AI search bar |
191
- | `aiButtonLabel` | `ReactNode` | `"Ask AI"` | Label for the AI button |
192
-
193
- ---
194
-
195
- ### `ColumnType<T>`
196
-
197
- | Field | Type | Default | Description |
198
- |-------|------|---------|-------------|
199
- | `key` | `string` | — | Unique column identifier (required) |
200
- | `dataIndex` | `string` | — | Row object property to display (required for leaf columns, omit for groups) |
201
- | `title` | `string \| ReactNode` | — | Header label (required) |
202
- | `width` | `number` | `150` | Column width in pixels |
203
- | `render` | `(value, record, index) => ReactNode` | — | Custom cell renderer |
204
- | `shimmerRender` | `() => ReactNode` | — | Custom shimmer skeleton for this column |
205
- | `sortable` | `boolean` | `true` | Show sort controls for this column |
206
- | `sorter` | `boolean \| (a: T, b: T) => number` | — | Custom sort comparator for client-side sort |
207
- | `filterable` | `boolean` | `true` | Show filter option in context menu |
208
- | `filterFn` | `(value, record, dataIndex) => boolean` | — | Custom filter predicate for client-side filter |
209
- | `hidden` | `boolean` | `false` | Hide this column |
210
- | `defaultHidden` | `boolean` | `false` | Hide this column on first render (uncontrolled) |
211
- | `pinned` | `'left' \| 'right' \| false` | `false` | Pin this column to an edge |
212
- | `defaultPinned` | `'left' \| 'right' \| false` | `false` | Pin this column on first render (uncontrolled) |
213
- | `className` | `string` | — | Class applied to all cells in this column |
214
- | `style` | `CSSProperties` | — | Styles applied to all cells in this column |
215
- | `copy` | `boolean \| (value, record, index) => string` | — | Enable "Copy" in cell context menu; function customizes what's copied |
216
- | `editable` | `boolean` | `false` | Cells become inline-editable (no custom `render` required) |
217
- | `children` | `ColumnType<T>[]` | — | Nested child columns. Makes this column a header group — only leaf columns render data |
218
-
219
- ---
220
-
221
- ### `ColumnPersistenceConfig`
222
-
223
- | Field | Type | Default | Description |
224
- |-------|------|---------|-------------|
225
- | `storageKey` | `string` | — | localStorage key prefix used to store the state (required) |
226
- | `persistOrder` | `boolean` | `true` | Persist and restore column order |
227
- | `persistWidths` | `boolean` | `true` | Persist and restore column widths |
228
- | `persistVisibility` | `boolean` | `true` | Persist and restore hidden/visible state |
229
- | `persistPinned` | `boolean` | `true` | Persist and restore column pinned state |
230
-
231
- ---
232
-
233
- ## Examples
234
-
235
- ### Global search
236
-
237
- BoltTable renders a search bar above the table by default. It searches across **all columns** of every row using a case-insensitive substring match.
238
-
239
- ```tsx
240
- // Uncontrolled — BoltTable manages the search value internally
241
- <BoltTable columns={columns} data={data} />
242
-
243
- // Hide it entirely
244
- <BoltTable columns={columns} data={data} hideGlobalSearch />
245
-
246
- // Controlled — drive the search value from outside
247
- const [search, setSearch] = useState('');
248
-
249
- <BoltTable
250
- columns={columns}
251
- data={data}
252
- globalSearchValue={search}
253
- onGlobalSearchChange={setSearch}
254
- />
255
- ```
256
-
257
- ---
258
-
259
- ### Column picker
260
-
261
- The column picker button ("Columns") is shown in the toolbar by default. Clicking it opens a checklist panel letting users toggle any non-pinned column on or off.
262
-
263
- ```tsx
264
- // Shown by default — no configuration needed
265
- <BoltTable columns={columns} data={data} />
266
-
267
- // Hide the column picker button
268
- <BoltTable columns={columns} data={data} showColumnSettings={false} />
269
- ```
270
-
271
- > **Note:** Pinned columns cannot be hidden from the picker — unpin them first.
272
-
273
- ---
274
-
275
- ### Column persistence
276
-
277
- Pass a `columnPersistence` config to automatically save column state to `localStorage`. On the next page load the table restores the saved order, widths, visibility, and pinned state.
278
-
279
- ```tsx
280
- <BoltTable
281
- columns={columns}
282
- data={data}
283
- rowKey="id"
284
- columnPersistence={{
285
- storageKey: 'users-table', // stored as bt_users-table in localStorage
286
- persistOrder: true, // default
287
- persistWidths: true, // default
288
- persistVisibility: true, // default
289
- persistPinned: true, // default
290
- }}
291
- />
292
- ```
293
-
294
- To persist only widths (not order or visibility):
295
-
296
- ```tsx
297
- <BoltTable
298
- columnPersistence={{
299
- storageKey: 'orders-table',
300
- persistOrder: false,
301
- persistVisibility: false,
302
- persistPinned: false,
303
- }}
304
- />
305
- ```
306
-
307
- ---
308
-
309
- ### Horizontal virtualization
310
-
311
- For tables with a large number of columns enable column virtualization. Only the columns currently visible in the viewport (plus one overscan column on each side) are rendered. Pinned columns always render regardless.
312
-
313
- ```tsx
314
- <BoltTable
315
- columns={hundredsOfColumns}
316
- data={data}
317
- enableColumnVirtualization
318
- />
319
- ```
320
-
321
- > Best suited for tables with 50+ columns. For typical column counts (< 30) the overhead isn't worth it.
322
-
323
- ---
324
-
325
- ### Dynamic row heights
326
-
327
- When row content can vary in height (e.g. multi-line text, embedded components), enable dynamic row heights. BoltTable uses `ResizeObserver` to measure each row's actual rendered height and updates the virtualizer accordingly.
328
-
329
- ```tsx
330
- <BoltTable
331
- columns={columns}
332
- data={data}
333
- enableDynamicRowHeight
334
- rowHeight={40} // used as the minimum / estimated height
335
- />
336
- ```
337
-
338
- The `rowHeight` prop still acts as the minimum and estimated height used before measurement. Rows grow taller as needed based on their content.
339
-
340
- ---
341
-
342
- ### Duplicate row keys
343
-
344
- If your data can contain rows with the same `id` (or whatever field is used as `rowKey`), BoltTable handles it automatically — no extra configuration needed. Internally it detects duplicates and appends the row index to produce a unique key for the virtualizer and DOM, while still passing the original key to selection and event callbacks.
345
-
346
- ```tsx
347
- // Works correctly even with duplicate ids
348
- <BoltTable
349
- columns={columns}
350
- data={[
351
- { id: 1, name: 'Alice' },
352
- { id: 1, name: 'Alice (copy)' }, // same id — renders correctly
353
- ]}
354
- rowKey="id"
355
- />
356
- ```
357
-
358
- ---
359
-
360
- ### Sorting
361
-
362
- **Client-side** (no `onSortChange` — BoltTable sorts locally):
363
-
364
- ```tsx
365
- const columns: ColumnType<User>[] = [
366
- {
367
- key: 'name',
368
- dataIndex: 'name',
369
- title: 'Name',
370
- sortable: true,
371
- sorter: (a, b) => a.name.localeCompare(b.name),
372
- },
373
- {
374
- key: 'age',
375
- dataIndex: 'age',
376
- title: 'Age',
377
- sortable: true,
378
- },
379
- ];
380
-
381
- <BoltTable columns={columns} data={data} />
382
- ```
383
-
384
- **Server-side** (provide `onSortChange` — BoltTable delegates to you):
385
-
386
- ```tsx
387
- const [sortKey, setSortKey] = useState('');
388
- const [sortDir, setSortDir] = useState<SortDirection>(null);
389
-
390
- <BoltTable
391
- columns={columns}
392
- data={serverData}
393
- onSortChange={(key, dir) => {
394
- setSortKey(key);
395
- setSortDir(dir);
396
- refetch({ sortKey: key, sortDir: dir });
397
- }}
398
- />
399
- ```
400
-
401
- ---
402
-
403
- ### Filtering
404
-
405
- **Client-side** (no `onFilterChange`):
406
-
407
- ```tsx
408
- const columns: ColumnType<User>[] = [
409
- {
410
- key: 'status',
411
- dataIndex: 'status',
412
- title: 'Status',
413
- filterable: true,
414
- filterFn: (value, record) => record.status === value,
415
- },
416
- ];
417
- ```
418
-
419
- **Server-side**:
420
-
421
- ```tsx
422
- <BoltTable
423
- columns={columns}
424
- data={serverData}
425
- onFilterChange={(filters) => {
426
- setActiveFilters(filters);
427
- refetch({ filters });
428
- }}
429
- />
430
- ```
431
-
432
- ---
433
-
434
- ### Pagination
435
-
436
- **Client-side** (pass all data, BoltTable slices it):
437
-
438
- ```tsx
439
- <BoltTable
440
- columns={columns}
441
- data={allUsers}
442
- pagination={{ pageSize: 20 }}
443
- onPaginationChange={(page, size) => setPage(page)}
444
- />
445
- ```
446
-
447
- **Server-side** (pass only the current page):
448
-
449
- ```tsx
450
- <BoltTable
451
- columns={columns}
452
- data={currentPageData}
453
- pagination={{
454
- current: page,
455
- pageSize: 20,
456
- total: 500,
457
- showTotal: (total, [from, to]) => `${from}-${to} of ${total} users`,
458
- }}
459
- onPaginationChange={(page, size) => fetchPage(page, size)}
460
- />
461
- ```
462
-
463
- **Disable pagination:**
464
-
465
- ```tsx
466
- <BoltTable columns={columns} data={data} pagination={false} />
467
- ```
468
-
469
- ---
470
-
471
- ### Row selection
472
-
473
- ```tsx
474
- const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
475
-
476
- <BoltTable
477
- columns={columns}
478
- data={data}
479
- rowKey="id"
480
- rowSelection={{
481
- type: 'checkbox',
482
- selectedRowKeys,
483
- onChange: (keys, rows) => setSelectedRowKeys(keys),
484
- getCheckboxProps: (record) => ({
485
- disabled: record.status === 'locked',
486
- }),
487
- }}
488
- />
489
- ```
490
-
491
- ---
492
-
493
- ### Expandable rows
494
-
495
- ```tsx
496
- <BoltTable
497
- columns={columns}
498
- data={data}
499
- rowKey="id"
500
- expandable={{
501
- rowExpandable: (record) => record.details !== null,
502
- expandedRowRender: (record) => (
503
- <div style={{ padding: 16 }}>
504
- <h4>{record.name} — Details</h4>
505
- <pre>{JSON.stringify(record.details, null, 2)}</pre>
506
- </div>
507
- ),
508
- }}
509
- expandedRowHeight={150}
510
- maxExpandedRowHeight={400}
511
- />
512
- ```
513
-
514
- ---
515
-
516
- ### Infinite scroll
517
-
518
- ```tsx
519
- const [data, setData] = useState<User[]>([]);
520
- const [isLoading, setIsLoading] = useState(false);
521
-
522
- const loadMore = async () => {
523
- setIsLoading(true);
524
- const newRows = await fetchNextPage();
525
- setData(prev => [...prev, ...newRows]);
526
- setIsLoading(false);
527
- };
528
-
529
- <BoltTable
530
- columns={columns}
531
- data={data}
532
- isLoading={isLoading}
533
- onEndReached={loadMore}
534
- onEndReachedThreshold={8}
535
- pagination={false}
536
- />
537
- ```
538
-
539
- ---
540
-
541
- ### Column pinning
542
-
543
- ```tsx
544
- const columns: ColumnType<User>[] = [
545
- { key: 'name', dataIndex: 'name', title: 'Name', pinned: 'left', width: 200 },
546
- { key: 'email', dataIndex: 'email', title: 'Email', width: 250 },
547
- { key: 'actions', dataIndex: 'actions', title: 'Actions', pinned: 'right', width: 100 },
548
- ];
549
- ```
550
-
551
- Users can also pin/unpin columns at runtime via the right-click context menu.
552
-
553
- ---
554
-
555
- ### Row pinning
556
-
557
- Pin rows to the top or bottom of the table so they stay visible while scrolling vertically. Pinned rows transcend pagination — they are always visible regardless of which page the user is on.
558
-
559
- ```tsx
560
- const [rowPinning, setRowPinning] = useState({ top: [], bottom: [] });
561
-
562
- <BoltTable
563
- columns={columns}
564
- data={data}
565
- rowKey="id"
566
- rowPinning={rowPinning}
567
- onRowPin={(key, pinned) => {
568
- setRowPinning(prev => {
569
- const top = (prev.top ?? []).filter(k => String(k) !== String(key));
570
- const bottom = (prev.bottom ?? []).filter(k => String(k) !== String(key));
571
- if (pinned === 'top') top.push(key);
572
- if (pinned === 'bottom') bottom.push(key);
573
- return { top, bottom };
574
- });
575
- }}
576
- styles={{ pinnedRowBg: 'rgba(255, 255, 255, 0.95)' }}
577
- />
578
- ```
579
-
580
- Users can also pin/unpin rows at runtime via the right-click context menu on any body cell (when `onRowPin` is provided).
581
-
582
- Pinned rows use `position: sticky` with `backdropFilter: blur(12px)` and a subtle box-shadow to visually separate them from scrolling content. Customize with `classNames.pinnedRow`, `styles.pinnedRow`, and `styles.pinnedRowBg`.
583
-
584
- ---
585
-
586
- ### Cell context menu & copy
587
-
588
- Right-click (or long-press on mobile) any body cell to see a context menu with:
589
-
590
- - **Pin to Top / Unpin from Top** — shown when `onRowPin` is provided
591
- - **Pin to Bottom / Unpin from Bottom** — shown when `onRowPin` is provided
592
- - **Copy** — shown when the column has `copy: true` or a copy function
593
-
594
- ```tsx
595
- const columns: ColumnType<User>[] = [
596
- {
597
- key: 'name',
598
- dataIndex: 'name',
599
- title: 'Name',
600
- copy: true, // copies the raw cell value
601
- },
602
- {
603
- key: 'email',
604
- dataIndex: 'email',
605
- title: 'Email',
606
- // Custom copy — control exactly what goes to the clipboard
607
- copy: (value, record) => `${record.name} <${value}>`,
608
- },
609
- ];
610
- ```
611
-
612
- The cell context menu only appears when there is at least one action available (either `onRowPin` or `column.copy`). Otherwise, the browser's default context menu is used.
613
-
614
- ---
615
-
616
- ### Editable cells
617
-
618
- Mark columns as `editable: true` and provide an `onEdit` callback to allow inline editing. Right-click (or long-press on mobile) an editable cell to see the **Edit** option (with a pencil icon) in the context menu. Selecting it turns the cell into an input field. Press **Enter** or click away to commit, **Escape** to cancel.
619
-
620
- ```tsx
621
- const [data, setData] = useState<User[]>(initialData);
622
-
623
- const columns: ColumnType<User>[] = [
624
- { key: 'name', dataIndex: 'name', title: 'Name', editable: true, width: 200 },
625
- { key: 'age', dataIndex: 'age', title: 'Age', editable: true, width: 80 },
626
- {
627
- key: 'status',
628
- dataIndex: 'status',
629
- title: 'Status',
630
- render: (value) => <span className="badge">{String(value)}</span>,
631
- editable: true, // ignored — custom render takes precedence
632
- },
633
- ];
634
-
635
- <BoltTable
636
- columns={columns}
637
- data={data}
638
- rowKey="id"
639
- onEdit={(value, record, dataIndex, rowIndex) => {
640
- setData(prev =>
641
- prev.map((row, i) =>
642
- i === rowIndex ? { ...row, [dataIndex]: value } : row
643
- )
644
- );
645
- }}
646
- />
647
- ```
648
-
649
- The edit icon can be customized via the `icons` prop:
650
-
651
- ```tsx
652
- <BoltTable icons={{ edit: <MyPencilIcon size={14} /> }} />
653
- ```
654
-
655
- > **Note:** `editable` is skipped for columns that define a custom `render` function — since the cell content is fully controlled by the renderer, inline editing wouldn't know how to display or commit changes. If you need editable custom-rendered cells, handle the editing UX inside your `render` function.
656
-
657
- ---
658
-
659
- ### Styling overrides
660
-
661
- ```tsx
662
- <BoltTable
663
- columns={columns}
664
- data={data}
665
- accentColor="#6366f1"
666
- classNames={{
667
- header: 'text-xs uppercase tracking-wider text-gray-500',
668
- cell: 'text-sm',
669
- pinnedHeader: 'border-r border-indigo-200',
670
- pinnedCell: 'border-r border-indigo-100',
671
- }}
672
- styles={{
673
- header: { fontWeight: 600 },
674
- rowHover: { backgroundColor: '#f0f9ff' },
675
- rowSelected: { backgroundColor: '#e0e7ff' },
676
- pinnedBg: 'rgba(238, 242, 255, 0.95)',
677
- }}
678
- />
679
- ```
680
-
681
- ---
682
-
683
- ### Nested / grouped columns
684
-
685
- Group related columns under a shared header. Parent columns act as header groups — only leaf columns (without `children`) render data cells. Leaf columns within groups support resizing and reordering just like standalone columns.
686
-
687
- ```tsx
688
- const columns: ColumnType<User>[] = [
689
- { key: 'id', dataIndex: 'id', title: 'ID', width: 80 },
690
- {
691
- key: 'nameGroup',
692
- title: 'Name',
693
- children: [
694
- { key: 'firstName', dataIndex: 'firstName', title: 'First Name', width: 150 },
695
- { key: 'lastName', dataIndex: 'lastName', title: 'Last Name', width: 150 },
696
- ],
697
- },
698
- {
699
- key: 'contactGroup',
700
- title: 'Contact Info',
701
- children: [
702
- { key: 'email', dataIndex: 'email', title: 'Email', width: 250 },
703
- { key: 'phone', dataIndex: 'phone', title: 'Phone', width: 150 },
704
- ],
705
- },
706
- { key: 'age', dataIndex: 'age', title: 'Age', width: 80 },
707
- ];
708
-
709
- <BoltTable columns={columns} data={data} rowKey="id" />
710
- ```
711
-
712
- The header renders two rows: group headers span their children columns in the top row, and leaf column headers appear in the bottom row. Standalone columns (not in any group) span both header rows.
713
-
714
- ---
715
-
716
- ### Pagination styles
717
-
718
- Customize every part of the pagination footer via `styles` and `classNames`:
719
-
720
- ```tsx
721
- <BoltTable
722
- columns={columns}
723
- data={data}
724
- pagination={{ pageSize: 20 }}
725
- classNames={{
726
- pagination: 'bg-gray-50 border-t border-gray-200',
727
- paginationButton: 'rounded hover:bg-gray-200',
728
- paginationActiveButton: 'font-bold text-blue-600',
729
- paginationSelect: 'rounded border-gray-300',
730
- paginationInfo: 'text-gray-500 text-xs',
731
- }}
732
- styles={{
733
- pagination: { height: 40 },
734
- paginationButton: { borderRadius: 4 },
735
- paginationActiveButton: { fontWeight: 700 },
736
- paginationSelect: { borderRadius: 4 },
737
- paginationInfo: { fontSize: 11 },
738
- }}
739
- />
740
- ```
741
-
742
- Available pagination style/class keys: `pagination` (wrapper), `paginationButton` (nav buttons), `paginationActiveButton` (active page), `paginationSelect` (page-size dropdown), `paginationInfo` (range text).
743
-
744
- ---
745
-
746
- ### Fixed height (fill parent)
747
-
748
- By default, BoltTable auto-sizes to its content. To fill a fixed-height container instead:
749
-
750
- ```tsx
751
- <div style={{ height: 600 }}>
752
- <BoltTable
753
- columns={columns}
754
- data={data}
755
- autoHeight={false}
756
- />
757
- </div>
758
- ```
759
-
760
- ### Row drag-and-drop reordering
761
-
762
- Enable row reordering with a drag grip handle on each row. Users can drag rows to new positions.
763
-
764
- ```tsx
765
- const [data, setData] = useState<User[]>(initialData);
766
-
767
- <BoltTable
768
- columns={columns}
769
- data={data}
770
- rowKey="id"
771
- rowDragEnabled
772
- onRowReorder={(fromIndex, toIndex) => {
773
- setData(prev => {
774
- const next = [...prev];
775
- const [moved] = next.splice(fromIndex, 1);
776
- next.splice(toIndex, 0, moved);
777
- return next;
778
- });
779
- }}
780
- />
781
- ```
782
-
783
- A grip icon appears as a pinned column on the left. Drag a row by its grip handle and drop it onto another row — the blue indicator line shows where it will land.
784
-
785
- > **Note:** `onRowReorder` receives the indices within the current page's visible data. If you use server-side pagination, map these back to your full dataset accordingly.
786
-
787
- ---
788
-
789
- ### Column auto-fit width
790
-
791
- Double-click the resize handle (right edge) of any column header to automatically fit the column width to its content. The table measures the header title and all visible cell content, then sets the optimal width (clamped between 60px and 800px).
792
-
793
- ```tsx
794
- // No extra configuration needed — auto-fit is built into every column's resize handle.
795
- // Just double-click the right edge of any header.
796
-
797
- <BoltTable columns={columns} data={data} />
798
- ```
799
-
800
- You still get notified via `onColumnResize` when an auto-fit happens:
801
-
802
- ```tsx
803
- <BoltTable
804
- columns={columns}
805
- data={data}
806
- onColumnResize={(columnKey, newWidth) => {
807
- console.log(`Column ${columnKey} auto-fitted to ${newWidth}px`);
808
- }}
809
- />
810
- ```
811
-
812
- ---
813
-
814
- ### AI mode
815
-
816
- Enable the AI assistant to let users interact with the table using natural language. The AI can filter, sort, style, resize, reorder, pin columns, and navigate pages.
817
-
818
- ```tsx
819
- <BoltTable
820
- columns={columns}
821
- data={data}
822
- rowKey="id"
823
- aiMode
824
- aiConfig={{
825
- provider: 'openai', // 'openai' | 'anthropic' | 'custom'
826
- apiKey: 'sk-...',
827
- model: 'gpt-4o-mini', // optional, defaults vary by provider
828
- }}
829
- onAIResponse={(response) => {
830
- console.log('AI applied:', response.message);
831
- }}
832
- />
833
- ```
834
-
835
- **Custom AI handler** (bring your own backend):
836
-
837
- ```tsx
838
- <BoltTable
839
- columns={columns}
840
- data={data}
841
- aiMode
842
- onAIQuery={async (query, { data, columns }) => {
843
- const res = await fetch('/api/table-ai', {
844
- method: 'POST',
845
- body: JSON.stringify({ query, schema: columns.map(c => c.key) }),
846
- });
847
- return res.json(); // must return { operations: [...], message: "..." }
848
- }}
849
- />
850
- ```
851
-
852
- **Saved filters** — when AI answers a query, a "Save Filter" button appears. Clicking it saves the operations to localStorage. Saved filters can be re-applied instantly with zero AI calls. The saved filters dropdown appears automatically when you have saved filters.
853
-
854
- ```tsx
855
- // Saved filters are stored automatically using the columnPersistence storageKey.
856
- // To enable saved filters, just use aiMode with columnPersistence:
857
- <BoltTable
858
- columns={columns}
859
- data={data}
860
- aiMode
861
- aiConfig={{ provider: 'openai', apiKey: 'sk-...' }}
862
- columnPersistence={{ storageKey: 'my-table' }}
863
- />
864
- ```
865
-
866
- ---
867
-
868
- ### Safe row keys (NaN / undefined / duplicates)
869
-
870
- BoltTable handles edge cases in row keys automatically. If your `rowKey` field contains `undefined`, `null`, `NaN`, or empty strings, the table falls back to index-based keys instead of producing broken or colliding keys.
871
-
872
- ```tsx
873
- // All of these work correctly — no crashes, no duplicate key warnings:
874
-
875
- // Missing id field
876
- <BoltTable data={[{ name: 'Alice' }, { name: 'Bob' }]} rowKey="id" columns={columns} />
877
-
878
- // NaN values
879
- <BoltTable data={[{ id: NaN, name: 'Alice' }]} rowKey="id" columns={columns} />
880
-
881
- // Duplicate ids — automatically deduplicated
882
- <BoltTable
883
- data={[
884
- { id: 1, name: 'Alice' },
885
- { id: 1, name: 'Alice (copy)' },
886
- ]}
887
- rowKey="id"
888
- columns={columns}
889
- />
890
-
891
- // rowKey function that might return undefined
892
- <BoltTable
893
- data={data}
894
- rowKey={(record) => record.uuid} // safe even if uuid is undefined
895
- columns={columns}
896
- />
897
- ```
898
-
899
- ---
900
-
901
- ## Documentation
902
-
903
- For the complete guide with in-depth examples for every feature, visit the **[BoltTable Documentation](https://bolt-table.vercel.app/)**.
904
-
905
- ---
906
-
907
- ## Type exports
908
-
909
- ```ts
910
- import type {
911
- ColumnType,
912
- ColumnContextMenuItem,
913
- ColumnPersistenceConfig,
914
- RowSelectionConfig,
915
- RowPinningConfig,
916
- ExpandableConfig,
917
- PaginationType,
918
- SortDirection,
919
- DataRecord,
920
- BoltTableIcons,
921
- // AI types
922
- AIResponse,
923
- AIOperation,
924
- BoltTableAIConfig,
925
- BoltTableConfig,
926
- } from 'bolt-table';
927
- ```
928
-
929
- ---
930
-
931
- ## License
932
-
933
- MIT © [Venkatesh Sirigineedi](https://github.com/venkateshwebdev)
934
-
935
- - **Row virtualization** — only visible rows are rendered, powered by TanStack Virtual
936
- - **Drag to reorder columns** — custom zero-dependency drag-and-drop (no @dnd-kit needed)
937
- - **Column pinning** — pin columns to the left or right edge via right-click
938
- - **Column resizing** — drag the right edge of any header to resize
939
- - **Column hiding** — hide/show columns via the right-click context menu
940
- - **Sorting** — client-side or server-side, with custom comparators per column
941
- - **Filtering** — client-side or server-side, with custom filter functions per column
942
- - **Pagination** — client-side slice or server-side with full control
943
- - **Row selection** — checkbox or radio, with select-all, indeterminate state, and disabled rows
944
- - **Expandable rows** — auto-measured content panels below each row, controlled or uncontrolled
945
- - **Shimmer loading** — animated skeleton rows on initial load and infinite scroll append
946
- - **Infinite scroll** — `onEndReached` callback with configurable threshold
947
- - **Empty state** — custom renderer or default "No data" message
948
- - **Auto height** — table shrinks/grows to fit rows, capped at 10 rows by default
949
- - **Row pinning** — pin rows to the top or bottom of the table, sticky during vertical scroll
950
- - **Cell context menu** — right-click (or long-press on mobile) any cell to pin rows or copy values
951
- - **Right-click context menu** — sort, filter, pin, hide, plus custom items
952
- - **Mobile-friendly context menus** — long-press (touch-and-hold) triggers context menus on touch devices
953
- - **Nested / grouped columns** — group related columns under a shared header spanning multiple columns
954
- - **Theme-agnostic** — works in light and dark mode out of the box, no CSS variables needed
955
- - **Editable cells** — right-click any cell on an `editable` column to inline-edit via the context menu
956
- - **Custom icons** — override any built-in icon via the `icons` prop
957
-
958
- ---
959
-
960
- ## Installation
961
-
962
- ```bash
963
- npm install bolt-table @tanstack/react-virtual
964
- ```
965
-
966
- That's it. No other peer dependencies.
967
-
968
45
  ---
969
46
 
970
47
  ## Quick start
@@ -1002,11 +79,7 @@ export default function App() {
1002
79
  }
1003
80
  ```
1004
81
 
1005
- ---
1006
-
1007
- ## Next.js (App Router)
1008
-
1009
- BoltTable uses browser APIs and must be wrapped in a client boundary:
82
+ ### Next.js (App Router)
1010
83
 
1011
84
  ```tsx
1012
85
  'use client';
@@ -1015,510 +88,9 @@ import { BoltTable } from 'bolt-table';
1015
88
 
1016
89
  ---
1017
90
 
1018
- ## Styling
1019
-
1020
- BoltTable uses **inline CSS styles** for all defaults — no Tailwind, no CSS variables, no external stylesheets required. It works out of the box in any React project, light or dark mode.
1021
-
1022
- You can customize everything via the `styles` and `classNames` props, including headers, cells, rows, pinned regions, and pagination. If your project uses Tailwind, you can pass Tailwind classes through `classNames` and they'll be applied on top of the inline defaults.
1023
-
1024
- ### Custom icons
1025
-
1026
- All built-in icons are inline SVGs. Override any icon via the `icons` prop:
1027
-
1028
- ```tsx
1029
- import type { BoltTableIcons } from 'bolt-table';
1030
-
1031
- <BoltTable
1032
- icons={{
1033
- gripVertical: <MyGripIcon size={12} />,
1034
- sortAsc: <MySortUpIcon size={12} />,
1035
- chevronsLeft: <MyFirstPageIcon size={12} />,
1036
- }}
1037
- />
1038
- ```
1039
-
1040
- Available icon keys: `gripVertical`, `sortAsc`, `sortDesc`, `filter`, `filterClear`, `pin`, `pinOff`, `eyeOff`, `chevronDown`, `chevronLeft`, `chevronRight`, `chevronsLeft`, `chevronsRight`, `copy`, `edit`.
1041
-
1042
- ---
1043
-
1044
- ## Props
1045
-
1046
- ### `BoltTable`
1047
-
1048
- | Prop | Type | Default | Description |
1049
- |------|------|---------|-------------|
1050
- | `columns` | `ColumnType<T>[]` | — | Column definitions (required) |
1051
- | `data` | `T[]` | — | Row data array (required) |
1052
- | `rowKey` | `string \| (record: T) => string` | `'id'` | Unique row identifier |
1053
- | `rowHeight` | `number` | `40` | Height of each row in pixels |
1054
- | `expandedRowHeight` | `number` | `200` | Estimated height for expanded rows |
1055
- | `maxExpandedRowHeight` | `number` | — | Max height for expanded row panels (makes them scrollable) |
1056
- | `accentColor` | `string` | `'#1890ff'` | Color used for sort icons, selected rows, resize line, etc. |
1057
- | `className` | `string` | `''` | Class name for the outer wrapper |
1058
- | `classNames` | `ClassNamesTypes` | `{}` | Granular class overrides per table region |
1059
- | `styles` | `StylesTypes` | `{}` | Inline style overrides per table region |
1060
- | `icons` | `BoltTableIcons` | — | Custom icon overrides for built-in SVG icons |
1061
- | `gripIcon` | `ReactNode` | — | Custom drag grip icon (deprecated, use `icons.gripVertical`) |
1062
- | `hideGripIcon` | `boolean` | `false` | Hide the drag grip icon from all headers |
1063
- | `pagination` | `PaginationType \| false` | — | Pagination config, or `false` to disable |
1064
- | `onPaginationChange` | `(page, pageSize) => void` | — | Called when page or page size changes |
1065
- | `onColumnResize` | `(columnKey, newWidth) => void` | — | Called when a column is resized |
1066
- | `onColumnOrderChange` | `(newOrder) => void` | — | Called when columns are reordered |
1067
- | `onColumnPin` | `(columnKey, pinned) => void` | — | Called when a column is pinned/unpinned |
1068
- | `onColumnHide` | `(columnKey, hidden) => void` | — | Called when a column is hidden/shown |
1069
- | `rowSelection` | `RowSelectionConfig<T>` | — | Row selection config |
1070
- | `rowPinning` | `RowPinningConfig` | — | Row pinning config (`{ top?: Key[], bottom?: Key[] }`) |
1071
- | `onRowPin` | `(rowKey, pinned) => void` | — | Called when a row is pinned/unpinned via cell context menu |
1072
- | `expandable` | `ExpandableConfig<T>` | — | Expandable row config |
1073
- | `onEndReached` | `() => void` | — | Called when scrolled near the bottom (infinite scroll) |
1074
- | `onEndReachedThreshold` | `number` | `5` | Rows from end to trigger `onEndReached` |
1075
- | `isLoading` | `boolean` | `false` | Shows shimmer skeleton rows when `true` |
1076
- | `onSortChange` | `(columnKey, direction) => void` | — | Server-side sort handler (disables local sort) |
1077
- | `onFilterChange` | `(filters) => void` | — | Server-side filter handler (disables local filter) |
1078
- | `columnContextMenuItems` | `ColumnContextMenuItem[]` | — | Custom items appended to the header context menu |
1079
- | `autoHeight` | `boolean` | `true` | Auto-size table height to content (capped at 10 rows) |
1080
- | `layoutLoading` | `boolean` | `false` | Show full skeleton layout (headers + rows) |
1081
- | `emptyRenderer` | `ReactNode` | — | Custom empty state content |
1082
- | `rowClassName` | `(record, index) => string` | — | Returns a CSS class name for conditional row styling |
1083
- | `rowStyle` | `(record, index) => CSSProperties` | — | Returns inline styles for conditional row styling |
1084
- | `disabledFilters` | `boolean` | `false` | Removes the filter option from all header context menus |
1085
- | `onCopy` | `(text, columnKey, record, rowIndex) => void` | — | Called after a cell value is copied to the clipboard |
1086
- | `keepPinnedRowsAcrossPages` | `boolean` | `false` | Pinned rows remain visible after navigating to a different page |
1087
- | `onEdit` | `(value, record, dataIndex, rowIndex) => void` | — | Called when a user finishes editing an editable cell |
1088
-
1089
- ---
1090
-
1091
- ### `ColumnType<T>`
1092
-
1093
- | Field | Type | Default | Description |
1094
- |-------|------|---------|-------------|
1095
- | `key` | `string` | — | Unique column identifier (required) |
1096
- | `dataIndex` | `string` | — | Row object property to display (required for leaf columns, omit for groups) |
1097
- | `title` | `string \| ReactNode` | — | Header label (required) |
1098
- | `width` | `number` | `150` | Column width in pixels |
1099
- | `render` | `(value, record, index) => ReactNode` | — | Custom cell renderer |
1100
- | `shimmerRender` | `() => ReactNode` | — | Custom shimmer skeleton for this column |
1101
- | `sortable` | `boolean` | `true` | Show sort controls for this column |
1102
- | `sorter` | `boolean \| (a: T, b: T) => number` | — | Custom sort comparator for client-side sort |
1103
- | `filterable` | `boolean` | `true` | Show filter option in context menu |
1104
- | `filterFn` | `(value, record, dataIndex) => boolean` | — | Custom filter predicate for client-side filter |
1105
- | `hidden` | `boolean` | `false` | Hide this column |
1106
- | `pinned` | `'left' \| 'right' \| false` | `false` | Pin this column to an edge |
1107
- | `className` | `string` | — | Class applied to all cells in this column |
1108
- | `style` | `CSSProperties` | — | Styles applied to all cells in this column |
1109
- | `copy` | `boolean \| (value, record, index) => string` | — | Enable "Copy" in cell context menu; function customizes what's copied |
1110
- | `editable` | `boolean` | `false` | When `true` and no custom `render` is set, cells become inline-editable on double-click |
1111
- | `children` | `ColumnType<T>[]` | — | Nested child columns. Makes this column a header group — only leaf columns render data |
1112
-
1113
- ---
1114
-
1115
- ## Examples
1116
-
1117
- ### Sorting
1118
-
1119
- **Client-side** (no `onSortChange` — BoltTable sorts locally):
1120
-
1121
- ```tsx
1122
- const columns: ColumnType<User>[] = [
1123
- {
1124
- key: 'name',
1125
- dataIndex: 'name',
1126
- title: 'Name',
1127
- sortable: true,
1128
- sorter: (a, b) => a.name.localeCompare(b.name),
1129
- },
1130
- {
1131
- key: 'age',
1132
- dataIndex: 'age',
1133
- title: 'Age',
1134
- sortable: true,
1135
- },
1136
- ];
1137
-
1138
- <BoltTable columns={columns} data={data} />
1139
- ```
1140
-
1141
- **Server-side** (provide `onSortChange` — BoltTable delegates to you):
1142
-
1143
- ```tsx
1144
- const [sortKey, setSortKey] = useState('');
1145
- const [sortDir, setSortDir] = useState<SortDirection>(null);
1146
-
1147
- <BoltTable
1148
- columns={columns}
1149
- data={serverData}
1150
- onSortChange={(key, dir) => {
1151
- setSortKey(key);
1152
- setSortDir(dir);
1153
- refetch({ sortKey: key, sortDir: dir });
1154
- }}
1155
- />
1156
- ```
1157
-
1158
- ---
1159
-
1160
- ### Filtering
1161
-
1162
- **Client-side** (no `onFilterChange`):
1163
-
1164
- ```tsx
1165
- const columns: ColumnType<User>[] = [
1166
- {
1167
- key: 'status',
1168
- dataIndex: 'status',
1169
- title: 'Status',
1170
- filterable: true,
1171
- filterFn: (value, record) => record.status === value,
1172
- },
1173
- ];
1174
- ```
1175
-
1176
- **Server-side**:
1177
-
1178
- ```tsx
1179
- <BoltTable
1180
- columns={columns}
1181
- data={serverData}
1182
- onFilterChange={(filters) => {
1183
- setActiveFilters(filters);
1184
- refetch({ filters });
1185
- }}
1186
- />
1187
- ```
1188
-
1189
- ---
1190
-
1191
- ### Pagination
1192
-
1193
- **Client-side** (pass all data, BoltTable slices it):
1194
-
1195
- ```tsx
1196
- <BoltTable
1197
- columns={columns}
1198
- data={allUsers}
1199
- pagination={{ pageSize: 20 }}
1200
- onPaginationChange={(page, size) => setPage(page)}
1201
- />
1202
- ```
1203
-
1204
- **Server-side** (pass only the current page):
1205
-
1206
- ```tsx
1207
- <BoltTable
1208
- columns={columns}
1209
- data={currentPageData}
1210
- pagination={{
1211
- current: page,
1212
- pageSize: 20,
1213
- total: 500,
1214
- showTotal: (total, [from, to]) => `${from}-${to} of ${total} users`,
1215
- }}
1216
- onPaginationChange={(page, size) => fetchPage(page, size)}
1217
- />
1218
- ```
1219
-
1220
- **Disable pagination:**
1221
-
1222
- ```tsx
1223
- <BoltTable columns={columns} data={data} pagination={false} />
1224
- ```
1225
-
1226
- ---
1227
-
1228
- ### Row selection
1229
-
1230
- ```tsx
1231
- const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
1232
-
1233
- <BoltTable
1234
- columns={columns}
1235
- data={data}
1236
- rowKey="id"
1237
- rowSelection={{
1238
- type: 'checkbox',
1239
- selectedRowKeys,
1240
- onChange: (keys, rows) => setSelectedRowKeys(keys),
1241
- getCheckboxProps: (record) => ({
1242
- disabled: record.status === 'locked',
1243
- }),
1244
- }}
1245
- />
1246
- ```
1247
-
1248
- ---
1249
-
1250
- ### Expandable rows
1251
-
1252
- ```tsx
1253
- <BoltTable
1254
- columns={columns}
1255
- data={data}
1256
- rowKey="id"
1257
- expandable={{
1258
- rowExpandable: (record) => record.details !== null,
1259
- expandedRowRender: (record) => (
1260
- <div style={{ padding: 16 }}>
1261
- <h4>{record.name} — Details</h4>
1262
- <pre>{JSON.stringify(record.details, null, 2)}</pre>
1263
- </div>
1264
- ),
1265
- }}
1266
- expandedRowHeight={150}
1267
- maxExpandedRowHeight={400}
1268
- />
1269
- ```
1270
-
1271
- ---
1272
-
1273
- ### Infinite scroll
1274
-
1275
- ```tsx
1276
- const [data, setData] = useState<User[]>([]);
1277
- const [isLoading, setIsLoading] = useState(false);
1278
-
1279
- const loadMore = async () => {
1280
- setIsLoading(true);
1281
- const newRows = await fetchNextPage();
1282
- setData(prev => [...prev, ...newRows]);
1283
- setIsLoading(false);
1284
- };
1285
-
1286
- <BoltTable
1287
- columns={columns}
1288
- data={data}
1289
- isLoading={isLoading}
1290
- onEndReached={loadMore}
1291
- onEndReachedThreshold={8}
1292
- pagination={false}
1293
- />
1294
- ```
1295
-
1296
- ---
1297
-
1298
- ### Column pinning
1299
-
1300
- ```tsx
1301
- const columns: ColumnType<User>[] = [
1302
- { key: 'name', dataIndex: 'name', title: 'Name', pinned: 'left', width: 200 },
1303
- { key: 'email', dataIndex: 'email', title: 'Email', width: 250 },
1304
- { key: 'actions', dataIndex: 'actions', title: 'Actions', pinned: 'right', width: 100 },
1305
- ];
1306
- ```
1307
-
1308
- Users can also pin/unpin columns at runtime via the right-click context menu.
1309
-
1310
- ---
1311
-
1312
- ### Row pinning
1313
-
1314
- Pin rows to the top or bottom of the table so they stay visible while scrolling vertically. Pinned rows transcend pagination — they are always visible regardless of which page the user is on.
1315
-
1316
- ```tsx
1317
- const [rowPinning, setRowPinning] = useState({ top: [], bottom: [] });
1318
-
1319
- <BoltTable
1320
- columns={columns}
1321
- data={data}
1322
- rowKey="id"
1323
- rowPinning={rowPinning}
1324
- onRowPin={(key, pinned) => {
1325
- setRowPinning(prev => {
1326
- const top = (prev.top ?? []).filter(k => String(k) !== String(key));
1327
- const bottom = (prev.bottom ?? []).filter(k => String(k) !== String(key));
1328
- if (pinned === 'top') top.push(key);
1329
- if (pinned === 'bottom') bottom.push(key);
1330
- return { top, bottom };
1331
- });
1332
- }}
1333
- styles={{ pinnedRowBg: 'rgba(255, 255, 255, 0.95)' }}
1334
- />
1335
- ```
1336
-
1337
- Users can also pin/unpin rows at runtime via the right-click context menu on any body cell (when `onRowPin` is provided).
1338
-
1339
- Pinned rows use `position: sticky` with `backdropFilter: blur(12px)` and a subtle box-shadow to visually separate them from scrolling content. Customize with `classNames.pinnedRow`, `styles.pinnedRow`, and `styles.pinnedRowBg`.
1340
-
1341
- ---
1342
-
1343
- ### Cell context menu & copy
1344
-
1345
- Right-click (or long-press on mobile) any body cell to see a context menu with:
1346
-
1347
- - **Pin to Top / Unpin from Top** — shown when `onRowPin` is provided
1348
- - **Pin to Bottom / Unpin from Bottom** — shown when `onRowPin` is provided
1349
- - **Copy** — shown when the column has `copy: true` or a copy function
1350
-
1351
- ```tsx
1352
- const columns: ColumnType<User>[] = [
1353
- {
1354
- key: 'name',
1355
- dataIndex: 'name',
1356
- title: 'Name',
1357
- copy: true, // copies the raw cell value
1358
- },
1359
- {
1360
- key: 'email',
1361
- dataIndex: 'email',
1362
- title: 'Email',
1363
- // Custom copy — control exactly what goes to the clipboard
1364
- copy: (value, record) => `${record.name} <${value}>`,
1365
- },
1366
- ];
1367
- ```
1368
-
1369
- The cell context menu only appears when there is at least one action available (either `onRowPin` or `column.copy`). Otherwise, the browser's default context menu is used.
1370
-
1371
- ---
1372
-
1373
- ### Editable cells
1374
-
1375
- Mark columns as `editable: true` and provide an `onEdit` callback to allow inline editing. Right-click (or long-press on mobile) an editable cell to see the **Edit** option (with a pencil icon) in the context menu. Selecting it turns the cell into an input field. Press **Enter** or click away to commit, **Escape** to cancel.
1376
-
1377
- ```tsx
1378
- const [data, setData] = useState<User[]>(initialData);
1379
-
1380
- const columns: ColumnType<User>[] = [
1381
- { key: 'name', dataIndex: 'name', title: 'Name', editable: true, width: 200 },
1382
- { key: 'age', dataIndex: 'age', title: 'Age', editable: true, width: 80 },
1383
- {
1384
- key: 'status',
1385
- dataIndex: 'status',
1386
- title: 'Status',
1387
- render: (value) => <span className="badge">{String(value)}</span>,
1388
- editable: true, // ignored — custom render takes precedence
1389
- },
1390
- ];
1391
-
1392
- <BoltTable
1393
- columns={columns}
1394
- data={data}
1395
- rowKey="id"
1396
- onEdit={(value, record, dataIndex, rowIndex) => {
1397
- setData(prev =>
1398
- prev.map((row, i) =>
1399
- i === rowIndex ? { ...row, [dataIndex]: value } : row
1400
- )
1401
- );
1402
- }}
1403
- />
1404
- ```
1405
-
1406
- The edit icon can be customized via the `icons` prop:
1407
-
1408
- ```tsx
1409
- <BoltTable icons={{ edit: <MyPencilIcon size={14} /> }} />
1410
- ```
1411
-
1412
- > **Note:** `editable` is skipped for columns that define a custom `render` function — since the cell content is fully controlled by the renderer, inline editing wouldn't know how to display or commit changes. If you need editable custom-rendered cells, handle the editing UX inside your `render` function.
1413
-
1414
- ---
1415
-
1416
- ### Styling overrides
1417
-
1418
- ```tsx
1419
- <BoltTable
1420
- columns={columns}
1421
- data={data}
1422
- accentColor="#6366f1"
1423
- classNames={{
1424
- header: 'text-xs uppercase tracking-wider text-gray-500',
1425
- cell: 'text-sm',
1426
- pinnedHeader: 'border-r border-indigo-200',
1427
- pinnedCell: 'border-r border-indigo-100',
1428
- }}
1429
- styles={{
1430
- header: { fontWeight: 600 },
1431
- rowHover: { backgroundColor: '#f0f9ff' },
1432
- rowSelected: { backgroundColor: '#e0e7ff' },
1433
- pinnedBg: 'rgba(238, 242, 255, 0.95)',
1434
- }}
1435
- />
1436
- ```
1437
-
1438
- ---
1439
-
1440
- ### Nested / grouped columns
1441
-
1442
- Group related columns under a shared header. Parent columns act as header groups — only leaf columns (without `children`) render data cells. Leaf columns within groups support resizing and reordering just like standalone columns.
1443
-
1444
- ```tsx
1445
- const columns: ColumnType<User>[] = [
1446
- { key: 'id', dataIndex: 'id', title: 'ID', width: 80 },
1447
- {
1448
- key: 'nameGroup',
1449
- title: 'Name',
1450
- children: [
1451
- { key: 'firstName', dataIndex: 'firstName', title: 'First Name', width: 150 },
1452
- { key: 'lastName', dataIndex: 'lastName', title: 'Last Name', width: 150 },
1453
- ],
1454
- },
1455
- {
1456
- key: 'contactGroup',
1457
- title: 'Contact Info',
1458
- children: [
1459
- { key: 'email', dataIndex: 'email', title: 'Email', width: 250 },
1460
- { key: 'phone', dataIndex: 'phone', title: 'Phone', width: 150 },
1461
- ],
1462
- },
1463
- { key: 'age', dataIndex: 'age', title: 'Age', width: 80 },
1464
- ];
1465
-
1466
- <BoltTable columns={columns} data={data} rowKey="id" />
1467
- ```
1468
-
1469
- The header renders two rows: group headers span their children columns in the top row, and leaf column headers appear in the bottom row. Standalone columns (not in any group) span both header rows.
1470
-
1471
- ---
1472
-
1473
- ### Pagination styles
1474
-
1475
- Customize every part of the pagination footer via `styles` and `classNames`:
1476
-
1477
- ```tsx
1478
- <BoltTable
1479
- columns={columns}
1480
- data={data}
1481
- pagination={{ pageSize: 20 }}
1482
- classNames={{
1483
- pagination: 'bg-gray-50 border-t border-gray-200',
1484
- paginationButton: 'rounded hover:bg-gray-200',
1485
- paginationActiveButton: 'font-bold text-blue-600',
1486
- paginationSelect: 'rounded border-gray-300',
1487
- paginationInfo: 'text-gray-500 text-xs',
1488
- }}
1489
- styles={{
1490
- pagination: { height: 40 },
1491
- paginationButton: { borderRadius: 4 },
1492
- paginationActiveButton: { fontWeight: 700 },
1493
- paginationSelect: { borderRadius: 4 },
1494
- paginationInfo: { fontSize: 11 },
1495
- }}
1496
- />
1497
- ```
1498
-
1499
- Available pagination style/class keys: `pagination` (wrapper), `paginationButton` (nav buttons), `paginationActiveButton` (active page), `paginationSelect` (page-size dropdown), `paginationInfo` (range text).
1500
-
1501
- ---
1502
-
1503
- ### Fixed height (fill parent)
1504
-
1505
- By default, BoltTable auto-sizes to its content. To fill a fixed-height container instead:
1506
-
1507
- ```tsx
1508
- <div style={{ height: 600 }}>
1509
- <BoltTable
1510
- columns={columns}
1511
- data={data}
1512
- autoHeight={false}
1513
- />
1514
- </div>
1515
- ```
1516
-
1517
- ---
1518
-
1519
91
  ## Documentation
1520
92
 
1521
- For the complete guide with in-depth examples for every feature, visit the **[BoltTable Documentation](https://bolt-table.vercel.app/)**.
93
+ For the complete guide with interactive examples, props reference, and API docs visit **[bolt-table.vercel.app](https://bolt-table.vercel.app/)**.
1522
94
 
1523
95
  ---
1524
96
 
@@ -1528,6 +100,8 @@ For the complete guide with in-depth examples for every feature, visit the **[Bo
1528
100
  import type {
1529
101
  ColumnType,
1530
102
  ColumnContextMenuItem,
103
+ CellContextMenuItem,
104
+ ColumnPersistenceConfig,
1531
105
  RowSelectionConfig,
1532
106
  RowPinningConfig,
1533
107
  ExpandableConfig,
@@ -1535,6 +109,16 @@ import type {
1535
109
  SortDirection,
1536
110
  DataRecord,
1537
111
  BoltTableIcons,
112
+ ConditionalFormatRule,
113
+ RowGroupingConfig,
114
+ AggregateFunction,
115
+ TreeDataConfig,
116
+ ClassNamesTypes,
117
+ StylesTypes,
118
+ AIResponse,
119
+ AIOperation,
120
+ BoltTableAIConfig,
121
+ BoltTableConfig,
1538
122
  } from 'bolt-table';
1539
123
  ```
1540
124