antd-crud-table 0.4.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 (41) hide show
  1. package/README.md +293 -783
  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 +85 -18
  6. package/dist/CrudTable.js +3 -306
  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 +13 -13
  22. package/dist/exportData.js +48 -39
  23. package/dist/fields/registry.d.ts +38 -0
  24. package/dist/fields/types.d.ts +29 -0
  25. package/dist/hooks/useCrudTable.d.ts +114 -57
  26. package/dist/hooks/useLocalStorageCrud.d.ts +14 -9
  27. package/dist/index.cjs +1 -0
  28. package/dist/index.d.ts +14 -6
  29. package/dist/index.js +7 -0
  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 -235
  38. package/dist/useLocalStorageCrud.cjs +1 -1
  39. package/dist/useLocalStorageCrud.js +12 -198
  40. package/dist/utils/exportData.d.ts +37 -12
  41. package/package.json +52 -16
package/README.md CHANGED
@@ -1,878 +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
- ## ๐Ÿ†• Enhanced ๐Ÿ“‹ 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
- ### CrudTableExperimental Props (Enhanced)
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 enhanced features |
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
- The experimental 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
25
+ ## Why
43
26
 
44
- Choose your preferred approach:
45
-
46
- ### Modern Approach (Experimental) - Hook-Based
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.
47
30
 
48
31
  ```tsx
49
- import { CrudTableExperimental } from 'antd-crud-table';
50
-
51
- // Static data example
52
- const UserManagement = () => (
53
- <CrudTableExperimental<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 (Experimental)**
114
-
115
- ### 1. **Multiple Data Source Strategies**
116
-
117
- #### Static Data (Perfect for Prototyping)
118
- ```tsx
119
- <CrudTableExperimental
120
- hookConfig={{
121
- staticData: mockUsers,
122
- optimisticUpdates: true,
123
- }}
124
- // ... other props
125
- />
126
- ```
127
-
128
- #### API Integration (Production Ready)
129
- ```tsx
130
- <CrudTableExperimental
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
- <CrudTableExperimental
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
- <CrudTableExperimental
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
- <CrudTableExperimental
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
- />
215
- ```
67
+ ## Installation
216
68
 
217
- #### Enhanced 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
- ]}
69
+ ```bash
70
+ npm install antd-crud-table
239
71
  ```
240
72
 
241
- ### 3. **Custom Hooks**
242
-
243
- Create your own specialized hooks for different use cases:
244
-
245
- #### Example 1: useUserCrud - Specialized User Management
246
- ```tsx
247
- import { useCrudTable, type UseCrudTableConfig } from 'antd-crud-table';
248
-
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
- };
280
-
281
- return useCrudTable('id', config);
282
- };
73
+ ```bash
74
+ pnpm add antd-crud-table
283
75
  ```
284
76
 
285
- #### Example 2: useLocalStorageCrud - Local Storage Persistence
286
- ```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
- };
300
-
301
- const setStoredData = (data: T[]) => {
302
- localStorage.setItem(storageKey, JSON.stringify(data));
303
- };
304
-
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);
364
- },
365
- },
366
- optimisticUpdates: true,
367
- });
368
- };
369
- ```
77
+ ### Peer dependencies
370
78
 
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
- ```
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` |
406
86
 
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
- ```
87
+ ### Stylesheet
424
88
 
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
- ```
89
+ The build extracts CSS to a separate file, so **import it once** โ€” importing the
90
+ component alone leaves the table unstyled:
447
91
 
