antd-crud-table 0.5.0 โ†’ 0.7.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.
Files changed (50) hide show
  1. package/README.md +295 -744
  2. package/dist/CrudTable-Btf58zof.js +610 -0
  3. package/dist/CrudTable-DRN70sET.cjs +1 -0
  4. package/dist/CrudTable.cjs +1 -1
  5. package/dist/CrudTable.d.ts +84 -17
  6. package/dist/CrudTable.js +1 -1
  7. package/dist/CrudTableLazy.cjs +1 -1
  8. package/dist/CrudTableLazy.d.ts +4 -3
  9. package/dist/CrudTableLazy.js +8 -7
  10. package/dist/core/CustomDataSource.d.ts +35 -0
  11. package/dist/core/InMemoryDataSource.d.ts +30 -0
  12. package/dist/core/LocalStorageDataSource.d.ts +30 -0
  13. package/dist/core/RestDataSource.d.ts +120 -0
  14. package/dist/core/StaticDataSource.d.ts +20 -0
  15. package/dist/core/identity.d.ts +28 -0
  16. package/dist/core/inMemoryQuery.d.ts +26 -0
  17. package/dist/core/index.d.ts +11 -0
  18. package/dist/core/types.d.ts +86 -0
  19. package/dist/en_US-D3Kl6g3n.cjs +1 -0
  20. package/dist/en_US-DGCMDOdX.js +41 -0
  21. package/dist/exportData.cjs +3 -3
  22. package/dist/exportData.js +37 -24
  23. package/dist/fields/registry.d.ts +17 -17
  24. package/dist/fields/types.d.ts +29 -0
  25. package/dist/hooks/useCrudTable.d.ts +114 -58
  26. package/dist/hooks/useLocalStorageCrud.d.ts +12 -12
  27. package/dist/index.cjs +1 -1
  28. package/dist/index.d.ts +13 -8
  29. package/dist/index.js +7 -6
  30. package/dist/locale/en_US.d.ts +10 -0
  31. package/dist/locale/index.d.ts +4 -0
  32. package/dist/locale/types.d.ts +89 -0
  33. package/dist/locale/useResolvedLocale.d.ts +35 -0
  34. package/dist/useCrudTable-40jpmCkr.cjs +1 -0
  35. package/dist/useCrudTable-DxjnXVlQ.js +460 -0
  36. package/dist/useCrudTable.cjs +1 -1
  37. package/dist/useCrudTable.js +2 -2
  38. package/dist/useLocalStorageCrud.cjs +1 -1
  39. package/dist/useLocalStorageCrud.js +12 -51
  40. package/dist/utils/exportData.d.ts +35 -14
  41. package/package.json +30 -9
  42. package/dist/CrudTable-DUfRGJW2.js +0 -524
  43. package/dist/CrudTable-TTRqkEoZ.cjs +0 -1
  44. package/dist/hooks/useCrudTable.test.d.ts +0 -1
  45. package/dist/hooks/useLocalStorageCrud.test.d.ts +0 -1
  46. package/dist/useCrudTable-CEY_riQg.cjs +0 -1
  47. package/dist/useCrudTable-DpF-oZHz.js +0 -246
  48. package/dist/utils/exportData.test.d.ts +0 -1
  49. package/dist/utils/query.d.ts +0 -13
  50. package/dist/utils/query.test.d.ts +0 -1
package/README.md CHANGED
@@ -1,837 +1,388 @@
1
+ <div align="center">
1
2
 
3
+ # antd-crud-table
2
4
 
