bolt-table 0.1.40 → 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 +37 -1453
- package/dist/index.d.mts +202 -45
- package/dist/index.d.ts +202 -45
- package/dist/index.js +100 -5484
- package/dist/index.mjs +100 -5454
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -5,40 +5,34 @@ A high-performance, zero-dependency\* React table component. Only the rows visib
|
|
|
5
5
|
[](https://www.npmjs.com/package/bolt-table)
|
|
6
6
|
[](./LICENSE)
|
|
7
7
|
[](https://github.com/venkateshwebdev/Bolt-Table)
|
|
8
|
-
[](https://bolt-table.vercel.app/)
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
## Features
|
|
13
13
|
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
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
|
|
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
|
|