448
- #### Usage Examples
449
- ```tsx
450
- // Using the specialized hooks
451
- const UserTable = () => {
452
- const userCrud = useUserCrud();
453
-
454
- return (
455
- <CrudTableExperimental
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
- <CrudTableExperimental
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
- <CrudTableExperimental
486
- title="Realtime Users"
487
- rowKey="id"
488
- hookConfig={realtimeCrud}
489
- columns={userColumns}
490
- />
491
- );
492
- };
92
+ ```ts
93
+ import 'antd-crud-table/styles.css';
493
94
  ```
494
95
 
495
- ---
496
-
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
-
510
- ### Enhanced Features (Experimental)
511
- - ๐Ÿช **Hook-Based Architecture** with `useCrudTable`
512
- - ๐Ÿ”Œ **Multiple Data Sources**: Static, API, or custom operations
513
- - โšก **Built-in State Management**: Loading, error states, optimistic updates
514
- - ๐Ÿ”ง **Extensible Design**: Create custom hooks for your domain
515
- - ๐Ÿ“Š **Performance Optimized**: Caching, lazy loading, optimistic updates
516
- - ๐ŸŽ›๏ธ **Bulk Operations**: Select and delete multiple rows
517
- - ๐ŸŽฏ **Custom Actions**: Add your own row-level actions
518
- - ๐Ÿ” **Advanced Search**: Configurable column-level search
519
- - โœจ **Enhanced Validation**: Complex form validation rules
520
-
521
- ---
522
-
523
- ## API Reference
524
-
525
- ### CrudTableV2 Props (Enhanced)
526
-
527
- | Prop | Type | Description |
528
- |------|------|-------------|
529
- | `title` | `string` | Table header title |
530
- | `rowKey` | `keyof T` | Unique identifier for each row |
531
- | `columns` | `CrudColumn<T>[]` | Column definitions with enhanced features |
532
- | `hookConfig` | `UseCrudTableConfig<T>` | Hook configuration for data operations |
533
- | `defaultPageSize?` | `number` | Initial page size (default: 10) |
534
- | `enableBulkOperations?` | `boolean` | Enable bulk select/delete (default: false) |
535
- | `customActions?` | `(record, actions) => ReactNode[]` | Custom row actions |
536
-
537
- ### CrudColumn<T> (Enhanced)
538
-
539
- | Prop | Type | Description |
540
- |------|------|-------------|
541
- | `dataIndex` | `keyof T` | Field key in your data |
542
- | `title` | `string` | Column header text |
543
- | `fieldType` | `FieldType` | `"string" \| "number" \| "boolean" \| "date" \| "enum" \| "custom"` |
544
- | `fieldEditable?` | `boolean` | Whether field can be edited (default: true) |
545
- | `searchable?` | `boolean` | Whether field appears in search (default: true) |
546
- | `enumOptions?` | `Record<string, {text: string, color?: string}>` | Options for enum fields |
547
- | `customRender?` | `(value, record) => ReactNode` | Custom display renderer |
548
- | `formConfig?` | `FormConfig` | Form field configuration |
549
-
550
- ### FormConfig
551
-
552
- | Prop | Type | Description |
553
- |------|------|-------------|
554
- | `required?` | `boolean` | Whether field is required |
555
- | `rules?` | `FormRule[]` | Ant Design validation rules |
556
- | `component?` | `ReactNode` | Custom form component |
557
- | `transform?` | `(value) => any` | Transform value before saving |
558
-
559
- ### UseCrudTableConfig<T>
560
-
561
- Choose one approach:
96
+ ## Quick start
562
97
 
563
98
  ```tsx
564
- // Static data approach
565
- {
566
- staticData: T[];
567
- optimisticUpdates?: boolean;
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;
568
109
  }
569
110
 
570
- // API approach
571
- {
572
- api: {
573
- baseUrl: string;
574
- endpoints?: {...};
575
- headers?: Record<string, string>;
576
- transform?: {...};
577
- };
578
- }
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
+ ];
579
125
 
580
- // Custom operations approach
581
- {
582
- operations: {
583
- getList: (params) => Promise<{data: T[], total: number}>;
584
- create: (data: Partial<T>) => Promise<T>;
585
- update: (id, data: Partial<T>) => Promise<T>;
586
- delete: (id) => Promise<void>;
587
- };
588
- }
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' },
137
+ },
138
+ }}
139
+ />
140
+ );
589
141
  ```
590
142
 
591
- ### Legacy CrudTable Props (Original)
143
+ The second type parameter is the row key. It is what makes ids typed: `remove(id)`
144
+ takes a `number` here, not `any`.
592
145
 
593
- | Prop | Type | Description |
594
- |------|------|-------------|
595
- | `columns` | `CrudColumn<T>[]` | Column definitions |
596
- | `service` | `CrudService<T>` | Service object with CRUD methods |
597
- | `rowKey` | `keyof T` | Unique key for each row |
598
- | `title` | `string` | Table header title |
599
- | `defaultPageSize?` | `number` | Optional default page size (default: 5) |
146
+ ## Data strategies
600
147
 
601
- ---
148
+ Every strategy implements the same `CrudDataSource<T, K>` interface, so the
149
+ columns and behaviour are identical and only the wiring differs.
602
150
 
603
- ## ๐Ÿ“ Styling
151
+ ### Static data
604
152
 
605
- Customize row striping using `.row-differentiator` in `CrudTable.css`:
606
-
607
- ```css
608
- .row-differentiator {
609
- background-color: #fafafa;
610
- }
153
+ ```tsx
154
+ hookConfig={{ staticData: users }}
611
155
  ```
612
156
 
613
- ---
614
-
615
- ## ๐Ÿ“Œ Notes
616
-
617
- - Date fields are handled via `dayjs` in the form and `date-fns` for display.
618
- - All requests are async with error handling via `antd`'s `message` API.
619
- - Add your own export logic or additional toolbar buttons as needed.
620
-
621
- ---
622
-
623
- ## ๐Ÿ”„ Migration Guide
157
+ In-memory, seeded once. Edits persist for the session. Good for demos, fixtures
158
+ and tests.
624
159
 