3
- # ๐Ÿงฉ `antd-crud-table` โ€“ A Dynamic React Table Generator with Forms ๐Ÿš€
5
+ **A typed, schema-driven CRUD table for React โ€” built on [antd](https://ant.design) and [ProComponents](https://procomponents.ant.design).**
4
6
 
5
- `antd-crud-table` is a highly flexible and powerful React library built using `antd` and `@ant-design/pro-components`. It provides both a declarative component-based approach and a modern hook-based architecture for creating editable, paginated tables with form support, data fetching, sorting, filtering, and custom rendering. Perfect for building admin dashboards and data management UIs with minimal boilerplate.
7
+ Describe your columns once and get a paginated, searchable, sortable table with a
8
+ create/edit form, delete confirmations, bulk actions and export โ€” wired to static
9
+ data, a REST API, `localStorage`, or anything you implement yourself.
6
10
 
7
- ## ๐Ÿ“‹ API Reference
11
+ [![npm](https://img.shields.io/npm/v/antd-crud-table?color=1677ff)](https://www.npmjs.com/package/antd-crud-table)
12
+ [![license](https://img.shields.io/npm/l/antd-crud-table?color=52c41a)](./LICENSE)
13
+ [![React](https://img.shields.io/badge/react-18%20%7C%2019-61dafb)](https://react.dev)
14
+ [![types](https://img.shields.io/badge/types-strict-3178c6)](https://www.typescriptlang.org)
8
15
 
9
- ### CrudTable Props
16
+ [**Live demo**](https://maifeeulasad.github.io/antd-crud-table/) ยท
17
+ [**Storybook**](https://maifeeulasad.github.io/antd-crud-table/storybook/) ยท
18
+ [**API reference**](https://maifeeulasad.github.io/antd-crud-table/api/) ยท
19
+ [**Changelog**](./CHANGELOG.md)
10
20
 
11
- | Prop | Type | Description |
12
- |------|------|-------------|
13
- | `title` | `string` | Table header title |
14
- | `rowKey` | `keyof T` | Unique identifier for each row |
15
- | `columns` | `CrudColumn<T>[]` | Column definitions with |
16
- | `hookConfig` | `UseCrudTableConfig<T>` | Hook configuration for data operations |
17
- | `defaultPageSize?` | `number` | Initial page size (default: 10) |
18
- | `enableBulkOperations?` | `boolean` | Enable bulk select/delete (default: false) |
19
- | `customActions?` | `(record, actions) => ReactNode[]` | Custom row actions |ased Architecture**
20
-
21
- Latest version introduces a powerful hook-based architecture with multiple data source strategies:
22
- - **Static Data**: Perfect for prototypes and small datasets
23
- - **API Integration**: REST API support with automatic request handling
24
- - **Custom Operations**: Full control with GraphQL, IndexedDB, or custom logic
25
- - **Built-in State Management**: Loading states, error handling, optimistic updates
26
-
27
- ---
28
-
29
- ## ๐Ÿ“ฆ Installation
30
-
31
- ```bash
32
- npm install antd-crud-table
33
- ```
34
-
35
- **Peer Dependencies:**
36
- ```bash
37
- npm install react react-dom antd @ant-design/pro-components
38
- ```
21
+ </div>
39
22
 
40
23
  ---
41
24
 
42
- ## ๐Ÿš€ Quick Start
43
-
44
- Choose your preferred approach:
25
+ ## Why
45
26
 
46
- ### Modern Approach - Hook-Based
47
-
48
- ```tsx
49
- import { CrudTable } from 'antd-crud-table';
50
-
51
- // Static data example
52
- const UserManagement = () => (
53
- <CrudTable<User>
54
- title="User Management"
55
- rowKey="id"
56
- hookConfig={{
57
- staticData: users, // Your data array
58
- optimisticUpdates: true,
59
- }}
60
- columns={[
61
- {
62
- title: 'Name',
63
- dataIndex: 'name',
64
- fieldType: 'string',
65
- formConfig: { required: true },
66
- },
67
- {
68
- title: 'Status',
69
- dataIndex: 'status',
70
- fieldType: 'enum',
71
- enumOptions: {
72
- active: { text: 'Active', color: 'green' },
73
- inactive: { text: 'Inactive', color: 'orange' },
74
- },
75
- },
76
- ]}
77
- />
78
- );
79
- ```
80
-
81
- ### Classic Approach (Original) - Service-Based
82
-
83
- ```tsx
84
- import { CrudTable } from 'antd-crud-table';
85
-
86
- const userService = {
87
- getList: async () => ({ data: [], total: 0 }),
88
- create: async (data) => data,
89
- update: async (id, data) => data,
90
- delete: async (id) => {},
91
- };
92
-
93
- const UserTable = () => (
94
- <CrudTable
95
- title="User Management"
96
- rowKey="id"
97
- service={userService}
98
- columns={[
99
- {
100
- title: 'Name',
101
- dataIndex: 'name',
102
- fieldType: 'string',
103
- fieldEditable: true,
104
- formConfig: { required: true },
105
- },
106
- ]}
107
- />
108
- );
109
- ```
110
-
111
- ---
112
-
113
- ## ๐ŸŽฏ **Enhanced Features**
114
-
115
- ### 1. **Multiple Data Source Strategies**
116
-
117
- #### Static Data (Perfect for Prototyping)
118
- ```tsx
119
- <CrudTable
120
- hookConfig={{
121
- staticData: mockUsers,
122
- optimisticUpdates: true,
123
- }}
124
- // ... other props
125
- />
126
- ```
27
+ Most table libraries hand you a grid and leave the CRUD to you. This one takes a
28
+ column schema and derives the whole surface from it โ€” the cell renderer, the form
29
+ control, the validation, and the value conversion in both directions.
127
30
 
128
- #### API Integration (Production Ready)
129
31
  ```tsx
130
- <CrudTable
32
+ <CrudTable<User, 'id'>
33
+ title="Users"
34
+ rowKey="id"
35
+ columns={[
36
+ { dataIndex: 'name', title: 'Name', fieldType: 'string', formConfig: { required: true } },
37
+ { dataIndex: 'email', title: 'Email', fieldType: 'email' },
38
+ { dataIndex: 'joined', title: 'Joined', fieldType: 'date' },
39
+ ]}
131
40
  hookConfig={{
132
41
  api: {
133
- baseUrl: 'https://api.example.com',
134
- endpoints: {
135
- list: '/users',
136
- create: '/users',
137
- update: '/users',
138
- delete: '/users',
139
- },
140
- headers: {
141
- 'Authorization': 'Bearer your-token',
142
- },
143
- transform: {
144
- response: (data) => ({
145
- data: data.users,
146
- total: data.totalCount,
147
- }),
148
- request: (data) => ({
149
- ...data,
150
- updatedAt: new Date().toISOString(),
151
- }),
152
- },
153
- },
154
- onSuccess: (operation, data) => {
155
- console.log(`${operation} completed`, data);
156
- },
157
- onError: (operation, error) => {
158
- console.error(`${operation} failed`, error);
42
+ baseUrl: '/api',
43
+ endpoints: { list: '/users', create: '/users', update: '/users', remove: '/users' },
159
44
  },
160
45
  }}
161
- // ... other props
162
46
  />
163
47
  ```
164
48
 
165
- #### Custom Operations (Maximum Flexibility)
166
- ```tsx
167
- <CrudTable
168
- hookConfig={{
169
- operations: {
170
- getList: async (params) => {
171
- const result = await myGraphQLQuery(params);
172
- return {
173
- data: result.users,
174
- total: result.totalCount,
175
- success: true,
176
- };
177
- },
178
- create: async (data) => await myCreateMutation(data),
179
- update: async (id, data) => await myUpdateMutation(id, data),
180
- delete: async (id) => await myDeleteMutation(id),
181
- },
182
- }}
183
- // ... other props
184
- />
185
- ```
49
+ That renders a searchable, sortable, paginated table with a working create/edit
50
+ form, confirmed deletes, and CSV/JSON/Excel export.
186
51
 
187
- ### 2. **Advanced Features**
52
+ ## Highlights
188
53
 
189
- #### Bulk Operations
190
- ```tsx
191
- <CrudTable
192
- enableBulkOperations={true}
193
- // Automatically adds bulk select and delete functionality
194
- />
195
- ```
54
+ - **Strictly typed.** `dataIndex` is bound to `keyof T`, record ids are `T[K]`, and
55
+ `customRender`/`transform` receive that property's own type. No `any` in the
56
+ public API.
57
+ - **20 field types** โ€” each one entry in a registry declaring its cell renderer,
58
+ form control, implied validation and value round-trip.
59
+ - **Four data strategies** behind one `CrudDataSource` interface: static, REST,
60
+ `localStorage`, or your own. Swap backends without touching your columns.
61
+ - **Localized.** Every string is overridable, and the table follows the antd
62
+ `ConfigProvider` around it.
63
+ - **Export** to CSV, JSON and Excel, covering the whole filtered result set โ€”
64
+ with CSV formula injection neutralised.
65
+ - **Zero runtime dependencies.** Everything is a peer you already have.
196
66
 
197
- #### Custom Actions
198
- ```tsx
199
- <CrudTable
200
- customActions={(record, actions) => [
201
- <Button
202
- key="export"
203
- onClick={() => exportUser(record)}
204
- >
205
- Export
206
- </Button>,
207
- <Button
208
- key="clone"
209
- onClick={() => actions.create({...record, id: undefined})}
210
- >
211
- Clone
212
- </Button>
213
- ]}
214
- />
67
+ ## Installation
68
+
69
+ ```bash
70
+ npm install antd-crud-table
215
71
  ```
216
72
 
217
- #### Validation
218
- ```tsx
219
- columns={[
220
- {
221
- dataIndex: 'email',
222
- title: 'Email',
223
- fieldType: 'string',
224
- formConfig: {
225
- required: true,
226
- rules: [
227
- { required: true, message: 'Email is required' },
228
- { type: 'email', message: 'Invalid email format' },
229
- {
230
- validator: async (_, value) => {
231
- const exists = await checkEmailExists(value);
232
- if (exists) throw new Error('Email already exists');
233
- }
234
- }
235
- ],
236
- },
237
- },
238
- ]}
73
+ ```bash
74
+ pnpm add antd-crud-table
239
75
  ```
240
76
 
241
- ### 3. **Custom Hooks**
77
+ ### Peer dependencies
242
78
 
243
- Create your own specialized hooks for different use cases:
79
+ | Package | Version |
80
+ |---|---|
81
+ | `react`, `react-dom` | `^18` or `^19` |
82
+ | `antd` | `^6.3.6` |
83
+ | `@ant-design/icons` | `^6` |
84
+ | `@ant-design/pro-components` | `^2.8.10` |
85
+ | `dayjs` | `^1.11.13` |
244
86
 
245
- #### Example 1: useUserCrud - Specialized User Management
246
- ```tsx
247
- import { useCrudTable, type UseCrudTableConfig } from 'antd-crud-table';
87
+ ### Stylesheet
248
88
 
249
- export const useUserCrud = (baseConfig?: Partial<UseCrudTableConfig<any>['api']>) => {
250
- const config: UseCrudTableConfig<any> = {
251
- api: {
252
- baseUrl: '/api/users',
253
- headers: {
254
- 'Authorization': `Bearer ${localStorage.getItem('token')}`,
255
- },
256
- transform: {
257
- request: (data) => ({
258
- ...data,
259
- // Add default values or transformations
260
- updatedAt: new Date().toISOString(),
261
- }),
262
- response: (data) => ({
263
- data: data.users || data.data || [],
264
- total: data.total || data.count || 0,
265
- success: true,
266
- }),
267
- },
268
- ...baseConfig,
269
- },
270
- defaultPageSize: 10,
271
- optimisticUpdates: true,
272
- onSuccess: (operation, data) => {
273
- console.log(`User ${operation} completed:`, data);
274
- },
275
- onError: (operation, error) => {
276
- console.error(`User ${operation} failed:`, error);
277
- // Could add toast notifications, error reporting, etc.
278
- },
279
- };
89
+ The build extracts CSS to a separate file, so **import it once** โ€” importing the
90
+ component alone leaves the table unstyled:
280
91
 
281
- return useCrudTable('id', config);
282
- };
92
+ ```ts
93
+ import 'antd-crud-table/styles.css';
283
94
  ```
284
95
 
285
- #### Example 2: useLocalStorageCrud - Local Storage Persistence
96
+ ## Quick start
97
+
286
98
  ```tsx
287
- export const useLocalStorageCrud = <T extends Record<string, any>>(
288
- storageKey: string,
289
- rowKey: keyof T,
290
- initialData: T[] = []
291
- ) => {
292
- const getStoredData = (): T[] => {
293
- try {
294
- const stored = localStorage.getItem(storageKey);
295
- return stored ? JSON.parse(stored) : initialData;
296
- } catch {
297
- return initialData;
298
- }
299
- };
99
+ import { CrudTable } from 'antd-crud-table';
100
+ import type { CrudColumn } from 'antd-crud-table';
101
+ import 'antd-crud-table/styles.css';
102
+
103
+ interface User {
104
+ id: number;
105
+ name: string;
106
+ email: string;
107
+ status: 'active' | 'inactive';
108
+ joined: string;
109
+ }
300
110
 
301
- const setStoredData = (data: T[]) => {
302
- localStorage.setItem(storageKey, JSON.stringify(data));
303
- };
111
+ const columns: CrudColumn<User>[] = [
112
+ { dataIndex: 'name', title: 'Name', fieldType: 'string', formConfig: { required: true } },
113
+ { dataIndex: 'email', title: 'Email', fieldType: 'email' },
114
+ {
115
+ dataIndex: 'status',
116
+ title: 'Status',
117
+ fieldType: 'enum',
118
+ enumOptions: {
119
+ active: { text: 'Active', color: 'green' },
120
+ inactive: { text: 'Inactive', color: 'red' },
121
+ },
122
+ },
123
+ { dataIndex: 'joined', title: 'Joined', fieldType: 'date' },
124
+ ];
304
125
 
305
- return useCrudTable(rowKey, {
306
- operations: {
307
- getList: async (params) => {
308
- const data = getStoredData();
309
- const { current = 1, pageSize = 10, ...filters } = params;
310
-
311
- // Apply filters
312
- let filteredData = data;
313
- Object.entries(filters).forEach(([key, value]) => {
314
- if (value !== undefined && value !== null && value !== '') {
315
- filteredData = filteredData.filter(item =>
316
- String(item[key]).toLowerCase().includes(String(value).toLowerCase())
317
- );
318
- }
319
- });
320
-
321
- // Apply pagination
322
- const start = (current - 1) * pageSize;
323
- const paginatedData = filteredData.slice(start, start + pageSize);
324
-
325
- return {
326
- data: paginatedData,
327
- total: filteredData.length,
328
- success: true,
329
- };
330
- },
331
-
332
- create: async (newItem) => {
333
- const data = getStoredData();
334
- const maxId = Math.max(...data.map(item => Number(item[rowKey]) || 0), 0);
335
- const created = {
336
- [rowKey]: maxId + 1,
337
- ...newItem,
338
- createdAt: new Date().toISOString(),
339
- } as T;
340
-
341
- data.push(created);
342
- setStoredData(data);
343
- return created;
344
- },
345
-
346
- update: async (id, updateData) => {
347
- const data = getStoredData();
348
- const index = data.findIndex(item => item[rowKey] === id);
349
- if (index === -1) throw new Error('Item not found');
350
-
351
- data[index] = {
352
- ...data[index],
353
- ...updateData,
354
- updatedAt: new Date().toISOString(),
355
- };
356
- setStoredData(data);
357
- return data[index];
358
- },
359
-
360
- delete: async (id) => {
361
- const data = getStoredData();
362
- const filtered = data.filter(item => item[rowKey] !== id);
363
- setStoredData(filtered);
126
+ export const Users = () => (
127
+ <CrudTable<User, 'id'>
128
+ title="Users"
129
+ rowKey="id"
130
+ columns={columns}
131
+ defaultPageSize={10}
132
+ enableBulkOperations
133
+ hookConfig={{
134
+ api: {
135
+ baseUrl: '/api',
136
+ endpoints: { list: '/users', create: '/users', update: '/users', remove: '/users' },
364
137
  },
365
- },
366
- optimisticUpdates: true,
367
- });
368
- };
138
+ }}
139
+ />
140
+ );
369
141
  ```
370
142
 
371
- #### Example 3: useRealtimeCrud - WebSocket Integration
372
- ```tsx
373
- export const useRealtimeCrud = <T extends Record<string, any>>(
374
- rowKey: keyof T,
375
- websocketUrl: string,
376
- apiConfig: UseCrudTableConfig<T>['api']
377
- ) => {
378
- const config: UseCrudTableConfig<T> = {
379
- api: apiConfig,
380
- optimisticUpdates: false, // Disable optimistic updates for realtime
381
- };
382
-
383
- const crud = useCrudTable(rowKey, config);
384
-
385
- // In a real implementation, you would set up WebSocket listeners here
386
- // useEffect(() => {
387
- // const ws = new WebSocket(websocketUrl);
388
- //
389
- // ws.onmessage = (event) => {
390
- // const { type, data } = JSON.parse(event.data);
391
- // switch (type) {
392
- // case 'created':
393
- // case 'updated':
394
- // case 'deleted':
395
- // crud.refresh(); // Refresh data when changes occur
396
- // break;
397
- // }
398
- // };
399
- //
400
- // return () => ws.close();
401
- // }, [websocketUrl]);
402
-
403
- return crud;
404
- };
405
- ```
143
+ The second type parameter is the row key. It is what makes ids typed: `remove(id)`
144
+ takes a `number` here, not `any`.
406
145
 
407
- #### Example 4: useInfiniteScrollCrud - Infinite Scrolling
408
- ```tsx
409
- export const useInfiniteScrollCrud = <T extends Record<string, any>>(
410
- rowKey: keyof T,
411
- baseConfig: UseCrudTableConfig<T>
412
- ) => {
413
- // This would extend the base hook with infinite scroll capabilities
414
- // Implementation would handle cursor-based pagination, data accumulation, etc.
415
-
416
- const config: UseCrudTableConfig<T> = {
417
- ...baseConfig,
418
- // Add infinite scroll specific configuration
419
- };
420
-
421
- return useCrudTable(rowKey, config);
422
- };
423
- ```
146
+ ## Data strategies
424
147
 
425
- #### Example 5: useCachedCrud - Advanced Caching
426
- ```tsx
427
- export const useCachedCrud = <T extends Record<string, any>>(
428
- rowKey: keyof T,
429
- cacheKey: string,
430
- baseConfig: UseCrudTableConfig<T>
431
- ) => {
432
- const config: UseCrudTableConfig<T> = {
433
- ...baseConfig,
434
- enableCache: true,
435
- // In a real implementation, you might integrate with:
436
- // - React Query
437
- // - SWR
438
- // - Redux Toolkit Query
439
- // - Apollo Client
440
- // etc.
441
- };
442
-
443
- return useCrudTable(rowKey, config);
444
- };
445
- };
446
- ```
148
+ Every strategy implements the same `CrudDataSource<T, K>` interface, so the
149
+ columns and behaviour are identical and only the wiring differs.
150
+
151
+ ### Static data
447
152
 
448
- #### Usage Examples
449
153
  ```tsx
450
- // Using the specialized hooks
451
- const UserTable = () => {
452
- const userCrud = useUserCrud();
453
-
454
- return (
455
- <CrudTable
456
- title="Users"
457
- rowKey="id"
458
- hookConfig={userCrud}
459
- columns={userColumns}
460
- />
461
- );
462
- };
463
-
464
- const OfflineTable = () => {
465
- const offlineCrud = useLocalStorageCrud<User>('users-cache', 'id', mockUsers);
466
-
467
- return (
468
- <CrudTable
469
- title="Offline Users"
470
- rowKey="id"
471
- hookConfig={offlineCrud}
472
- columns={userColumns}
473
- />
474
- );
475
- };
476
-
477
- const RealtimeTable = () => {
478
- const realtimeCrud = useRealtimeCrud<User>(
479
- 'id',
480
- 'wss://api.example.com/ws',
481
- { baseUrl: '/api/users' }
482
- );
483
-
484
- return (
485
- <CrudTable
486
- title="Realtime Users"
487
- rowKey="id"
488
- hookConfig={realtimeCrud}
489
- columns={userColumns}
490
- />
491
- );
492
- };
154
+ hookConfig={{ staticData: users }}
493
155
  ```
494
156
 
495
- ---
157
+ In-memory, seeded once. Edits persist for the session. Good for demos, fixtures
158
+ and tests.
496
159
 
497
- ## ๐Ÿ† Complete Feature Set
498
-
499
- ### Core Features
500
- - ๐ŸŽจ **Multiple Column Types**: `string`, `number`, `boolean`, `date`, `enum`, `custom`
501
- - โœ… **Integrated Create/Edit Modal Forms** with validation
502
- - ๐Ÿš€ **ProTable Integration**: Sorting, pagination & filtering built-in
503
- - ๐Ÿ” **Real-time Data Operations** with loading states
504
- - ๐Ÿง  **Custom Transform & Render Logic** per field
505
- - ๐Ÿ“† **Smart Date/Time Handling** with `date-fns` + `dayjs`
506
- - ๐Ÿงฐ **Full TypeScript Support** with generics
507
- - ๐Ÿ” **Field-Level Edit Controls**
508
- - ๐Ÿงผ **Professional UI** with row differentiation
509
- - ๐Ÿช **Hook-Based Architecture** with `useCrudTable`
510
- - ๐Ÿ”Œ **Multiple Data Sources**: Static, API, or custom operations
511
- - โšก **Built-in State Management**: Loading, error states, optimistic updates
512
- - ๐Ÿ”ง **Extensible Design**: Create custom hooks for your domain
513
- - ๐Ÿ“Š **Performance Optimized**: Caching, lazy loading, optimistic updates
514
- - ๐ŸŽ›๏ธ **Bulk Operations**: Select and delete multiple rows
515
- - ๐ŸŽฏ **Custom Actions**: Add your own row-level actions
516
- - ๐Ÿ” **Advanced Search**: Configurable column-level search
517
- - โœจ **Enhanced Validation**: Complex form validation rules
518
-
519
- ---
520
-
521
- ## API Reference
522
-
523
- ### CrudTable Props
524
-
525
- | Prop | Type | Description |
526
- |------|------|-------------|
527
- | `title` | `string` | Table header title |
528
- | `rowKey` | `keyof T` | Unique identifier for each row |
529
- | `columns` | `CrudColumn<T>[]` | Column definitions with enhanced features |
530
- | `hookConfig` | `UseCrudTableConfig<T>` | Hook configuration for data operations |
531
- | `defaultPageSize?` | `number` | Initial page size (default: 10) |
532
- | `enableBulkOperations?` | `boolean` | Enable bulk select/delete (default: false) |
533
- | `customActions?` | `(record, actions) => ReactNode[]` | Custom row actions |
534
-
535
- ### CrudColumn<T>
536
-
537
- | Prop | Type | Description |
538
- |------|------|-------------|
539
- | `dataIndex` | `keyof T` | Field key in your data |
540
- | `title` | `string` | Column header text |
541
- | `fieldType` | `FieldType` | See the field type table below (default: `"string"`) |
542
- | `fieldEditable?` | `boolean` | Whether field can be edited (default: true) |
543
- | `searchable?` | `boolean` | Whether field appears in search (default: true) |
544
- | `enumOptions?` | `Record<string, {text: string, color?: string}>` | Options for enum fields |
545
- | `customRender?` | `(value, record) => ReactNode` | Custom display renderer |
546
- | `formConfig?` | `FormConfig` | Form field configuration |
547
-
548
- ### Field Types
549
-
550
- | `fieldType` | Stored as | Table cell | Form control |
551
- |-------------|-----------|------------|--------------|
552
- | `string` (default) | `string` | text | `Input` |
553
- | `textarea` | `string` | ellipsised text | `Input.TextArea` |
554
- | `email` | `string` | `mailto:` link (validated) | `Input` |
555
- | `url` | `string` | link (validated) | `Input` |
556
- | `password` | `string` | masked, excluded from search | `Input.Password` |
557
- | `number` | `number` | localized number | `InputNumber` |
558
- | `money` | `number` | currency (via ProTable intl) | `InputNumber` |
559
- | `percent` | `number` | percentage | `InputNumber` 0โ€“100 |
560
- | `rating` | `number` | stars | `Rate` |
561
- | `progress` | `number` | progress bar | `InputNumber` 0โ€“100 |
562
- | `date` | ISO string | `YYYY-MM-DD HH:mm` | `DatePicker` |
563
- | `time` | `HH:mm:ss` string | time | `TimePicker` |
564
- | `dateRange` | `[startISO, endISO]` | `start ~ end` | `RangePicker` |
565
- | `boolean` | `boolean` | Yes/No tag | `Switch` |
566
- | `enum` | `string` | colored tag | `Select` |
567
- | `tags` | `string[]` | tag list | `Select mode="tags"` |
568
- | `image` | URL string | 48px preview | `Input` (URL, validated) |
569
- | `color` | hex string | swatch + code | `ColorPicker` |
570
- | `json` | object | inline code | validated `Input.TextArea` |
571
- | `custom` | anything | `customRender` | `formConfig.component` |
572
-
573
- Every field type is one entry in the exported `fieldRegistry` (`lib/fields/registry.tsx`), declaring its cell render, form control, implied validation rules and recordโ†”form value conversion in one place.
574
-
575
- ### FormConfig
576
-
577
- | Prop | Type | Description |
578
- |------|------|-------------|
579
- | `required?` | `boolean` | Whether field is required |
580
- | `rules?` | `FormRule[]` | Ant Design validation rules |
581
- | `component?` | `ReactNode` | Custom form component |
582
- | `transform?` | `(value) => any` | Transform value before saving |
583
-
584
- ### UseCrudTableConfig<T>
585
-
586
- Choose one approach:
160
+ ### REST API
587
161
 
588
162
  ```tsx
589
- // Static data approach
590
- {
591
- staticData: T[];
592
- optimisticUpdates?: boolean;
593
- }
594
-
595
- // API approach
596
- {
163
+ hookConfig={{
597
164
  api: {
598
- baseUrl: string;
599
- endpoints?: {...};
600
- headers?: Record<string, string>;
601
- transform?: {...};
602
- };
603
- }
604
-
605
- // Custom operations approach
606
- {
607
- operations: {
608
- getList: (params) => Promise<{data: T[], total: number}>;
609
- create: (data: Partial<T>) => Promise<T>;
610
- update: (id, data: Partial<T>) => Promise<T>;
611
- delete: (id) => Promise<void>;
612
- };
613
- }
165
+ baseUrl: '/api',
166
+ endpoints: { list: '/users', create: '/users', update: '/users', remove: '/users' },
167
+ },
168
+ }}
614
169
  ```
615
170
 
616
- ---
171
+ Endpoints default to `/list`, `/create`, `/update` and `/delete` under `baseUrl`,
172
+ so a conventional collection needs them stated as above. Paging defaults to
173
+ `current`/`pageSize`, and updates to `PUT {update}/:id`. For an API that
174
+ speaks a different dialect, `paramNames`, `methods`, `serializeRequest` and
175
+ `parseResponse` cover most of it declaratively โ€” see
176
+ **[REST dialect recipes](./docs/rest-recipes.md)** for offset/limit, Django REST
177
+ Framework, JSON:API and auth.
617
178
 
618
- ## ๐Ÿ“ Styling
179
+ Failures throw `RestError`, carrying `status` and `body` so you can branch on a
180
+ 422 rather than parsing a message.
619
181
 
620
- Customize row striping using `.row-differentiator` in `CrudTable.css`:
182
+ ### localStorage
621
183
 
622
- ```css
623
- .row-differentiator {
624
- background-color: #fafafa;
625
- }
184
+ ```tsx
185
+ hookConfig={{ storageKey: 'my-users', initialData: seed }}
626
186
  ```
627
187
 
628
- ---
188
+ Persists across reloads and stamps `createdAt` / `updatedAt`.
629
189
 
630
- ## ๐Ÿ“Œ Notes
190
+ ### Your own
631
191
 
632
- - Date fields are handled via `dayjs` (already required by antd) in both the form and display.
633
- - All requests are async with error handling via `antd`'s `message` API.
634
- - Add your own export logic or additional toolbar buttons as needed.
635
-
636
- ---
637
-
638
- ## ๐ŸŽจ Column Type Examples
639
-
640
- ### String Field
641
192
  ```tsx
642
- {
643
- dataIndex: 'name',
644
- title: 'Full Name',
645
- fieldType: 'string',
646
- formConfig: {
647
- required: true,
648
- rules: [
649
- { min: 2, message: 'Name must be at least 2 characters' }
650
- ]
193
+ hookConfig={{
194
+ operations: {
195
+ list: async (query) => ({ items: await db.find(query), total: await db.count() }),
196
+ create: async (draft) => db.insert(draft),
197
+ update: async (id, draft) => db.update(id, draft),
198
+ remove: async (id) => db.delete(id),
651
199
  },
652
- }
200
+ }}
653
201
  ```
654
202
 
655
- ### Number Field
656
- ```tsx
657
- {
658
- dataIndex: 'age',
659
- title: 'Age',
660
- fieldType: 'number',
661
- formConfig: {
662
- rules: [
663
- { type: 'number', min: 0, max: 120, message: 'Invalid age' }
664
- ]
665
- },
203
+ Omitted operations fail with a message naming what is missing, rather than on
204
+ `undefined`. For full control, construct a `CrudDataSource` and pass it as
205
+ `dataSource`.
206
+
207
+ ## Columns
208
+
209
+ ```ts
210
+ interface CrudColumnFor<T, K extends keyof T> {
211
+ dataIndex: K; // must be a real key of T
212
+ title: string; // header, and the form label
213
+ fieldType?: FieldType; // defaults to 'string'
214
+ enumOptions?: Record<string, EnumOption>; // for 'enum'
215
+ customRender?: (value: T[K], record: T) => ReactNode;
216
+ formConfig?: {
217
+ required?: boolean;
218
+ component?: ReactNode; // replace the control entirely
219
+ transform?: (value: T[K]) => T[K]; // applied before writing
220
+ rules?: FormRule[]; // antd validation rules
221
+ };
222
+ fieldEditable?: boolean; // default true
223
+ searchable?: boolean; // default true
666
224
  }
667
225
  ```
668
226
 
669
- ### Date Field
670
- ```tsx
671
- {
672
- dataIndex: 'createdAt',
673
- title: 'Created Date',
674
- fieldType: 'date',
675
- searchable: false, // Exclude from search
676
- }
677
- ```
227
+ `CrudColumn<T>` is the union across every key of `T`, so an array annotated
228
+ `CrudColumn<User>[]` keeps each column's callbacks bound to its own property type.
229
+ Naming a property that does not exist on `T` is a compile error.
230
+
231
+ ### Field types
232
+
233
+ | Type | Cell | Form control |
234
+ |---|---|---|
235
+ | `string` | text | `Input` |
236
+ | `textarea` | truncated text | `Input.TextArea` |
237
+ | `number` | locale-grouped | `InputNumber` |
238
+ | `money` | currency | `InputNumber` (2 dp) |
239
+ | `percent` | percentage | `InputNumber` with `%` |
240
+ | `boolean` | Yes/No tag | `Switch` |
241
+ | `enum` | coloured tag | `Select` |
242
+ | `date` | formatted datetime | `DatePicker` |
243
+ | `time` | time | `TimePicker` |
244
+ | `dateRange` | `start ~ end` | `RangePicker` |
245
+ | `email` | `mailto:` link | `Input` + email rule |
246
+ | `url` | external link | `Input` + url rule |
247
+ | `password` | `โ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ข` | `Input.Password` |
248
+ | `rating` | `Rate` | `Rate` |
249
+ | `progress` | `Progress` | `InputNumber` |
250
+ | `tags` | tag list | tag `Select` |
251
+ | `image` | thumbnail | `Input` |
252
+ | `color` | swatch + hex | `ColorPicker` |
253
+ | `json` | inline code | monospace `TextArea` |
254
+ | `custom` | your `customRender` | your `formConfig.component` |
255
+
256
+ Each type is one entry in `fieldRegistry` declaring its renderer, control,
257
+ validation and `toFormValue`/`fromFormValue` conversion โ€” so a value survives the
258
+ round-trip into the edit form and back. Browse them all in the
259
+ [Storybook](https://maifeeulasad.github.io/antd-crud-table/storybook/).
260
+
261
+ **Security note:** `password` values are masked in the table and excluded from
262
+ exports. `url`, `email` and `image` render only `http`/`https` (plus `mailto:`)
263
+ targets โ€” a `javascript:` value renders as inert text rather than a clickable
264
+ link.
265
+
266
+ ## Options
267
+
268
+ | Prop | Type | Default | Description |
269
+ |---|---|---|---|
270
+ | `title` | `string` | โ€” | Header, and the export filename |
271
+ | `rowKey` | `K` | โ€” | Identity property |
272
+ | `columns` | `CrudColumn<T>[]` | โ€” | Column definitions |
273
+ | `hookConfig` | `UseCrudTableOptions<T, K>` | โ€” | Data strategy |
274
+ | `defaultPageSize` | `number` | `10` | Rows per page |
275
+ | `enableBulkOperations` | `boolean` | `false` | Row selection and bulk delete |
276
+ | `enableColumnSettings` | `boolean` | `true` | Column visibility and density |
277
+ | `enableExport` | `boolean` | `true` | Export menu entries |
278
+ | `exportScope` | `'all' \| 'page'` | `'all'` | Whole result set, or visible rows |
279
+ | `customActions` | `(record, actions) => ReactNode[]` | โ€” | Extra row controls |
280
+ | `locale` | `PartialCrudTableLocale` | English | String overrides |
281
+
282
+ ## Localization
283
+
284
+ The table follows the antd `ConfigProvider` around it, so setting your app locale
285
+ once localizes pagination, date pickers, empty states and the ProTable chrome:
678
286
 
679
- ### Boolean Field
680
287
  ```tsx
681
- {
682
- dataIndex: 'isActive',
683
- title: 'Active Status',
684
- fieldType: 'boolean',
685
- }
686
- ```
288
+ import { ConfigProvider } from 'antd';
289
+ import frFR from 'antd/locale/fr_FR';
687
290
 
688
- ### Enum Field
689
- ```tsx
690
- {
691
- dataIndex: 'status',
692
- title: 'Status',
693
- fieldType: 'enum',
694
- enumOptions: {
695
- active: { text: 'Active', color: 'green' },
696
- pending: { text: 'Pending', color: 'orange' },
697
- inactive: { text: 'Inactive', color: 'red' },
698
- },
699
- }
291
+ <ConfigProvider locale={frFR}>
292
+ <CrudTable {...config} />
293
+ </ConfigProvider>
700
294
  ```
701
295
 
702
- ### Custom Field
296
+ **With no `ConfigProvider` in the tree the table supplies English itself** โ€” antd
297
+ components otherwise fall back to their own built-in defaults.
298
+
299
+ The library's own wording comes from the `locale` prop. Supply only what you want
300
+ to change; anything omitted stays English:
301
+
703
302
  ```tsx
704
- {
705
- dataIndex: 'customField',
706
- title: 'Custom Display',
707
- fieldType: 'custom',
708
- customRender: (value, record) => (
709
- <div>
710
- <Avatar src={record.avatar} />
711
- <span>{record.name}</span>
712
- </div>
713
- ),
714
- formConfig: {
715
- component: <MyCustomInput />,
716
- },
717
- }
303
+ <CrudTable
304
+ locale={{
305
+ actions: 'Aktionen',
306
+ edit: 'Bearbeiten',
307
+ delete: 'Lรถschen',
308
+ create: 'Neu',
309
+ confirmDeleteTitle: 'Wirklich lรถschen?',
310
+ deleteSelected: (count) => `${count} entfernen`,
311
+ }}
312
+ {...config}
313
+ />
718
314
  ```
719
315
 
720
- ---
316
+ Interpolated strings are functions rather than templates with placeholders, so a
317
+ translation cannot silently drop a value or reorder its arguments. The full
318
+ contract is `CrudTableLocale`; `enUS` is the exported default.
721
319
 
722
- ## ๐Ÿงช Testing
320
+ ## Export
723
321
 
724
- The hook-based architecture enables easy testing:
322
+ The toolbar menu writes **the whole filtered result set**, not just the visible
323
+ page, using the data source's `listAll`. The labels state which they will do โ€”
324
+ `Export all as CSV` or `Export page as CSV` when the source cannot list without
325
+ pagination. Set `exportScope: 'page'` to opt out.
725
326
 
726
- ```tsx
727
- import { renderHook, act } from '@testing-library/react';
728
- import { useCrudTable } from 'antd-crud-table';
729
-
730
- test('should handle CRUD operations', async () => {
731
- const mockData = [
732
- { id: 1, name: 'John', age: 30 }
733
- ];
734
-
735
- const { result } = renderHook(() =>
736
- useCrudTable('id', {
737
- staticData: mockData,
738
- })
739
- );
740
-
741
- await act(async () => {
742
- const created = await result.current.create({
743
- name: 'Jane',
744
- age: 25
745
- });
746
- expect(created).toBeTruthy();
747
- });
748
-
749
- expect(result.current.state.data).toHaveLength(2);
750
- });
751
- ```
327
+ | Format | Output |
328
+ |---|---|
329
+ | `csv` | `.csv`, formula-injection safe |
330
+ | `json` | `.json`, the raw records |
331
+ | `excel` | `.xls`, Excel 2003 SpreadsheetML |
752
332
 
753
- ---
333
+ Cells beginning `=`, `+`, `-`, `@`, tab or CR are prefixed so spreadsheets treat
334
+ them as text. Quoting alone does not prevent evaluation, and row content in a CRUD
335
+ table is exactly the untrusted input an attacker controls. Genuine numbers are
336
+ left alone.
754
337
 
755
- ## ๐Ÿš€ Performance Tips
338
+ > **Excel note:** `excel` emits SpreadsheetML in a `.xls` file rather than OOXML,
339
+ > which would mean taking on a ZIP implementation. Excel 2016+ shows a
340
+ > format/extension mismatch prompt on open; the file is intact. Use CSV if you
341
+ > need a prompt-free export.
756
342
 
757
- ### 1. **Use Static Data for Prototyping**
758
- ```tsx
759
- // Perfect for demos and development
760
- hookConfig={{ staticData: mockData }}
761
- ```
343
+ ## Using the hook directly
762
344
 
763
- ### 2. **Enable Optimistic Updates**
764
- ```tsx
765
- // For better UX with reliable backends
766
- hookConfig={{
767
- api: {...},
768
- optimisticUpdates: true
769
- }}
770
- ```
345
+ `useCrudTable` works without the table when you want your own UI:
771
346
 
772
- ### 3. **Implement Proper Caching**
773
347
  ```tsx
774
- // Custom hook with caching
775
- const useUserCrud = () => {
776
- return useCrudTable('id', {
777
- enableCache: true,
778
- // ... other config
779
- });
780
- };
781
- ```
348
+ const crud = useCrudTable<User, 'id'>('id', { storageKey: 'users' });
782
349
 
783
- ### 4. **Lazy Load Components**
784
- ```tsx
785
- import { CrudTableLazy } from 'antd-crud-table';
786
- // Component will be loaded when needed
350
+ crud.state; // { loading, error, data, total, page, pageSize }
351
+ await crud.create({ name: 'Ada' });
352
+ await crud.update(1, { name: 'Ada L.' });
353
+ await crud.remove(1);
787
354
  ```
788
355
 
789
- ---
790
-
791
- ## ๐Ÿ”ฎ Roadmap
792
-
793
- ### Coming Soon
794
- - ๐ŸŒ **WebSocket Integration**: Real-time updates
795
- - ๐Ÿ“Š **Virtual Scrolling**: Handle thousands of rows
796
- - ๐Ÿ“ค **Export Functionality**: CSV/Excel export
797
- - ๐ŸŽจ **Theme Support**: Multiple UI themes
798
- - ๐Ÿ” **Advanced Filters**: Complex filtering UI
799
- - ๐Ÿ“ฑ **Mobile Optimization**: Better mobile experience
356
+ `onSuccess` and `onError` fire for every operation โ€” including list failures, with
357
+ the original `Error` rather than a flattened string.
800
358
 
801
- ### Community Requests
802
- - ๐Ÿ”ง **Plugin System**: Extensible architecture
803
- - ๐Ÿ“ˆ **Analytics Integration**: Built-in tracking
804
- - ๐ŸŒ **i18n Support**: Multi-language support
359
+ ## Documentation
805
360
 
806
- ---
807
-
808
- ## ๐Ÿค Contributing
361
+ | | |
362
+ |---|---|
363
+ | [Live demo](https://maifeeulasad.github.io/antd-crud-table/) | Every strategy, running |
364
+ | [Storybook](https://maifeeulasad.github.io/antd-crud-table/storybook/) | One story per field type |
365
+ | [API reference](https://maifeeulasad.github.io/antd-crud-table/api/) | Generated from the types |
366
+ | [REST recipes](./docs/rest-recipes.md) | Non-default API dialects |
367
+ | [Migration guide](./MIGRATION.md) | Upgrading from 0.5.x |
368
+ | [Changelog](./CHANGELOG.md) | Release history |
809
369
 
810
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md).
370
+ ## Development
811
371
 
812
- ### Development Setup
813
372
  ```bash
814
- git clone https://github.com/maifeeulasad/antd-crud-table
815
- cd antd-crud-table
816
- npm install
817
- npm run dev
373
+ pnpm install
374
+ pnpm dev # demo app
375
+ pnpm storybook # component playground
376
+ pnpm test # unit and component tests
377
+ pnpm test:coverage # with enforced thresholds
378
+ pnpm lint
379
+ pnpm build:lib # the published package
380
+ pnpm build:site # demo + storybook + api reference
818
381
  ```
819
382
 
820
- ---
821
-
822
- ## ๐Ÿ“„ License
823
-
824
- MIT License - feel free to use in personal and commercial projects.
825
-
826
- ---
827
-
828
- ๐ŸŽ‰ Build elegant CRUD interfaces faster than ever with `antd-crud-table`!
829
-
830
-
831
- ---
383
+ Contributions are welcome. Tests and lint run on every pull request against both
384
+ React 18 and 19.
832
385
 
833
- ## References
386
+ ## License
834
387
 
835
- - NPM: https://www.npmjs.com/package/antd-crud-table/
836
- - GitHub: https://github.com/maifeeulasad/antd-crud-table/
837
- - GitHub page (Live Demo): https://maifeeulasad.github.io/antd-crud-table/
388
+ [MIT](./LICENSE) ยฉ [Maifee Ul Asad](https://github.com/maifeeulasad)