bolt-table 0.1.39 → 0.1.41

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,8 +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
45
  ---
54
46
 
55
47
  ## Quick start
@@ -87,11 +79,7 @@ export default function App() {
87
79
  }
88
80
  ```
89
81
 
90
- ---
91
-
92
- ## Next.js (App Router)
93
-
94
- BoltTable uses browser APIs and must be wrapped in a client boundary:
82
+ ### Next.js (App Router)
95
83
 
96
84
  ```tsx
97
85
  'use client';
@@ -100,660 +88,9 @@ import { BoltTable } from 'bolt-table';
100
88
 
101
89
  ---
102
90
 
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
-
185
- ---
186
-
187
- ### `ColumnType<T>`
188
-
189
- | Field | Type | Default | Description |
190
- |-------|------|---------|-------------|
191
- | `key` | `string` | — | Unique column identifier (required) |
192
- | `dataIndex` | `string` | — | Row object property to display (required for leaf columns, omit for groups) |
193
- | `title` | `string \| ReactNode` | — | Header label (required) |
194
- | `width` | `number` | `150` | Column width in pixels |
195
- | `render` | `(value, record, index) => ReactNode` | — | Custom cell renderer |
196
- | `shimmerRender` | `() => ReactNode` | — | Custom shimmer skeleton for this column |
197
- | `sortable` | `boolean` | `true` | Show sort controls for this column |
198
- | `sorter` | `boolean \| (a: T, b: T) => number` | — | Custom sort comparator for client-side sort |
199
- | `filterable` | `boolean` | `true` | Show filter option in context menu |
200
- | `filterFn` | `(value, record, dataIndex) => boolean` | — | Custom filter predicate for client-side filter |
201
- | `hidden` | `boolean` | `false` | Hide this column |
202
- | `defaultHidden` | `boolean` | `false` | Hide this column on first render (uncontrolled) |
203
- | `pinned` | `'left' \| 'right' \| false` | `false` | Pin this column to an edge |
204
- | `defaultPinned` | `'left' \| 'right' \| false` | `false` | Pin this column on first render (uncontrolled) |
205
- | `className` | `string` | — | Class applied to all cells in this column |
206
- | `style` | `CSSProperties` | — | Styles applied to all cells in this column |
207
- | `copy` | `boolean \| (value, record, index) => string` | — | Enable "Copy" in cell context menu; function customizes what's copied |
208
- | `editable` | `boolean` | `false` | Cells become inline-editable (no custom `render` required) |
209
- | `children` | `ColumnType<T>[]` | — | Nested child columns. Makes this column a header group — only leaf columns render data |
210
-
211
- ---
212
-
213
- ### `ColumnPersistenceConfig`
214
-
215
- | Field | Type | Default | Description |
216
- |-------|------|---------|-------------|
217
- | `storageKey` | `string` | — | localStorage key prefix used to store the state (required) |
218
- | `persistOrder` | `boolean` | `true` | Persist and restore column order |
219
- | `persistWidths` | `boolean` | `true` | Persist and restore column widths |
220
- | `persistVisibility` | `boolean` | `true` | Persist and restore hidden/visible state |
221
- | `persistPinned` | `boolean` | `true` | Persist and restore column pinned state |
222
-
223
- ---
224
-
225
- ## Examples
226
-
227
- ### Global search
228
-
229
- BoltTable renders a search bar above the table by default. It searches across **all columns** of every row using a case-insensitive substring match.
230
-
231
- ```tsx
232
- // Uncontrolled — BoltTable manages the search value internally
233
- <BoltTable columns={columns} data={data} />
234
-
235
- // Hide it entirely
236
- <BoltTable columns={columns} data={data} hideGlobalSearch />
237
-
238
- // Controlled — drive the search value from outside
239
- const [search, setSearch] = useState('');
240
-
241
- <BoltTable
242
- columns={columns}
243
- data={data}
244
- globalSearchValue={search}
245
- onGlobalSearchChange={setSearch}
246
- />
247
- ```
248
-
249
- ---
250
-
251
- ### Column picker
252
-
253
- 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.
254
-
255
- ```tsx
256
- // Shown by default — no configuration needed
257
- <BoltTable columns={columns} data={data} />
258
-
259
- // Hide the column picker button
260
- <BoltTable columns={columns} data={data} showColumnSettings={false} />
261
- ```
262
-
263
- > **Note:** Pinned columns cannot be hidden from the picker — unpin them first.
264
-
265
- ---
266
-
267
- ### Column persistence
268
-
269
- 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.
270
-
271
- ```tsx
272
- <BoltTable
273
- columns={columns}
274
- data={data}
275
- rowKey="id"
276
- columnPersistence={{
277
- storageKey: 'users-table', // stored as bt_users-table in localStorage
278
- persistOrder: true, // default
279
- persistWidths: true, // default
280
- persistVisibility: true, // default
281
- persistPinned: true, // default
282
- }}
283
- />
284
- ```
285
-
286
- To persist only widths (not order or visibility):
287
-
288
- ```tsx
289
- <BoltTable
290
- columnPersistence={{
291
- storageKey: 'orders-table',
292
- persistOrder: false,
293
- persistVisibility: false,
294
- persistPinned: false,
295
- }}
296
- />
297
- ```
298
-
299
- ---
300
-
301
- ### Horizontal virtualization
302
-
303
- 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.
304
-
305
- ```tsx
306
- <BoltTable
307
- columns={hundredsOfColumns}
308
- data={data}
309
- enableColumnVirtualization
310
- />
311
- ```
312
-
313
- > Best suited for tables with 50+ columns. For typical column counts (< 30) the overhead isn't worth it.
314
-
315
- ---
316
-
317
- ### Dynamic row heights
318
-
319
- 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.
320
-
321
- ```tsx
322
- <BoltTable
323
- columns={columns}
324
- data={data}
325
- enableDynamicRowHeight
326
- rowHeight={40} // used as the minimum / estimated height
327
- />
328
- ```
329
-
330
- The `rowHeight` prop still acts as the minimum and estimated height used before measurement. Rows grow taller as needed based on their content.
331
-
332
- ---
333
-
334
- ### Duplicate row keys
335
-
336
- 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.
337
-
338
- ```tsx
339
- // Works correctly even with duplicate ids
340
- <BoltTable
341
- columns={columns}
342
- data={[
343
- { id: 1, name: 'Alice' },
344
- { id: 1, name: 'Alice (copy)' }, // same id — renders correctly
345
- ]}
346
- rowKey="id"
347
- />
348
- ```
349
-
350
- ---
351
-
352
- ### Sorting
353
-
354
- **Client-side** (no `onSortChange` — BoltTable sorts locally):
355
-
356
- ```tsx
357
- const columns: ColumnType<User>[] = [
358
- {
359
- key: 'name',
360
- dataIndex: 'name',
361
- title: 'Name',
362
- sortable: true,
363
- sorter: (a, b) => a.name.localeCompare(b.name),
364
- },
365
- {
366
- key: 'age',
367
- dataIndex: 'age',
368
- title: 'Age',
369
- sortable: true,
370
- },
371
- ];
372
-
373
- <BoltTable columns={columns} data={data} />
374
- ```
375
-
376
- **Server-side** (provide `onSortChange` — BoltTable delegates to you):
377
-
378
- ```tsx
379
- const [sortKey, setSortKey] = useState('');
380
- const [sortDir, setSortDir] = useState<SortDirection>(null);
381
-
382
- <BoltTable
383
- columns={columns}
384
- data={serverData}
385
- onSortChange={(key, dir) => {
386
- setSortKey(key);
387
- setSortDir(dir);
388
- refetch({ sortKey: key, sortDir: dir });
389
- }}
390
- />
391
- ```
392
-
393
- ---
394
-
395
- ### Filtering
396
-
397
- **Client-side** (no `onFilterChange`):
398
-
399
- ```tsx
400
- const columns: ColumnType<User>[] = [
401
- {
402
- key: 'status',
403
- dataIndex: 'status',
404
- title: 'Status',
405
- filterable: true,
406
- filterFn: (value, record) => record.status === value,
407
- },
408
- ];
409
- ```
410
-
411
- **Server-side**:
412
-
413
- ```tsx
414
- <BoltTable
415
- columns={columns}
416
- data={serverData}
417
- onFilterChange={(filters) => {
418
- setActiveFilters(filters);
419
- refetch({ filters });
420
- }}
421
- />
422
- ```
423
-
424
- ---
425
-
426
- ### Pagination
427
-
428
- **Client-side** (pass all data, BoltTable slices it):
429
-
430
- ```tsx
431
- <BoltTable
432
- columns={columns}
433
- data={allUsers}
434
- pagination={{ pageSize: 20 }}
435
- onPaginationChange={(page, size) => setPage(page)}
436
- />
437
- ```
438
-
439
- **Server-side** (pass only the current page):
440
-
441
- ```tsx
442
- <BoltTable
443
- columns={columns}
444
- data={currentPageData}
445
- pagination={{
446
- current: page,
447
- pageSize: 20,
448
- total: 500,
449
- showTotal: (total, [from, to]) => `${from}-${to} of ${total} users`,
450
- }}
451
- onPaginationChange={(page, size) => fetchPage(page, size)}
452
- />
453
- ```
454
-
455
- **Disable pagination:**
456
-
457
- ```tsx
458
- <BoltTable columns={columns} data={data} pagination={false} />
459
- ```
460
-
461
- ---
462
-
463
- ### Row selection
464
-
465
- ```tsx
466
- const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
467
-
468
- <BoltTable
469
- columns={columns}
470
- data={data}
471
- rowKey="id"
472
- rowSelection={{
473
- type: 'checkbox',
474
- selectedRowKeys,
475
- onChange: (keys, rows) => setSelectedRowKeys(keys),
476
- getCheckboxProps: (record) => ({
477
- disabled: record.status === 'locked',
478
- }),
479
- }}
480
- />
481
- ```
482
-
483
- ---
484
-
485
- ### Expandable rows
486
-
487
- ```tsx
488
- <BoltTable
489
- columns={columns}
490
- data={data}
491
- rowKey="id"
492
- expandable={{
493
- rowExpandable: (record) => record.details !== null,
494
- expandedRowRender: (record) => (
495
- <div style={{ padding: 16 }}>
496
- <h4>{record.name} — Details</h4>
497
- <pre>{JSON.stringify(record.details, null, 2)}</pre>
498
- </div>
499
- ),
500
- }}
501
- expandedRowHeight={150}
502
- maxExpandedRowHeight={400}
503
- />
504
- ```
505
-
506
- ---
507
-
508
- ### Infinite scroll
509
-
510
- ```tsx
511
- const [data, setData] = useState<User[]>([]);
512
- const [isLoading, setIsLoading] = useState(false);
513
-
514
- const loadMore = async () => {
515
- setIsLoading(true);
516
- const newRows = await fetchNextPage();
517
- setData(prev => [...prev, ...newRows]);
518
- setIsLoading(false);
519
- };
520
-
521
- <BoltTable
522
- columns={columns}
523
- data={data}
524
- isLoading={isLoading}
525
- onEndReached={loadMore}
526
- onEndReachedThreshold={8}
527
- pagination={false}
528
- />
529
- ```
530
-
531
- ---
532
-
533
- ### Column pinning
534
-
535
- ```tsx
536
- const columns: ColumnType<User>[] = [
537
- { key: 'name', dataIndex: 'name', title: 'Name', pinned: 'left', width: 200 },
538
- { key: 'email', dataIndex: 'email', title: 'Email', width: 250 },
539
- { key: 'actions', dataIndex: 'actions', title: 'Actions', pinned: 'right', width: 100 },
540
- ];
541
- ```
542
-
543
- Users can also pin/unpin columns at runtime via the right-click context menu.
544
-
545
- ---
546
-
547
- ### Row pinning
548
-
549
- 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.
550
-
551
- ```tsx
552
- const [rowPinning, setRowPinning] = useState({ top: [], bottom: [] });
553
-
554
- <BoltTable
555
- columns={columns}
556
- data={data}
557
- rowKey="id"
558
- rowPinning={rowPinning}
559
- onRowPin={(key, pinned) => {
560
- setRowPinning(prev => {
561
- const top = (prev.top ?? []).filter(k => String(k) !== String(key));
562
- const bottom = (prev.bottom ?? []).filter(k => String(k) !== String(key));
563
- if (pinned === 'top') top.push(key);
564
- if (pinned === 'bottom') bottom.push(key);
565
- return { top, bottom };
566
- });
567
- }}
568
- styles={{ pinnedRowBg: 'rgba(255, 255, 255, 0.95)' }}
569
- />
570
- ```
571
-
572
- Users can also pin/unpin rows at runtime via the right-click context menu on any body cell (when `onRowPin` is provided).
573
-
574
- 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`.
575
-
576
- ---
577
-
578
- ### Cell context menu & copy
579
-
580
- Right-click (or long-press on mobile) any body cell to see a context menu with:
581
-
582
- - **Pin to Top / Unpin from Top** — shown when `onRowPin` is provided
583
- - **Pin to Bottom / Unpin from Bottom** — shown when `onRowPin` is provided
584
- - **Copy** — shown when the column has `copy: true` or a copy function
585
-
586
- ```tsx
587
- const columns: ColumnType<User>[] = [
588
- {
589
- key: 'name',
590
- dataIndex: 'name',
591
- title: 'Name',
592
- copy: true, // copies the raw cell value
593
- },
594
- {
595
- key: 'email',
596
- dataIndex: 'email',
597
- title: 'Email',
598
- // Custom copy — control exactly what goes to the clipboard
599
- copy: (value, record) => `${record.name} <${value}>`,
600
- },
601
- ];
602
- ```
603
-
604
- 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.
605
-
606
- ---
607
-
608
- ### Editable cells
609
-
610
- 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.
611
-
612
- ```tsx
613
- const [data, setData] = useState<User[]>(initialData);
614
-
615
- const columns: ColumnType<User>[] = [
616
- { key: 'name', dataIndex: 'name', title: 'Name', editable: true, width: 200 },
617
- { key: 'age', dataIndex: 'age', title: 'Age', editable: true, width: 80 },
618
- {
619
- key: 'status',
620
- dataIndex: 'status',
621
- title: 'Status',
622
- render: (value) => <span className="badge">{String(value)}</span>,
623
- editable: true, // ignored — custom render takes precedence
624
- },
625
- ];
626
-
627
- <BoltTable
628
- columns={columns}
629
- data={data}
630
- rowKey="id"
631
- onEdit={(value, record, dataIndex, rowIndex) => {
632
- setData(prev =>
633
- prev.map((row, i) =>
634
- i === rowIndex ? { ...row, [dataIndex]: value } : row
635
- )
636
- );
637
- }}
638
- />
639
- ```
640
-
641
- The edit icon can be customized via the `icons` prop:
642
-
643
- ```tsx
644
- <BoltTable icons={{ edit: <MyPencilIcon size={14} /> }} />
645
- ```
646
-
647
- > **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.
648
-
649
- ---
650
-
651
- ### Styling overrides
652
-
653
- ```tsx
654
- <BoltTable
655
- columns={columns}
656
- data={data}
657
- accentColor="#6366f1"
658
- classNames={{
659
- header: 'text-xs uppercase tracking-wider text-gray-500',
660
- cell: 'text-sm',
661
- pinnedHeader: 'border-r border-indigo-200',
662
- pinnedCell: 'border-r border-indigo-100',
663
- }}
664
- styles={{
665
- header: { fontWeight: 600 },
666
- rowHover: { backgroundColor: '#f0f9ff' },
667
- rowSelected: { backgroundColor: '#e0e7ff' },
668
- pinnedBg: 'rgba(238, 242, 255, 0.95)',
669
- }}
670
- />
671
- ```
672
-
673
- ---
674
-
675
- ### Nested / grouped columns
676
-
677
- 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.
678
-
679
- ```tsx
680
- const columns: ColumnType<User>[] = [
681
- { key: 'id', dataIndex: 'id', title: 'ID', width: 80 },
682
- {
683
- key: 'nameGroup',
684
- title: 'Name',
685
- children: [
686
- { key: 'firstName', dataIndex: 'firstName', title: 'First Name', width: 150 },
687
- { key: 'lastName', dataIndex: 'lastName', title: 'Last Name', width: 150 },
688
- ],
689
- },
690
- {
691
- key: 'contactGroup',
692
- title: 'Contact Info',
693
- children: [
694
- { key: 'email', dataIndex: 'email', title: 'Email', width: 250 },
695
- { key: 'phone', dataIndex: 'phone', title: 'Phone', width: 150 },
696
- ],
697
- },
698
- { key: 'age', dataIndex: 'age', title: 'Age', width: 80 },
699
- ];
700
-
701
- <BoltTable columns={columns} data={data} rowKey="id" />
702
- ```
703
-
704
- 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.
705
-
706
- ---
707
-
708
- ### Pagination styles
709
-
710
- Customize every part of the pagination footer via `styles` and `classNames`:
711
-
712
- ```tsx
713
- <BoltTable
714
- columns={columns}
715
- data={data}
716
- pagination={{ pageSize: 20 }}
717
- classNames={{
718
- pagination: 'bg-gray-50 border-t border-gray-200',
719
- paginationButton: 'rounded hover:bg-gray-200',
720
- paginationActiveButton: 'font-bold text-blue-600',
721
- paginationSelect: 'rounded border-gray-300',
722
- paginationInfo: 'text-gray-500 text-xs',
723
- }}
724
- styles={{
725
- pagination: { height: 40 },
726
- paginationButton: { borderRadius: 4 },
727
- paginationActiveButton: { fontWeight: 700 },
728
- paginationSelect: { borderRadius: 4 },
729
- paginationInfo: { fontSize: 11 },
730
- }}
731
- />
732
- ```
733
-
734
- Available pagination style/class keys: `pagination` (wrapper), `paginationButton` (nav buttons), `paginationActiveButton` (active page), `paginationSelect` (page-size dropdown), `paginationInfo` (range text).
735
-
736
- ---
737
-
738
- ### Fixed height (fill parent)
739
-
740
- By default, BoltTable auto-sizes to its content. To fill a fixed-height container instead:
741
-
742
- ```tsx
743
- <div style={{ height: 600 }}>
744
- <BoltTable
745
- columns={columns}
746
- data={data}
747
- autoHeight={false}
748
- />
749
- </div>
750
- ```
751
-
752
- ---
753
-
754
91
  ## Documentation
755
92
 
756
- 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/)**.
757
94
 
758
95
  ---
759
96
 
@@ -763,6 +100,7 @@ For the complete guide with in-depth examples for every feature, visit the **[Bo
763
100
  import type {
764
101
  ColumnType,
765
102
  ColumnContextMenuItem,
103
+ CellContextMenuItem,
766
104
  ColumnPersistenceConfig,
767
105
  RowSelectionConfig,
768
106
  RowPinningConfig,
@@ -771,628 +109,16 @@ import type {
771
109
  SortDirection,
772
110
  DataRecord,
773
111
  BoltTableIcons,
774
- } from 'bolt-table';
775
- ```
776
-
777
- ---
778
-
779
- ## License
780
-
781
- MIT © [Venkatesh Sirigineedi](https://github.com/venkateshwebdev)
782
-
783
-
784
- [![npm version](https://img.shields.io/npm/v/bolt-table)](https://www.npmjs.com/package/bolt-table)
785
- [![license](https://img.shields.io/npm/l/bolt-table)](./LICENSE)
786
- [![github](https://img.shields.io/badge/GitHub-Source-181717?logo=github)](https://github.com/venkateshwebdev/Bolt-Table)
787
- [![website](https://img.shields.io/badge/Website-Live_Demo-blue?logo=vercel)](https://bolt-table.vercel.app/)
788
-
789
- ---
790
-
791
- ## Features
792
-
793
- - **Row virtualization** — only visible rows are rendered, powered by TanStack Virtual
794
- - **Drag to reorder columns** — custom zero-dependency drag-and-drop (no @dnd-kit needed)
795
- - **Column pinning** — pin columns to the left or right edge via right-click
796
- - **Column resizing** — drag the right edge of any header to resize
797
- - **Column hiding** — hide/show columns via the right-click context menu
798
- - **Sorting** — client-side or server-side, with custom comparators per column
799
- - **Filtering** — client-side or server-side, with custom filter functions per column
800
- - **Pagination** — client-side slice or server-side with full control
801
- - **Row selection** — checkbox or radio, with select-all, indeterminate state, and disabled rows
802
- - **Expandable rows** — auto-measured content panels below each row, controlled or uncontrolled
803
- - **Shimmer loading** — animated skeleton rows on initial load and infinite scroll append
804
- - **Infinite scroll** — `onEndReached` callback with configurable threshold
805
- - **Empty state** — custom renderer or default "No data" message
806
- - **Auto height** — table shrinks/grows to fit rows, capped at 10 rows by default
807
- - **Row pinning** — pin rows to the top or bottom of the table, sticky during vertical scroll
808
- - **Cell context menu** — right-click (or long-press on mobile) any cell to pin rows or copy values
809
- - **Right-click context menu** — sort, filter, pin, hide, plus custom items
810
- - **Mobile-friendly context menus** — long-press (touch-and-hold) triggers context menus on touch devices
811
- - **Nested / grouped columns** — group related columns under a shared header spanning multiple columns
812
- - **Theme-agnostic** — works in light and dark mode out of the box, no CSS variables needed
813
- - **Editable cells** — right-click any cell on an `editable` column to inline-edit via the context menu
814
- - **Custom icons** — override any built-in icon via the `icons` prop
815
-
816
- ---
817
-
818
- ## Installation
819
-
820
- ```bash
821
- npm install bolt-table @tanstack/react-virtual
822
- ```
823
-
824
- That's it. No other peer dependencies.
825
-
826
- ---
827
-
828
- ## Quick start
829
-
830
- ```tsx
831
- import { BoltTable, ColumnType } from 'bolt-table';
832
-
833
- interface User {
834
- id: string;
835
- name: string;
836
- email: string;
837
- age: number;
838
- }
839
-
840
- const columns: ColumnType<User>[] = [
841
- { key: 'name', dataIndex: 'name', title: 'Name', width: 200 },
842
- { key: 'email', dataIndex: 'email', title: 'Email', width: 280 },
843
- { key: 'age', dataIndex: 'age', title: 'Age', width: 80 },
844
- ];
845
-
846
- const data: User[] = [
847
- { id: '1', name: 'Alice', email: 'alice@example.com', age: 28 },
848
- { id: '2', name: 'Bob', email: 'bob@example.com', age: 34 },
849
- { id: '3', name: 'Charlie', email: 'charlie@example.com', age: 22 },
850
- ];
851
-
852
- export default function App() {
853
- return (
854
- <BoltTable<User>
855
- columns={columns}
856
- data={data}
857
- rowKey="id"
858
- />
859
- );
860
- }
861
- ```
862
-
863
- ---
864
-
865
- ## Next.js (App Router)
866
-
867
- BoltTable uses browser APIs and must be wrapped in a client boundary:
868
-
869
- ```tsx
870
- 'use client';
871
- import { BoltTable } from 'bolt-table';
872
- ```
873
-
874
- ---
875
-
876
- ## Styling
877
-
878
- 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.
879
-
880
- 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.
881
-
882
- ### Custom icons
883
-
884
- All built-in icons are inline SVGs. Override any icon via the `icons` prop:
885
-
886
- ```tsx
887
- import type { BoltTableIcons } from 'bolt-table';
888
-
889
- <BoltTable
890
- icons={{
891
- gripVertical: <MyGripIcon size={12} />,
892
- sortAsc: <MySortUpIcon size={12} />,
893
- chevronsLeft: <MyFirstPageIcon size={12} />,
894
- }}
895
- />
896
- ```
897
-
898
- Available icon keys: `gripVertical`, `sortAsc`, `sortDesc`, `filter`, `filterClear`, `pin`, `pinOff`, `eyeOff`, `chevronDown`, `chevronLeft`, `chevronRight`, `chevronsLeft`, `chevronsRight`, `copy`, `edit`.
899
-
900
- ---
901
-
902
- ## Props
903
-
904
- ### `BoltTable`
905
-
906
- | Prop | Type | Default | Description |
907
- |------|------|---------|-------------|
908
- | `columns` | `ColumnType<T>[]` | — | Column definitions (required) |
909
- | `data` | `T[]` | — | Row data array (required) |
910
- | `rowKey` | `string \| (record: T) => string` | `'id'` | Unique row identifier |
911
- | `rowHeight` | `number` | `40` | Height of each row in pixels |
912
- | `expandedRowHeight` | `number` | `200` | Estimated height for expanded rows |
913
- | `maxExpandedRowHeight` | `number` | — | Max height for expanded row panels (makes them scrollable) |
914
- | `accentColor` | `string` | `'#1890ff'` | Color used for sort icons, selected rows, resize line, etc. |
915
- | `className` | `string` | `''` | Class name for the outer wrapper |
916
- | `classNames` | `ClassNamesTypes` | `{}` | Granular class overrides per table region |
917
- | `styles` | `StylesTypes` | `{}` | Inline style overrides per table region |
918
- | `icons` | `BoltTableIcons` | — | Custom icon overrides for built-in SVG icons |
919
- | `gripIcon` | `ReactNode` | — | Custom drag grip icon (deprecated, use `icons.gripVertical`) |
920
- | `hideGripIcon` | `boolean` | `false` | Hide the drag grip icon from all headers |
921
- | `pagination` | `PaginationType \| false` | — | Pagination config, or `false` to disable |
922
- | `onPaginationChange` | `(page, pageSize) => void` | — | Called when page or page size changes |
923
- | `onColumnResize` | `(columnKey, newWidth) => void` | — | Called when a column is resized |
924
- | `onColumnOrderChange` | `(newOrder) => void` | — | Called when columns are reordered |
925
- | `onColumnPin` | `(columnKey, pinned) => void` | — | Called when a column is pinned/unpinned |
926
- | `onColumnHide` | `(columnKey, hidden) => void` | — | Called when a column is hidden/shown |
927
- | `rowSelection` | `RowSelectionConfig<T>` | — | Row selection config |
928
- | `rowPinning` | `RowPinningConfig` | — | Row pinning config (`{ top?: Key[], bottom?: Key[] }`) |
929
- | `onRowPin` | `(rowKey, pinned) => void` | — | Called when a row is pinned/unpinned via cell context menu |
930
- | `expandable` | `ExpandableConfig<T>` | — | Expandable row config |
931
- | `onEndReached` | `() => void` | — | Called when scrolled near the bottom (infinite scroll) |
932
- | `onEndReachedThreshold` | `number` | `5` | Rows from end to trigger `onEndReached` |
933
- | `isLoading` | `boolean` | `false` | Shows shimmer skeleton rows when `true` |
934
- | `onSortChange` | `(columnKey, direction) => void` | — | Server-side sort handler (disables local sort) |
935
- | `onFilterChange` | `(filters) => void` | — | Server-side filter handler (disables local filter) |
936
- | `columnContextMenuItems` | `ColumnContextMenuItem[]` | — | Custom items appended to the header context menu |
937
- | `autoHeight` | `boolean` | `true` | Auto-size table height to content (capped at 10 rows) |
938
- | `layoutLoading` | `boolean` | `false` | Show full skeleton layout (headers + rows) |
939
- | `emptyRenderer` | `ReactNode` | — | Custom empty state content |
940
- | `rowClassName` | `(record, index) => string` | — | Returns a CSS class name for conditional row styling |
941
- | `rowStyle` | `(record, index) => CSSProperties` | — | Returns inline styles for conditional row styling |
942
- | `disabledFilters` | `boolean` | `false` | Removes the filter option from all header context menus |
943
- | `onCopy` | `(text, columnKey, record, rowIndex) => void` | — | Called after a cell value is copied to the clipboard |
944
- | `keepPinnedRowsAcrossPages` | `boolean` | `false` | Pinned rows remain visible after navigating to a different page |
945
- | `onEdit` | `(value, record, dataIndex, rowIndex) => void` | — | Called when a user finishes editing an editable cell |
946
-
947
- ---
948
-
949
- ### `ColumnType<T>`
950
-
951
- | Field | Type | Default | Description |
952
- |-------|------|---------|-------------|
953
- | `key` | `string` | — | Unique column identifier (required) |
954
- | `dataIndex` | `string` | — | Row object property to display (required for leaf columns, omit for groups) |
955
- | `title` | `string \| ReactNode` | — | Header label (required) |
956
- | `width` | `number` | `150` | Column width in pixels |
957
- | `render` | `(value, record, index) => ReactNode` | — | Custom cell renderer |
958
- | `shimmerRender` | `() => ReactNode` | — | Custom shimmer skeleton for this column |
959
- | `sortable` | `boolean` | `true` | Show sort controls for this column |
960
- | `sorter` | `boolean \| (a: T, b: T) => number` | — | Custom sort comparator for client-side sort |
961
- | `filterable` | `boolean` | `true` | Show filter option in context menu |
962
- | `filterFn` | `(value, record, dataIndex) => boolean` | — | Custom filter predicate for client-side filter |
963
- | `hidden` | `boolean` | `false` | Hide this column |
964
- | `pinned` | `'left' \| 'right' \| false` | `false` | Pin this column to an edge |
965
- | `className` | `string` | — | Class applied to all cells in this column |
966
- | `style` | `CSSProperties` | — | Styles applied to all cells in this column |
967
- | `copy` | `boolean \| (value, record, index) => string` | — | Enable "Copy" in cell context menu; function customizes what's copied |
968
- | `editable` | `boolean` | `false` | When `true` and no custom `render` is set, cells become inline-editable on double-click |
969
- | `children` | `ColumnType<T>[]` | — | Nested child columns. Makes this column a header group — only leaf columns render data |
970
-
971
- ---
972
-
973
- ## Examples
974
-
975
- ### Sorting
976
-
977
- **Client-side** (no `onSortChange` — BoltTable sorts locally):
978
-
979
- ```tsx
980
- const columns: ColumnType<User>[] = [
981
- {
982
- key: 'name',
983
- dataIndex: 'name',
984
- title: 'Name',
985
- sortable: true,
986
- sorter: (a, b) => a.name.localeCompare(b.name),
987
- },
988
- {
989
- key: 'age',
990
- dataIndex: 'age',
991
- title: 'Age',
992
- sortable: true,
993
- },
994
- ];
995
-
996
- <BoltTable columns={columns} data={data} />
997
- ```
998
-
999
- **Server-side** (provide `onSortChange` — BoltTable delegates to you):
1000
-
1001
- ```tsx
1002
- const [sortKey, setSortKey] = useState('');
1003
- const [sortDir, setSortDir] = useState<SortDirection>(null);
1004
-
1005
- <BoltTable
1006
- columns={columns}
1007
- data={serverData}
1008
- onSortChange={(key, dir) => {
1009
- setSortKey(key);
1010
- setSortDir(dir);
1011
- refetch({ sortKey: key, sortDir: dir });
1012
- }}
1013
- />
1014
- ```
1015
-
1016
- ---
1017
-
1018
- ### Filtering
1019
-
1020
- **Client-side** (no `onFilterChange`):
1021
-
1022
- ```tsx
1023
- const columns: ColumnType<User>[] = [
1024
- {
1025
- key: 'status',
1026
- dataIndex: 'status',
1027
- title: 'Status',
1028
- filterable: true,
1029
- filterFn: (value, record) => record.status === value,
1030
- },
1031
- ];
1032
- ```
1033
-
1034
- **Server-side**:
1035
-
1036
- ```tsx
1037
- <BoltTable
1038
- columns={columns}
1039
- data={serverData}
1040
- onFilterChange={(filters) => {
1041
- setActiveFilters(filters);
1042
- refetch({ filters });
1043
- }}
1044
- />
1045
- ```
1046
-
1047
- ---
1048
-
1049
- ### Pagination
1050
-
1051
- **Client-side** (pass all data, BoltTable slices it):
1052
-
1053
- ```tsx
1054
- <BoltTable
1055
- columns={columns}
1056
- data={allUsers}
1057
- pagination={{ pageSize: 20 }}
1058
- onPaginationChange={(page, size) => setPage(page)}
1059
- />
1060
- ```
1061
-
1062
- **Server-side** (pass only the current page):
1063
-
1064
- ```tsx
1065
- <BoltTable
1066
- columns={columns}
1067
- data={currentPageData}
1068
- pagination={{
1069
- current: page,
1070
- pageSize: 20,
1071
- total: 500,
1072
- showTotal: (total, [from, to]) => `${from}-${to} of ${total} users`,
1073
- }}
1074
- onPaginationChange={(page, size) => fetchPage(page, size)}
1075
- />
1076
- ```
1077
-
1078
- **Disable pagination:**
1079
-
1080
- ```tsx
1081
- <BoltTable columns={columns} data={data} pagination={false} />
1082
- ```
1083
-
1084
- ---
1085
-
1086
- ### Row selection
1087
-
1088
- ```tsx
1089
- const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
1090
-
1091
- <BoltTable
1092
- columns={columns}
1093
- data={data}
1094
- rowKey="id"
1095
- rowSelection={{
1096
- type: 'checkbox',
1097
- selectedRowKeys,
1098
- onChange: (keys, rows) => setSelectedRowKeys(keys),
1099
- getCheckboxProps: (record) => ({
1100
- disabled: record.status === 'locked',
1101
- }),
1102
- }}
1103
- />
1104
- ```
1105
-
1106
- ---
1107
-
1108
- ### Expandable rows
1109
-
1110
- ```tsx
1111
- <BoltTable
1112
- columns={columns}
1113
- data={data}
1114
- rowKey="id"
1115
- expandable={{
1116
- rowExpandable: (record) => record.details !== null,
1117
- expandedRowRender: (record) => (
1118
- <div style={{ padding: 16 }}>
1119
- <h4>{record.name} — Details</h4>
1120
- <pre>{JSON.stringify(record.details, null, 2)}</pre>
1121
- </div>
1122
- ),
1123
- }}
1124
- expandedRowHeight={150}
1125
- maxExpandedRowHeight={400}
1126
- />
1127
- ```
1128
-
1129
- ---
1130
-
1131
- ### Infinite scroll
1132
-
1133
- ```tsx
1134
- const [data, setData] = useState<User[]>([]);
1135
- const [isLoading, setIsLoading] = useState(false);
1136
-
1137
- const loadMore = async () => {
1138
- setIsLoading(true);
1139
- const newRows = await fetchNextPage();
1140
- setData(prev => [...prev, ...newRows]);
1141
- setIsLoading(false);
1142
- };
1143
-
1144
- <BoltTable
1145
- columns={columns}
1146
- data={data}
1147
- isLoading={isLoading}
1148
- onEndReached={loadMore}
1149
- onEndReachedThreshold={8}
1150
- pagination={false}
1151
- />
1152
- ```
1153
-
1154
- ---
1155
-
1156
- ### Column pinning
1157
-
1158
- ```tsx
1159
- const columns: ColumnType<User>[] = [
1160
- { key: 'name', dataIndex: 'name', title: 'Name', pinned: 'left', width: 200 },
1161
- { key: 'email', dataIndex: 'email', title: 'Email', width: 250 },
1162
- { key: 'actions', dataIndex: 'actions', title: 'Actions', pinned: 'right', width: 100 },
1163
- ];
1164
- ```
1165
-
1166
- Users can also pin/unpin columns at runtime via the right-click context menu.
1167
-
1168
- ---
1169
-
1170
- ### Row pinning
1171
-
1172
- 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.
1173
-
1174
- ```tsx
1175
- const [rowPinning, setRowPinning] = useState({ top: [], bottom: [] });
1176
-
1177
- <BoltTable
1178
- columns={columns}
1179
- data={data}
1180
- rowKey="id"
1181
- rowPinning={rowPinning}
1182
- onRowPin={(key, pinned) => {
1183
- setRowPinning(prev => {
1184
- const top = (prev.top ?? []).filter(k => String(k) !== String(key));
1185
- const bottom = (prev.bottom ?? []).filter(k => String(k) !== String(key));
1186
- if (pinned === 'top') top.push(key);
1187
- if (pinned === 'bottom') bottom.push(key);
1188
- return { top, bottom };
1189
- });
1190
- }}
1191
- styles={{ pinnedRowBg: 'rgba(255, 255, 255, 0.95)' }}
1192
- />
1193
- ```
1194
-
1195
- Users can also pin/unpin rows at runtime via the right-click context menu on any body cell (when `onRowPin` is provided).
1196
-
1197
- 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`.
1198
-
1199
- ---
1200
-
1201
- ### Cell context menu & copy
1202
-
1203
- Right-click (or long-press on mobile) any body cell to see a context menu with:
1204
-
1205
- - **Pin to Top / Unpin from Top** — shown when `onRowPin` is provided
1206
- - **Pin to Bottom / Unpin from Bottom** — shown when `onRowPin` is provided
1207
- - **Copy** — shown when the column has `copy: true` or a copy function
1208
-
1209
- ```tsx
1210
- const columns: ColumnType<User>[] = [
1211
- {
1212
- key: 'name',
1213
- dataIndex: 'name',
1214
- title: 'Name',
1215
- copy: true, // copies the raw cell value
1216
- },
1217
- {
1218
- key: 'email',
1219
- dataIndex: 'email',
1220
- title: 'Email',
1221
- // Custom copy — control exactly what goes to the clipboard
1222
- copy: (value, record) => `${record.name} <${value}>`,
1223
- },
1224
- ];
1225
- ```
1226
-
1227
- 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.
1228
-
1229
- ---
1230
-
1231
- ### Editable cells
1232
-
1233
- 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.
1234
-
1235
- ```tsx
1236
- const [data, setData] = useState<User[]>(initialData);
1237
-
1238
- const columns: ColumnType<User>[] = [
1239
- { key: 'name', dataIndex: 'name', title: 'Name', editable: true, width: 200 },
1240
- { key: 'age', dataIndex: 'age', title: 'Age', editable: true, width: 80 },
1241
- {
1242
- key: 'status',
1243
- dataIndex: 'status',
1244
- title: 'Status',
1245
- render: (value) => <span className="badge">{String(value)}</span>,
1246
- editable: true, // ignored — custom render takes precedence
1247
- },
1248
- ];
1249
-
1250
- <BoltTable
1251
- columns={columns}
1252
- data={data}
1253
- rowKey="id"
1254
- onEdit={(value, record, dataIndex, rowIndex) => {
1255
- setData(prev =>
1256
- prev.map((row, i) =>
1257
- i === rowIndex ? { ...row, [dataIndex]: value } : row
1258
- )
1259
- );
1260
- }}
1261
- />
1262
- ```
1263
-
1264
- The edit icon can be customized via the `icons` prop:
1265
-
1266
- ```tsx
1267
- <BoltTable icons={{ edit: <MyPencilIcon size={14} /> }} />
1268
- ```
1269
-
1270
- > **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.
1271
-
1272
- ---
1273
-
1274
- ### Styling overrides
1275
-
1276
- ```tsx
1277
- <BoltTable
1278
- columns={columns}
1279
- data={data}
1280
- accentColor="#6366f1"
1281
- classNames={{
1282
- header: 'text-xs uppercase tracking-wider text-gray-500',
1283
- cell: 'text-sm',
1284
- pinnedHeader: 'border-r border-indigo-200',
1285
- pinnedCell: 'border-r border-indigo-100',
1286
- }}
1287
- styles={{
1288
- header: { fontWeight: 600 },
1289
- rowHover: { backgroundColor: '#f0f9ff' },
1290
- rowSelected: { backgroundColor: '#e0e7ff' },
1291
- pinnedBg: 'rgba(238, 242, 255, 0.95)',
1292
- }}
1293
- />
1294
- ```
1295
-
1296
- ---
1297
-
1298
- ### Nested / grouped columns
1299
-
1300
- 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.
1301
-
1302
- ```tsx
1303
- const columns: ColumnType<User>[] = [
1304
- { key: 'id', dataIndex: 'id', title: 'ID', width: 80 },
1305
- {
1306
- key: 'nameGroup',
1307
- title: 'Name',
1308
- children: [
1309
- { key: 'firstName', dataIndex: 'firstName', title: 'First Name', width: 150 },
1310
- { key: 'lastName', dataIndex: 'lastName', title: 'Last Name', width: 150 },
1311
- ],
1312
- },
1313
- {
1314
- key: 'contactGroup',
1315
- title: 'Contact Info',
1316
- children: [
1317
- { key: 'email', dataIndex: 'email', title: 'Email', width: 250 },
1318
- { key: 'phone', dataIndex: 'phone', title: 'Phone', width: 150 },
1319
- ],
1320
- },
1321
- { key: 'age', dataIndex: 'age', title: 'Age', width: 80 },
1322
- ];
1323
-
1324
- <BoltTable columns={columns} data={data} rowKey="id" />
1325
- ```
1326
-
1327
- 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.
1328
-
1329
- ---
1330
-
1331
- ### Pagination styles
1332
-
1333
- Customize every part of the pagination footer via `styles` and `classNames`:
1334
-
1335
- ```tsx
1336
- <BoltTable
1337
- columns={columns}
1338
- data={data}
1339
- pagination={{ pageSize: 20 }}
1340
- classNames={{
1341
- pagination: 'bg-gray-50 border-t border-gray-200',
1342
- paginationButton: 'rounded hover:bg-gray-200',
1343
- paginationActiveButton: 'font-bold text-blue-600',
1344
- paginationSelect: 'rounded border-gray-300',
1345
- paginationInfo: 'text-gray-500 text-xs',
1346
- }}
1347
- styles={{
1348
- pagination: { height: 40 },
1349
- paginationButton: { borderRadius: 4 },
1350
- paginationActiveButton: { fontWeight: 700 },
1351
- paginationSelect: { borderRadius: 4 },
1352
- paginationInfo: { fontSize: 11 },
1353
- }}
1354
- />
1355
- ```
1356
-
1357
- Available pagination style/class keys: `pagination` (wrapper), `paginationButton` (nav buttons), `paginationActiveButton` (active page), `paginationSelect` (page-size dropdown), `paginationInfo` (range text).
1358
-
1359
- ---
1360
-
1361
- ### Fixed height (fill parent)
1362
-
1363
- By default, BoltTable auto-sizes to its content. To fill a fixed-height container instead:
1364
-
1365
- ```tsx
1366
- <div style={{ height: 600 }}>
1367
- <BoltTable
1368
- columns={columns}
1369
- data={data}
1370
- autoHeight={false}
1371
- />
1372
- </div>
1373
- ```
1374
-
1375
- ---
1376
-
1377
- ## Documentation
1378
-
1379
- For the complete guide with in-depth examples for every feature, visit the **[BoltTable Documentation](https://bolt-table.vercel.app/)**.
1380
-
1381
- ---
1382
-
1383
- ## Type exports
1384
-
1385
- ```ts
1386
- import type {
1387
- ColumnType,
1388
- ColumnContextMenuItem,
1389
- RowSelectionConfig,
1390
- RowPinningConfig,
1391
- ExpandableConfig,
1392
- PaginationType,
1393
- SortDirection,
1394
- DataRecord,
1395
- BoltTableIcons,
112
+ ConditionalFormatRule,
113
+ RowGroupingConfig,
114
+ AggregateFunction,
115
+ TreeDataConfig,
116
+ ClassNamesTypes,
117
+ StylesTypes,
118
+ AIResponse,
119
+ AIOperation,
120
+ BoltTableAIConfig,
121
+ BoltTableConfig,
1396
122
  } from 'bolt-table';
1397
123
  ```
1398
124