625
- ### Upgrading from Original to Experimental
160
+ ### REST API
626
161
 
627
- **Original (Service-Based):**
628
162
  ```tsx
629
- <CrudTable
630
- title="Users"
631
- rowKey="id"
632
- service={UserService}
633
- columns={columns}
634
- />
635
- ```
636
-
637
- **Experimental (Hook-Based):**
638
- ```tsx
639
- <CrudTableExperimental
640
- title="Users"
641
- rowKey="id"
642
- hookConfig={{
643
- operations: UserService, // Reuse existing service
644
- // Or choose new approaches:
645
- // staticData: users,
646
- // api: { baseUrl: '/api' },
647
- }}
648
- columns={columns}
649
- />
163
+ hookConfig={{
164
+ api: {
165
+ baseUrl: '/api',
166
+ endpoints: { list: '/users', create: '/users', update: '/users', remove: '/users' },
167
+ },
168
+ }}
650
169
  ```
651
170
 
652
- ### Breaking Changes in Experimental
653
- - โœ… **Fully backward compatible**: Original components still work
654
- - ๐Ÿ”„ **New import**: `CrudTableExperimental` for enhanced version
655
- - ๐ŸŽ›๏ธ **Service โ†’ hookConfig**: More flexible configuration
656
- - ๐Ÿ“Š **Enhanced props**: Additional optional features
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.
657
178
 
658
- ### Lazy Loading Options
179
+ Failures throw `RestError`, carrying `status` and `body` so you can branch on a
180
+ 422 rather than parsing a message.
659
181
 
660
- For better performance with code splitting:
182
+ ### localStorage
661
183
 
662
184
  ```tsx
663
- // Standard lazy loading
664
- import { CrudTableLazy } from 'antd-crud-table';
665
-
666
- // Experimental lazy loading
667
- import { CrudTableExperimentalLazy } from 'antd-crud-table';
668
-
669
- <CrudTableExperimentalLazy
670
- title="Users"
671
- rowKey="id"
672
- hookConfig={hookConfig}
673
- columns={columns}
674
- />
185
+ hookConfig={{ storageKey: 'my-users', initialData: seed }}
675
186
  ```
676
187
 
677
- ---
188
+ Persists across reloads and stamps `createdAt` / `updatedAt`.
678
189
 
679
- ## ๐ŸŽจ Column Type Examples
190
+ ### Your own
680
191
 
681
- ### String Field
682
192
  ```tsx
683
- {
684
- dataIndex: 'name',
685
- title: 'Full Name',
686
- fieldType: 'string',
687
- formConfig: {
688
- required: true,
689
- rules: [
690
- { min: 2, message: 'Name must be at least 2 characters' }
691
- ]
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),
692
199
  },
693
- }
200
+ }}
694
201
  ```
695
202
 
696
- ### Number Field
697
- ```tsx
698
- {
699
- dataIndex: 'age',
700
- title: 'Age',
701
- fieldType: 'number',
702
- formConfig: {
703
- rules: [
704
- { type: 'number', min: 0, max: 120, message: 'Invalid age' }
705
- ]
706
- },
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
707
224
  }
708
225
  ```
709
226
 
710
- ### Date Field
711
- ```tsx
712
- {
713
- dataIndex: 'createdAt',
714
- title: 'Created Date',
715
- fieldType: 'date',
716
- searchable: false, // Exclude from search
717
- }
718
- ```
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:
719
286
 
720
- ### Boolean Field
721
287
  ```tsx
722
- {
723
- dataIndex: 'isActive',
724
- title: 'Active Status',
725
- fieldType: 'boolean',
726
- }
727
- ```
288
+ import { ConfigProvider } from 'antd';
289
+ import frFR from 'antd/locale/fr_FR';
728
290
 
729
- ### Enum Field
730
- ```tsx
731
- {
732
- dataIndex: 'status',
733
- title: 'Status',
734
- fieldType: 'enum',
735
- enumOptions: {
736
- active: { text: 'Active', color: 'green' },
737
- pending: { text: 'Pending', color: 'orange' },
738
- inactive: { text: 'Inactive', color: 'red' },
739
- },
740
- }
291
+ <ConfigProvider locale={frFR}>
292
+ <CrudTable {...config} />
293
+ </ConfigProvider>
741
294
  ```
742
295
 
743
- ### 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
+
744
302
  ```tsx
745
- {
746
- dataIndex: 'customField',
747
- title: 'Custom Display',
748
- fieldType: 'custom',
749
- customRender: (value, record) => (
750
- <div>
751
- <Avatar src={record.avatar} />
752
- <span>{record.name}</span>
753
- </div>
754
- ),
755
- formConfig: {
756
- component: <MyCustomInput />,
757
- },
758
- }
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
+ />
759
314
  ```
