@snowpact/react-tanstack-query-table 1.0.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) 2025 Snowpact
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,461 @@
1
+ # @snowpact/react-tanstack-query-table
2
+
3
+ Ultra-light, registry-based data table for React + TanStack Table + TanStack Query.
4
+
5
+ ## Features
6
+
7
+ - **Zero heavy dependencies**: No clsx, no tailwind-merge, no lucide-react bundled
8
+ - **Registry-based**: Inject your own i18n, Link component, confirmation dialogs
9
+ - **TypeScript**: Full type support with generics
10
+ - **Two modes**: Client-side and Server-side pagination/filtering/sorting
11
+ - **Customizable**: Override styles via registry
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @snowpact/react-tanstack-query-table
17
+ # or
18
+ pnpm add @snowpact/react-tanstack-query-table
19
+ ```
20
+
21
+ ### Peer Dependencies
22
+
23
+ ```bash
24
+ npm install @tanstack/react-query @tanstack/react-table react react-dom
25
+ ```
26
+
27
+ > **Tailwind CSS** is required. Make sure Tailwind is configured in your app.
28
+
29
+ ## Quick Start
30
+
31
+ ### 1. Setup (once in your app)
32
+
33
+ ```tsx
34
+ import { setupSnowTable } from '@snowpact/react-tanstack-query-table';
35
+ import { useTranslation } from 'react-i18next';
36
+ import { Link } from 'react-router-dom';
37
+ import { useConfirm } from './your-confirm-dialog';
38
+
39
+ setupSnowTable({
40
+ useTranslation: () => useTranslation(),
41
+ LinkComponent: Link,
42
+ useConfirm: () => useConfirm(),
43
+ });
44
+ ```
45
+
46
+ > **Full setup example**: See [examples/full-setup.md](./examples/full-setup.md) for a complete Shadcn/UI integration with custom `useConfirm` hook, tooltips, and translations.
47
+
48
+ ### 2. Use a Client Table
49
+
50
+ ```tsx
51
+ import { SnowClientTable, SnowColumnConfig } from '@snowpact/react-tanstack-query-table';
52
+ import { Edit, Trash } from 'lucide-react';
53
+
54
+ type User = { id: string; name: string; email: string };
55
+
56
+ const columns: SnowColumnConfig<User>[] = [{ key: 'name' }, { key: 'email' }];
57
+
58
+ <SnowClientTable
59
+ queryKey={['users']}
60
+ fetchAllItemsEndpoint={() => fetchUsers()}
61
+ columnConfig={columns}
62
+ actions={[
63
+ { type: 'click', icon: Edit, label: 'Edit', onClick: user => editUser(user) },
64
+ { type: 'endpoint', icon: Trash, label: 'Delete', endpoint: user => deleteUser(user.id) },
65
+ ]}
66
+ enableGlobalSearch
67
+ enablePagination
68
+ />;
69
+ ```
70
+
71
+ ## Client vs Server Mode
72
+
73
+ | Mode | Component | Use case | Data handling |
74
+ | ---------- | ----------------- | ------------- | ----------------------------------------------- |
75
+ | **Client** | `SnowClientTable` | < 5,000 items | All data loaded, filtered/sorted locally |
76
+ | **Server** | `SnowServerTable` | > 5,000 items | Paginated API, server handles filtering/sorting |
77
+
78
+ ### SnowClientTable
79
+
80
+ Best for small to medium datasets. Fetches all data once via React Query, then handles pagination, search, and sorting entirely in the browser.
81
+
82
+ ```tsx
83
+ <SnowClientTable
84
+ queryKey={['users']}
85
+ fetchAllItemsEndpoint={() => api.getUsers()} // Returns User[]
86
+ columnConfig={columns}
87
+ />
88
+ ```
89
+
90
+ ### SnowServerTable
91
+
92
+ Best for large datasets. The server handles pagination, search, filtering, and sorting. Returns paginated results with a total count.
93
+
94
+ ```tsx
95
+ import { SnowServerTable, ServerFetchParams } from '@snowpact/react-tanstack-query-table';
96
+
97
+ const fetchUsers = async (params: ServerFetchParams) => {
98
+ // params: { limit, offset, search?, sortBy?, sortOrder?, filters?, prefilter? }
99
+ const response = await api.getUsers(params);
100
+ return {
101
+ items: response.data,
102
+ totalItemCount: response.total,
103
+ };
104
+ };
105
+
106
+ <SnowServerTable queryKey={['users']} fetchServerEndpoint={fetchUsers} columnConfig={columns} />;
107
+ ```
108
+
109
+ ## Actions
110
+
111
+ Actions appear as buttons in each row. Three types are available:
112
+
113
+ ### Click Action
114
+
115
+ Simple callback when clicked.
116
+
117
+ ```tsx
118
+ {
119
+ type: 'click',
120
+ icon: Edit,
121
+ label: 'Edit',
122
+ onClick: (item) => openEditModal(item),
123
+ }
124
+ ```
125
+
126
+ ### Link Action
127
+
128
+ Navigates to a URL. Uses your registered `LinkComponent`.
129
+
130
+ ```tsx
131
+ {
132
+ type: 'link',
133
+ icon: Eye,
134
+ label: 'View',
135
+ href: (item) => `/users/${item.id}`,
136
+ external: false, // true for target="_blank"
137
+ }
138
+ ```
139
+
140
+ ### Endpoint Action
141
+
142
+ Calls an async endpoint (API mutation). Integrates with React Query invalidation.
143
+
144
+ ```tsx
145
+ {
146
+ type: 'endpoint',
147
+ icon: Trash,
148
+ label: 'Delete',
149
+ endpoint: (item) => api.deleteUser(item.id),
150
+ onSuccess: () => queryClient.invalidateQueries(['users']),
151
+ onError: (error) => toast.error(error.message),
152
+ }
153
+ ```
154
+
155
+ ### Confirmation Dialog
156
+
157
+ Any action can require confirmation before executing:
158
+
159
+ ```tsx
160
+ {
161
+ type: 'endpoint',
162
+ icon: Trash,
163
+ label: 'Delete',
164
+ variant: 'danger',
165
+ endpoint: (item) => api.deleteUser(item.id),
166
+ confirm: {
167
+ title: 'Delete user?',
168
+ content: 'This action cannot be undone.',
169
+ confirmText: 'Delete',
170
+ cancelText: 'Cancel',
171
+ },
172
+ }
173
+ ```
174
+
175
+ For forms inside confirm dialogs, use a function to access the `close` helper:
176
+
177
+ ```tsx
178
+ {
179
+ type: 'click',
180
+ icon: Edit,
181
+ label: 'Change Status',
182
+ onClick: () => {},
183
+ confirm: {
184
+ title: 'Change Status',
185
+ hideButtons: true, // Form handles its own buttons
186
+ content: ({ close }) => (
187
+ <StatusForm
188
+ onSuccess={() => {
189
+ queryClient.invalidateQueries(['users']);
190
+ close();
191
+ }}
192
+ />
193
+ ),
194
+ },
195
+ }
196
+ ```
197
+
198
+ ### Action Options
199
+
200
+ | Option | Type | Description |
201
+ | ----------- | ----------------------------------------------------------- | ---------------------------------------- |
202
+ | `icon` | `ComponentType<SVGProps>` | Icon component (lucide-react or any SVG) |
203
+ | `label` | `string` | Button label (used for tooltip) |
204
+ | `variant` | `'default' \| 'danger' \| 'warning' \| 'info' \| 'success'` | Button color variant |
205
+ | `display` | `'button' \| 'dropdown'` | Show as button or in dropdown menu |
206
+ | `hidden` | `boolean` | Conditionally hide the action |
207
+ | `disabled` | `boolean` | Disable the action |
208
+ | `showLabel` | `boolean` | Show label text next to icon |
209
+
210
+ ### Dynamic Actions
211
+
212
+ Actions can be functions that return the action config, allowing per-row customization:
213
+
214
+ ```tsx
215
+ actions={[
216
+ (item) => ({
217
+ type: 'click',
218
+ icon: item.isActive ? Pause : Play,
219
+ label: item.isActive ? 'Deactivate' : 'Activate',
220
+ onClick: () => toggleStatus(item),
221
+ hidden: item.role === 'admin', // Hide for admins
222
+ }),
223
+ ]}
224
+ ```
225
+
226
+ ## Search & Filtering
227
+
228
+ ### Global Search
229
+
230
+ Enable fuzzy search across all columns:
231
+
232
+ ```tsx
233
+ <SnowClientTable enableGlobalSearch texts={{ searchPlaceholder: 'Search users...' }} />
234
+ ```
235
+
236
+ For custom search values (computed columns):
237
+
238
+ ```tsx
239
+ const columns: SnowColumnConfig<User>[] = [
240
+ {
241
+ key: '_extra_fullName',
242
+ label: 'Full Name',
243
+ render: item => `${item.firstName} ${item.lastName}`,
244
+ searchableValue: item => `${item.firstName} ${item.lastName}`,
245
+ },
246
+ ];
247
+ ```
248
+
249
+ ### Column Filters
250
+
251
+ Multi-select dropdown filters:
252
+
253
+ ```tsx
254
+ <SnowClientTable
255
+ filters={[
256
+ {
257
+ key: 'status',
258
+ label: 'Status',
259
+ options: [
260
+ { value: 'active', label: 'Active' },
261
+ { value: 'inactive', label: 'Inactive' },
262
+ ],
263
+ },
264
+ {
265
+ key: 'role',
266
+ label: 'Role',
267
+ options: [
268
+ { value: 'admin', label: 'Admin' },
269
+ { value: 'user', label: 'User' },
270
+ ],
271
+ },
272
+ ]}
273
+ />
274
+ ```
275
+
276
+ ### Prefilters (Tabs)
277
+
278
+ Quick segmentation via tabs:
279
+
280
+ ```tsx
281
+ <SnowClientTable
282
+ prefilters={[
283
+ { id: 'all', label: 'All' },
284
+ { id: 'active', label: 'Active' },
285
+ { id: 'archived', label: 'Archived' },
286
+ ]}
287
+ prefilterFn={(item, prefilterId) => {
288
+ if (prefilterId === 'all') return true;
289
+ return item.status === prefilterId;
290
+ }}
291
+ />
292
+ ```
293
+
294
+ For server mode, the `prefilter` value is sent to your endpoint.
295
+
296
+ ## Advanced Configuration
297
+
298
+ ### Column Configuration
299
+
300
+ Users can show/hide columns via a settings button. Configuration is saved in cookies.
301
+
302
+ ```tsx
303
+ <SnowClientTable
304
+ enableColumnConfiguration
305
+ columnConfig={[
306
+ { key: 'name' }, // Always visible
307
+ { key: 'email' },
308
+ { key: 'details', meta: { defaultHidden: true } }, // Hidden by default
309
+ ]}
310
+ />
311
+ ```
312
+
313
+ ### URL State Persistence
314
+
315
+ Persist table state (pagination, search, filters, sorting) in URL query params:
316
+
317
+ ```tsx
318
+ <SnowClientTable
319
+ persistState // State saved in URL
320
+ />
321
+ ```
322
+
323
+ URL params used:
324
+
325
+ - `dt_page`, `dt_pageSize` - Pagination
326
+ - `dt_search` - Search query
327
+ - `dt_prefilter` - Active prefilter
328
+ - `dt_filters` - Column filters
329
+ - `dt_sortBy`, `dt_sortDesc` - Sorting
330
+
331
+ ### Column Meta Properties
332
+
333
+ ```tsx
334
+ {
335
+ key: 'actions',
336
+ meta: {
337
+ width: '100px', // Fixed width
338
+ minWidth: '80px', // Minimum width
339
+ center: true, // Center content
340
+ defaultHidden: true, // Hidden by default
341
+ disableColumnClick: true, // Disable row click for this column
342
+ },
343
+ }
344
+ ```
345
+
346
+ ### Sorting
347
+
348
+ ```tsx
349
+ <SnowClientTable enableSorting defaultSortBy="createdAt" defaultSortOrder="desc" />
350
+ ```
351
+
352
+ ### Pagination
353
+
354
+ ```tsx
355
+ <SnowClientTable
356
+ enablePagination
357
+ defaultPageSize={25}
358
+ displayTotalNumber // Show "X elements"
359
+ enableElementLabel // Show element label
360
+ />
361
+ ```
362
+
363
+ ### Row Click
364
+
365
+ ```tsx
366
+ <SnowClientTable
367
+ onRowClick={item => navigate(`/users/${item.id}`)}
368
+ activeRowId={selectedUserId} // Highlight active row
369
+ />
370
+ ```
371
+
372
+ ### Custom Styles
373
+
374
+ Override default Tailwind classes via the registry:
375
+
376
+ ```tsx
377
+ setupSnowTable({
378
+ // ... other options
379
+ styles: {
380
+ table: {
381
+ wrapper: 'border border-gray-200 rounded-lg',
382
+ header: 'bg-gray-50',
383
+ row: 'hover:bg-gray-100',
384
+ },
385
+ button: {
386
+ visual: 'bg-blue-500 text-white',
387
+ hover: 'hover:bg-blue-600',
388
+ },
389
+ },
390
+ });
391
+ ```
392
+
393
+ ### Action Hover (Tooltips)
394
+
395
+ Integrate with your tooltip system:
396
+
397
+ ```tsx
398
+ setupSnowTable({
399
+ // ... other options
400
+ onActionHover: ({ label, element }) => showTooltip(label, element),
401
+ onActionUnhover: () => hideTooltip(),
402
+ });
403
+ ```
404
+
405
+ ## API Reference
406
+
407
+ ### SnowClientTable Props
408
+
409
+ | Prop | Type | Default | Description |
410
+ | --------------------------- | ----------------------- | -------- | ------------------------------- |
411
+ | `queryKey` | `string[]` | Required | React Query cache key |
412
+ | `fetchAllItemsEndpoint` | `() => Promise<T[]>` | Required | Data fetching function |
413
+ | `columnConfig` | `SnowColumnConfig<T>[]` | Required | Column definitions |
414
+ | `actions` | `TableAction<T>[]` | `[]` | Row actions |
415
+ | `filters` | `FilterConfig<T>[]` | `[]` | Column filters |
416
+ | `prefilters` | `PreFilter[]` | `[]` | Tab filters |
417
+ | `prefilterFn` | `(item, id) => boolean` | - | Client-side prefilter logic |
418
+ | `persistState` | `boolean` | `false` | Persist state in URL |
419
+ | `enableGlobalSearch` | `boolean` | `false` | Enable search bar |
420
+ | `enablePagination` | `boolean` | `false` | Enable pagination |
421
+ | `enableSorting` | `boolean` | `false` | Enable column sorting |
422
+ | `enableColumnConfiguration` | `boolean` | `false` | Enable column visibility toggle |
423
+ | `enableResetFilters` | `boolean` | `false` | Show reset filters button |
424
+ | `defaultPageSize` | `number` | `10` | Initial page size |
425
+ | `defaultSortBy` | `string` | - | Initial sort column |
426
+ | `defaultSortOrder` | `'asc' \| 'desc'` | `'asc'` | Initial sort direction |
427
+
428
+ ### SnowServerTable Props
429
+
430
+ Same as `SnowClientTable`, except:
431
+
432
+ | Prop | Type | Description |
433
+ | --------------------- | -------------------------------------------------------------------- | ------------------------ |
434
+ | `fetchServerEndpoint` | `(params: ServerFetchParams) => Promise<ServerPaginatedResponse<T>>` | Paginated fetch function |
435
+
436
+ ### ServerFetchParams
437
+
438
+ ```typescript
439
+ interface ServerFetchParams {
440
+ limit: number;
441
+ offset: number;
442
+ search?: string;
443
+ prefilter?: string;
444
+ filters?: Record<string, string[]>;
445
+ sortBy?: string;
446
+ sortOrder?: 'ASC' | 'DESC';
447
+ }
448
+ ```
449
+
450
+ ### ServerPaginatedResponse
451
+
452
+ ```typescript
453
+ interface ServerPaginatedResponse<T> {
454
+ items: T[];
455
+ totalItemCount: number;
456
+ }
457
+ ```
458
+
459
+ ## License
460
+
461
+ MIT