bolt-table 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Keerthi Venkatesh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,501 @@
1
+ # @venkateshsirigineedi/bolt-table
2
+
3
+ A high-performance, fully-featured React table component built on [TanStack Virtual](https://tanstack.com/virtual) and [@dnd-kit](https://dndkit.com). Only the rows visible in the viewport are ever in the DOM — making it fast for datasets of any size.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@venkateshsirigineedi/bolt-table)](https://www.npmjs.com/package/@venkateshsirigineedi/bolt-table)
6
+ [![license](https://img.shields.io/npm/l/@venkateshsirigineedi/bolt-table)](./LICENSE)
7
+
8
+ ---
9
+
10
+ ## Features
11
+
12
+ - **Row virtualization** — only visible rows are rendered, powered by TanStack Virtual
13
+ - **Drag to reorder columns** — grab any header and drag it to a new position
14
+ - **Column pinning** — pin columns to the left or right edge via right-click
15
+ - **Column resizing** — drag the right edge of any header to resize
16
+ - **Column hiding** — hide/show columns via the right-click context menu
17
+ - **Sorting** — client-side or server-side, with custom comparators per column
18
+ - **Filtering** — client-side or server-side, with custom filter functions per column
19
+ - **Pagination** — client-side slice or server-side with full control
20
+ - **Row selection** — checkbox or radio, with select-all, indeterminate state, and disabled rows
21
+ - **Expandable rows** — auto-measured content panels below each row, controlled or uncontrolled
22
+ - **Shimmer loading** — animated skeleton rows on initial load and infinite scroll append
23
+ - **Infinite scroll** — `onEndReached` callback with configurable threshold
24
+ - **Empty state** — custom renderer or default "No data" message
25
+ - **Auto height** — table shrinks/grows to fit rows, capped at 10 rows by default
26
+ - **Right-click context menu** — sort, filter, pin, hide, plus custom items
27
+ - **Dark mode** — works out of the box with CSS variables
28
+
29
+ ---
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ npm install @venkateshsirigineedi/bolt-table
35
+ ```
36
+
37
+ ### Peer dependencies
38
+
39
+ These must be installed separately in your project:
40
+
41
+ ```bash
42
+ npm install @tanstack/react-virtual @dnd-kit/core @dnd-kit/sortable lucide-react
43
+ ```
44
+
45
+ ---
46
+
47
+ ## Quick start
48
+
49
+ ```tsx
50
+ import { BoltTable, ColumnType } from '@venkateshsirigineedi/bolt-table';
51
+
52
+ interface User {
53
+ id: string;
54
+ name: string;
55
+ email: string;
56
+ age: number;
57
+ }
58
+
59
+ const columns: ColumnType<User>[] = [
60
+ { key: 'name', dataIndex: 'name', title: 'Name', width: 200 },
61
+ { key: 'email', dataIndex: 'email', title: 'Email', width: 280 },
62
+ { key: 'age', dataIndex: 'age', title: 'Age', width: 80 },
63
+ ];
64
+
65
+ const data: User[] = [
66
+ { id: '1', name: 'Alice', email: 'alice@example.com', age: 28 },
67
+ { id: '2', name: 'Bob', email: 'bob@example.com', age: 34 },
68
+ { id: '3', name: 'Charlie', email: 'charlie@example.com', age: 22 },
69
+ ];
70
+
71
+ export default function App() {
72
+ return (
73
+ <BoltTable<User>
74
+ columns={columns}
75
+ data={data}
76
+ rowKey="id"
77
+ />
78
+ );
79
+ }
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Next.js (App Router)
85
+
86
+ BoltTable uses browser APIs and must be wrapped in a client boundary. Remove the `'use client'` directive from the component files and wrap usage instead:
87
+
88
+ ```tsx
89
+ 'use client';
90
+ import { BoltTable } from '@venkateshsirigineedi/bolt-table';
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Styling
96
+
97
+ BoltTable uses [Tailwind CSS](https://tailwindcss.com) utility classes and [Shadcn/ui](https://ui.shadcn.com) CSS variables (`--muted`, `--background`, `--border`, etc.).
98
+
99
+ Make sure your project has Tailwind configured and the Shadcn CSS variables defined in your global stylesheet. If you use a different design system, you can override styles via the `styles` and `classNames` props.
100
+
101
+ ---
102
+
103
+ ## Props
104
+
105
+ ### `BoltTable`
106
+
107
+ | Prop | Type | Default | Description |
108
+ |------|------|---------|-------------|
109
+ | `columns` | `ColumnType<T>[]` | — | Column definitions (required) |
110
+ | `data` | `T[]` | — | Row data array (required) |
111
+ | `rowKey` | `string \| (record: T) => string` | `'id'` | Unique row identifier |
112
+ | `rowHeight` | `number` | `40` | Height of each row in pixels |
113
+ | `expandedRowHeight` | `number` | `200` | Estimated height for expanded rows |
114
+ | `maxExpandedRowHeight` | `number` | — | Max height for expanded row panels (makes them scrollable) |
115
+ | `accentColor` | `string` | `'#1890ff'` | Color used for sort icons, selected rows, resize line, etc. |
116
+ | `className` | `string` | `''` | Class name for the outer wrapper |
117
+ | `classNames` | `ClassNamesTypes` | `{}` | Granular class overrides per table region |
118
+ | `styles` | `StylesTypes` | `{}` | Inline style overrides per table region |
119
+ | `gripIcon` | `ReactNode` | — | Custom drag grip icon (defaults to `GripVertical`) |
120
+ | `hideGripIcon` | `boolean` | `false` | Hide the drag grip icon from all headers |
121
+ | `pagination` | `PaginationType \| false` | — | Pagination config, or `false` to disable |
122
+ | `onPaginationChange` | `(page, pageSize) => void` | — | Called when page or page size changes |
123
+ | `onColumnResize` | `(columnKey, newWidth) => void` | — | Called when a column is resized |
124
+ | `onColumnOrderChange` | `(newOrder) => void` | — | Called when columns are reordered |
125
+ | `onColumnPin` | `(columnKey, pinned) => void` | — | Called when a column is pinned/unpinned |
126
+ | `onColumnHide` | `(columnKey, hidden) => void` | — | Called when a column is hidden/shown |
127
+ | `rowSelection` | `RowSelectionConfig<T>` | — | Row selection config |
128
+ | `expandable` | `ExpandableConfig<T>` | — | Expandable row config |
129
+ | `onEndReached` | `() => void` | — | Called when scrolled near the bottom (infinite scroll) |
130
+ | `onEndReachedThreshold` | `number` | `5` | Rows from end to trigger `onEndReached` |
131
+ | `isLoading` | `boolean` | `false` | Shows shimmer skeleton rows when `true` |
132
+ | `onSortChange` | `(columnKey, direction) => void` | — | Server-side sort handler (disables local sort) |
133
+ | `onFilterChange` | `(filters) => void` | — | Server-side filter handler (disables local filter) |
134
+ | `columnContextMenuItems` | `ColumnContextMenuItem[]` | — | Custom items appended to the header context menu |
135
+ | `autoHeight` | `boolean` | `true` | Auto-size table height to content (capped at 10 rows) |
136
+ | `layoutLoading` | `boolean` | `false` | Show full skeleton layout (headers + rows) |
137
+ | `emptyRenderer` | `ReactNode` | — | Custom empty state content |
138
+
139
+ ---
140
+
141
+ ### `ColumnType<T>`
142
+
143
+ | Field | Type | Default | Description |
144
+ |-------|------|---------|-------------|
145
+ | `key` | `string` | — | Unique column identifier (required) |
146
+ | `dataIndex` | `string` | — | Row object property to display (required) |
147
+ | `title` | `string \| ReactNode` | — | Header label (required) |
148
+ | `width` | `number` | `150` | Column width in pixels |
149
+ | `render` | `(value, record, index) => ReactNode` | — | Custom cell renderer |
150
+ | `shimmerRender` | `() => ReactNode` | — | Custom shimmer skeleton for this column |
151
+ | `sortable` | `boolean` | `true` | Show sort controls for this column |
152
+ | `sorter` | `boolean \| (a: T, b: T) => number` | — | Custom sort comparator for client-side sort |
153
+ | `filterable` | `boolean` | `true` | Show filter option in context menu |
154
+ | `filterFn` | `(value, record, dataIndex) => boolean` | — | Custom filter predicate for client-side filter |
155
+ | `hidden` | `boolean` | `false` | Hide this column |
156
+ | `pinned` | `'left' \| 'right' \| false` | `false` | Pin this column to an edge |
157
+ | `className` | `string` | — | Class applied to all cells in this column |
158
+ | `style` | `CSSProperties` | — | Styles applied to all cells in this column |
159
+
160
+ ---
161
+
162
+ ## Examples
163
+
164
+ ### Sorting
165
+
166
+ **Client-side** (no `onSortChange` — BoltTable sorts locally):
167
+
168
+ ```tsx
169
+ const columns: ColumnType<User>[] = [
170
+ {
171
+ key: 'name',
172
+ dataIndex: 'name',
173
+ title: 'Name',
174
+ sortable: true,
175
+ // Optional custom comparator:
176
+ sorter: (a, b) => a.name.localeCompare(b.name),
177
+ },
178
+ {
179
+ key: 'age',
180
+ dataIndex: 'age',
181
+ title: 'Age',
182
+ sortable: true,
183
+ // Default numeric comparator used when sorter is omitted
184
+ },
185
+ ];
186
+
187
+ <BoltTable columns={columns} data={data} />
188
+ ```
189
+
190
+ **Server-side** (provide `onSortChange` — BoltTable delegates to you):
191
+
192
+ ```tsx
193
+ const [sortKey, setSortKey] = useState('');
194
+ const [sortDir, setSortDir] = useState<SortDirection>(null);
195
+
196
+ <BoltTable
197
+ columns={columns}
198
+ data={serverData}
199
+ onSortChange={(key, dir) => {
200
+ setSortKey(key);
201
+ setSortDir(dir);
202
+ refetch({ sortKey: key, sortDir: dir });
203
+ }}
204
+ />
205
+ ```
206
+
207
+ ---
208
+
209
+ ### Filtering
210
+
211
+ **Client-side** (no `onFilterChange`):
212
+
213
+ ```tsx
214
+ const columns: ColumnType<User>[] = [
215
+ {
216
+ key: 'status',
217
+ dataIndex: 'status',
218
+ title: 'Status',
219
+ filterable: true,
220
+ // Exact match instead of default substring:
221
+ filterFn: (value, record) => record.status === value,
222
+ },
223
+ ];
224
+ ```
225
+
226
+ **Server-side**:
227
+
228
+ ```tsx
229
+ <BoltTable
230
+ columns={columns}
231
+ data={serverData}
232
+ onFilterChange={(filters) => {
233
+ setActiveFilters(filters);
234
+ refetch({ filters });
235
+ }}
236
+ />
237
+ ```
238
+
239
+ ---
240
+
241
+ ### Pagination
242
+
243
+ **Client-side** (pass all data, BoltTable slices it):
244
+
245
+ ```tsx
246
+ <BoltTable
247
+ columns={columns}
248
+ data={allUsers} // all 500 rows
249
+ pagination={{ pageSize: 20 }}
250
+ onPaginationChange={(page, size) => {
251
+ setPage(page);
252
+ }}
253
+ />
254
+ ```
255
+
256
+ **Server-side** (pass only the current page):
257
+
258
+ ```tsx
259
+ <BoltTable
260
+ columns={columns}
261
+ data={currentPageData} // only 20 rows
262
+ pagination={{
263
+ current: page,
264
+ pageSize: 20,
265
+ total: 500,
266
+ showTotal: (total, [from, to]) => `${from}-${to} of ${total} users`,
267
+ }}
268
+ onPaginationChange={(page, size) => fetchPage(page, size)}
269
+ />
270
+ ```
271
+
272
+ **Disable pagination:**
273
+
274
+ ```tsx
275
+ <BoltTable columns={columns} data={data} pagination={false} />
276
+ ```
277
+
278
+ ---
279
+
280
+ ### Row selection
281
+
282
+ ```tsx
283
+ const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
284
+
285
+ <BoltTable
286
+ columns={columns}
287
+ data={data}
288
+ rowKey="id"
289
+ rowSelection={{
290
+ type: 'checkbox', // or 'radio'
291
+ selectedRowKeys,
292
+ onChange: (keys, rows) => setSelectedRowKeys(keys),
293
+ // Disable selection for specific rows:
294
+ getCheckboxProps: (record) => ({
295
+ disabled: record.status === 'locked',
296
+ }),
297
+ }}
298
+ />
299
+ ```
300
+
301
+ ---
302
+
303
+ ### Expandable rows
304
+
305
+ ```tsx
306
+ <BoltTable
307
+ columns={columns}
308
+ data={data}
309
+ rowKey="id"
310
+ expandable={{
311
+ rowExpandable: (record) => record.details !== null,
312
+ expandedRowRender: (record) => (
313
+ <div style={{ padding: 16 }}>
314
+ <h4>{record.name} — Details</h4>
315
+ <pre>{JSON.stringify(record.details, null, 2)}</pre>
316
+ </div>
317
+ ),
318
+ // Optional: control expanded state yourself
319
+ // expandedRowKeys={expandedKeys}
320
+ // onExpandedRowsChange={(keys) => setExpandedKeys(keys)}
321
+ }}
322
+ expandedRowHeight={150} // initial estimate
323
+ maxExpandedRowHeight={400} // makes panel scrollable if taller
324
+ />
325
+ ```
326
+
327
+ ---
328
+
329
+ ### Infinite scroll
330
+
331
+ ```tsx
332
+ const [data, setData] = useState<User[]>([]);
333
+ const [isLoading, setIsLoading] = useState(false);
334
+
335
+ const loadMore = async () => {
336
+ setIsLoading(true);
337
+ const newRows = await fetchNextPage();
338
+ setData(prev => [...prev, ...newRows]);
339
+ setIsLoading(false);
340
+ };
341
+
342
+ <BoltTable
343
+ columns={columns}
344
+ data={data}
345
+ isLoading={isLoading}
346
+ onEndReached={loadMore}
347
+ onEndReachedThreshold={8}
348
+ pagination={false}
349
+ />
350
+ ```
351
+
352
+ ---
353
+
354
+ ### Column pinning
355
+
356
+ Pinning via column definition:
357
+
358
+ ```tsx
359
+ const columns: ColumnType<User>[] = [
360
+ { key: 'name', dataIndex: 'name', title: 'Name', pinned: 'left', width: 200 },
361
+ { key: 'email', dataIndex: 'email', title: 'Email', width: 250 },
362
+ { key: 'actions', dataIndex: 'actions', title: 'Actions', pinned: 'right', width: 100 },
363
+ ];
364
+ ```
365
+
366
+ Users can also pin/unpin columns at runtime via the right-click context menu.
367
+
368
+ ---
369
+
370
+ ### Custom cell rendering
371
+
372
+ ```tsx
373
+ const columns: ColumnType<User>[] = [
374
+ {
375
+ key: 'status',
376
+ dataIndex: 'status',
377
+ title: 'Status',
378
+ width: 120,
379
+ render: (value, record) => (
380
+ <span
381
+ style={{
382
+ padding: '2px 8px',
383
+ borderRadius: 4,
384
+ fontSize: 12,
385
+ backgroundColor: record.status === 'active' ? '#d1fae5' : '#fee2e2',
386
+ color: record.status === 'active' ? '#065f46' : '#991b1b',
387
+ }}
388
+ >
389
+ {String(value)}
390
+ </span>
391
+ ),
392
+ },
393
+ ];
394
+ ```
395
+
396
+ ---
397
+
398
+ ### Custom context menu items
399
+
400
+ ```tsx
401
+ <BoltTable
402
+ columns={columns}
403
+ data={data}
404
+ columnContextMenuItems={[
405
+ {
406
+ key: 'copy',
407
+ label: 'Copy column data',
408
+ icon: <CopyIcon className="h-3 w-3" />,
409
+ onClick: (columnKey) => copyColumnToClipboard(columnKey),
410
+ },
411
+ {
412
+ key: 'reset-width',
413
+ label: 'Reset width',
414
+ onClick: (columnKey) => resetColumnWidth(columnKey),
415
+ },
416
+ ]}
417
+ />
418
+ ```
419
+
420
+ ---
421
+
422
+ ### Styling overrides
423
+
424
+ ```tsx
425
+ <BoltTable
426
+ columns={columns}
427
+ data={data}
428
+ accentColor="#6366f1"
429
+ classNames={{
430
+ header: 'text-xs uppercase tracking-wider text-gray-500',
431
+ cell: 'text-sm',
432
+ pinnedHeader: 'border-r border-indigo-200',
433
+ pinnedCell: 'border-r border-indigo-100',
434
+ }}
435
+ styles={{
436
+ header: { fontWeight: 600 },
437
+ rowHover: { backgroundColor: '#f0f9ff' },
438
+ rowSelected: { backgroundColor: '#e0e7ff' },
439
+ pinnedBg: 'rgba(238, 242, 255, 0.95)',
440
+ }}
441
+ />
442
+ ```
443
+
444
+ ---
445
+
446
+ ### Loading skeleton
447
+
448
+ ```tsx
449
+ // Full skeleton on initial load (no data yet)
450
+ <BoltTable
451
+ columns={columns}
452
+ data={[]}
453
+ isLoading={true}
454
+ pagination={{ pageSize: 20 }}
455
+ />
456
+
457
+ // Layout skeleton before column widths are known
458
+ <BoltTable
459
+ columns={columns}
460
+ data={[]}
461
+ layoutLoading={true}
462
+ />
463
+ ```
464
+
465
+ ---
466
+
467
+ ### Fixed height (fill parent)
468
+
469
+ By default, BoltTable auto-sizes to its content. To fill a fixed-height container instead:
470
+
471
+ ```tsx
472
+ <div style={{ height: 600 }}>
473
+ <BoltTable
474
+ columns={columns}
475
+ data={data}
476
+ autoHeight={false}
477
+ />
478
+ </div>
479
+ ```
480
+
481
+ ---
482
+
483
+ ## Type exports
484
+
485
+ ```ts
486
+ import type {
487
+ ColumnType,
488
+ ColumnContextMenuItem,
489
+ RowSelectionConfig,
490
+ ExpandableConfig,
491
+ PaginationType,
492
+ SortDirection,
493
+ DataRecord,
494
+ } from '@venkateshsirigineedi/bolt-table';
495
+ ```
496
+
497
+ ---
498
+
499
+ ## License
500
+
501
+ MIT © [Venkatesh Sirigineedi](https://github.com/venkateshsirigineedi)