760
315
 
761
- ---
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.
762
319
 
763
- ## ๐Ÿงช Testing
320
+ ## Export
764
321
 
765
- 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.
766
326
 
767
- ```tsx
768
- import { renderHook, act } from '@testing-library/react';
769
- import { useCrudTable } from 'antd-crud-table';
770
-
771
- test('should handle CRUD operations', async () => {
772
- const mockData = [
773
- { id: 1, name: 'John', age: 30 }
774
- ];
775
-
776
- const { result } = renderHook(() =>
777
- useCrudTable('id', {
778
- staticData: mockData,
779
- })
780
- );
781
-
782
- await act(async () => {
783
- const created = await result.current.create({
784
- name: 'Jane',
785
- age: 25
786
- });
787
- expect(created).toBeTruthy();
788
- });
789
-
790
- expect(result.current.state.data).toHaveLength(2);
791
- });
792
- ```
327
+ | Format | Output |
328
+ |---|---|
329
+ | `csv` | `.csv`, formula-injection safe |
330
+ | `json` | `.json`, the raw records |
331
+ | `excel` | `.xls`, Excel 2003 SpreadsheetML |
793
332
 
794
- ---
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.
795
337
 
796
- ## ๐Ÿš€ 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.
797
342
 
798
- ### 1. **Use Static Data for Prototyping**
799
- ```tsx
800
- // Perfect for demos and development
801
- hookConfig={{ staticData: mockData }}
802
- ```
343
+ ## Using the hook directly
803
344
 
804
- ### 2. **Enable Optimistic Updates**
805
- ```tsx
806
- // For better UX with reliable backends
807
- hookConfig={{
808
- api: {...},
809
- optimisticUpdates: true
810
- }}
811
- ```
345
+ `useCrudTable` works without the table when you want your own UI:
812
346
 
813
- ### 3. **Implement Proper Caching**
814
347
  ```tsx
815
- // Custom hook with caching
816
- const useUserCrud = () => {
817
- return useCrudTable('id', {
818
- enableCache: true,
819
- // ... other config
820
- });
821
- };
822
- ```
348
+ const crud = useCrudTable<User, 'id'>('id', { storageKey: 'users' });
823
349
 
824
- ### 4. **Lazy Load Components**
825
- ```tsx
826
- import { CrudTableLazy } from 'antd-crud-table';
827
- // 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);
828
354
  ```
829
355
 
830
- ---
831
-
832
- ## ๐Ÿ”ฎ Roadmap
833
-
834
- ### Coming Soon
835
- - ๐ŸŒ **WebSocket Integration**: Real-time updates
836
- - ๐Ÿ“Š **Virtual Scrolling**: Handle thousands of rows
837
- - ๐Ÿ“ค **Export Functionality**: CSV/Excel export
838
- - ๐ŸŽจ **Theme Support**: Multiple UI themes
839
- - ๐Ÿ” **Advanced Filters**: Complex filtering UI
840
- - ๐Ÿ“ฑ **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.
841
358
 
842
- ### Community Requests
843
- - ๐Ÿ”ง **Plugin System**: Extensible architecture
844
- - ๐Ÿ“ˆ **Analytics Integration**: Built-in tracking
845
- - ๐ŸŒ **i18n Support**: Multi-language support
846
-
847
- ---
359
+ ## Documentation
848
360
 
849
- ## ๐Ÿค 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 |
850
369
 
851
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md).
370
+ ## Development
852
371
 
853
- ### Development Setup
854
372
  ```bash
855
- git clone https://github.com/maifeeulasad/antd-crud-table
856
- cd antd-crud-table
857
- npm install
858
- 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
859
381
  ```
860
382
 
861
- ---
862
-
863
- ## ๐Ÿ“„ License
864
-
865
- MIT License - feel free to use in personal and commercial projects.
866
-
867
- ---
868
-
869
- ๐ŸŽ‰ Build elegant CRUD interfaces faster than ever with `antd-crud-table`!
870
-
871
-
872
- ---
383
+ Contributions are welcome. Tests and lint run on every pull request against both
384
+ React 18 and 19.
873
385
 
874
- ## References
386
+ ## License
875
387
 
876
- - NPM: https://www.npmjs.com/package/antd-crud-table/
877
- - GitHub: https://github.com/maifeeulasad/antd-crud-table/
878
- - GitHub page (Live Demo): https://maifeeulasad.github.io/antd-crud-table/
388
+ [MIT](./LICENSE) ยฉ [Maifee Ul Asad](https://github.com/maifeeulasad)