antd-crud-table 0.5.0 โ†’ 0.8.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 (54) hide show
  1. package/README.md +326 -738
  2. package/dist/CrudImportModal.d.ts +31 -0
  3. package/dist/CrudTable-BfVtTGYV.cjs +3 -0
  4. package/dist/CrudTable-jy8Db0uQ.js +1072 -0
  5. package/dist/CrudTable.cjs +1 -1
  6. package/dist/CrudTable.d.ts +94 -17
  7. package/dist/CrudTable.js +1 -1
  8. package/dist/CrudTableLazy.cjs +1 -1
  9. package/dist/CrudTableLazy.d.ts +4 -3
  10. package/dist/CrudTableLazy.js +8 -7
  11. package/dist/core/CustomDataSource.d.ts +35 -0
  12. package/dist/core/InMemoryDataSource.d.ts +30 -0
  13. package/dist/core/LocalStorageDataSource.d.ts +30 -0
  14. package/dist/core/RestDataSource.d.ts +209 -0
  15. package/dist/core/StaticDataSource.d.ts +20 -0
  16. package/dist/core/identity.d.ts +28 -0
  17. package/dist/core/inMemoryQuery.d.ts +26 -0
  18. package/dist/core/index.d.ts +11 -0
  19. package/dist/core/types.d.ts +94 -0
  20. package/dist/en_US-DghHhwRq.js +60 -0
  21. package/dist/en_US-Q8nDeJhg.cjs +1 -0
  22. package/dist/exportData.cjs +3 -3
  23. package/dist/exportData.js +37 -24
  24. package/dist/fields/registry.d.ts +29 -17
  25. package/dist/fields/types.d.ts +29 -0
  26. package/dist/hooks/useCrudTable.d.ts +114 -58
  27. package/dist/hooks/useLocalStorageCrud.d.ts +12 -12
  28. package/dist/index.cjs +1 -1
  29. package/dist/index.d.ts +19 -8
  30. package/dist/index.js +50 -6
  31. package/dist/locale/en_US.d.ts +10 -0
  32. package/dist/locale/index.d.ts +4 -0
  33. package/dist/locale/types.d.ts +127 -0
  34. package/dist/locale/useResolvedLocale.d.ts +35 -0
  35. package/dist/useCrudTable-BVXxakfl.js +497 -0
  36. package/dist/useCrudTable-Ex2Imgll.cjs +1 -0
  37. package/dist/useCrudTable.cjs +1 -1
  38. package/dist/useCrudTable.js +2 -2
  39. package/dist/useLocalStorageCrud.cjs +1 -1
  40. package/dist/useLocalStorageCrud.js +12 -51
  41. package/dist/utils/exportData.d.ts +35 -14
  42. package/dist/utils/importData.d.ts +130 -0
  43. package/dist/utils/importSpreadsheetML.d.ts +8 -0
  44. package/dist/utils/importXlsx.d.ts +7 -0
  45. package/package.json +35 -9
  46. package/dist/CrudTable-DUfRGJW2.js +0 -524
  47. package/dist/CrudTable-TTRqkEoZ.cjs +0 -1
  48. package/dist/hooks/useCrudTable.test.d.ts +0 -1
  49. package/dist/hooks/useLocalStorageCrud.test.d.ts +0 -1
  50. package/dist/useCrudTable-CEY_riQg.cjs +0 -1
  51. package/dist/useCrudTable-DpF-oZHz.js +0 -246
  52. package/dist/utils/exportData.test.d.ts +0 -1
  53. package/dist/utils/query.d.ts +0 -13
  54. package/dist/utils/query.test.d.ts +0 -1
package/README.md CHANGED
@@ -1,837 +1,425 @@
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
21
+ </div>
26
22
 
27
23
  ---
28
24
 
29
- ## ๐Ÿ“ฆ Installation
25
+ ## Why
26
+
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.
30
+
31
+ ```tsx
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
+ ]}
40
+ hookConfig={{
41
+ api: {
42
+ baseUrl: '/api',
43
+ endpoints: { list: '/users', create: '/users', update: '/users', remove: '/users' },
44
+ },
45
+ }}
46
+ />
47
+ ```
48
+
49
+ That renders a searchable, sortable, paginated table with a working create/edit
50
+ form, confirmed deletes, and CSV/JSON/Excel export.
51
+
52
+ ## Highlights
53
+
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.
66
+
67
+ ## Installation
30
68
 
31
69
  ```bash
32
70
  npm install antd-crud-table
33
71
  ```
34
72
 
35
- **Peer Dependencies:**
36
73
  ```bash
37
- npm install react react-dom antd @ant-design/pro-components
74
+ pnpm add antd-crud-table
38
75
  ```
39
76
 
40
- ---
77
+ ### Peer dependencies
41
78
 
42
- ## ๐Ÿš€ Quick Start
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` |
43
86
 
44
- Choose your preferred approach:
87
+ ### Stylesheet
45
88
 
46
- ### Modern Approach - Hook-Based
89
+ The build extracts CSS to a separate file, so **import it once** โ€” importing the
90
+ component alone leaves the table unstyled:
47
91
 
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
- );
92
+ ```ts
93
+ import 'antd-crud-table/styles.css';
79
94
  ```
80
95
 
81
- ### Classic Approach (Original) - Service-Based
96
+ ## Quick start
82
97
 
83
98
  ```tsx
84
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
+ }
85
110
 
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
- };
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
+ ];
92
125
 
93
- const UserTable = () => (
94
- <CrudTable
95
- title="User Management"
126
+ export const Users = () => (
127
+ <CrudTable<User, 'id'>
128
+ title="Users"
96
129
  rowKey="id"
97
- service={userService}
98
- columns={[
99
- {
100
- title: 'Name',
101
- dataIndex: 'name',
102
- fieldType: 'string',
103
- fieldEditable: true,
104
- formConfig: { required: true },
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' },
105
137
  },
106
- ]}
138
+ }}
107
139
  />
108
140
  );
109
141
  ```
110
142
 
111
- ---
143
+ The second type parameter is the row key. It is what makes ids typed: `remove(id)`
144
+ takes a `number` here, not `any`.
112
145
 
113
- ## ๐ŸŽฏ **Enhanced Features**
146
+ ## Data strategies
114
147
 
115
- ### 1. **Multiple Data Source Strategies**
148
+ Every strategy implements the same `CrudDataSource<T, K>` interface, so the
149
+ columns and behaviour are identical and only the wiring differs.
116
150
 
117
- #### Static Data (Perfect for Prototyping)
118
- ```tsx
119
- <CrudTable
120
- hookConfig={{
121
- staticData: mockUsers,
122
- optimisticUpdates: true,
123
- }}
124
- // ... other props
125
- />
126
- ```
151
+ ### Static data
127
152
 
128
- #### API Integration (Production Ready)
129
153
  ```tsx
130
- <CrudTable
131
- hookConfig={{
132
- 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);
159
- },
160
- }}
161
- // ... other props
162
- />
163
- ```
164
-
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
- />
154
+ hookConfig={{ staticData: users }}
185
155
  ```
186
156
 
187
- ### 2. **Advanced Features**
157
+ In-memory, seeded once. Edits persist for the session. Good for demos, fixtures
158
+ and tests.
188
159
 
189
- #### Bulk Operations
190
- ```tsx
191
- <CrudTable
192
- enableBulkOperations={true}
193
- // Automatically adds bulk select and delete functionality
194
- />
195
- ```
196
-
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
- />
215
- ```
160
+ ### REST API
216
161
 
217
- #### Validation
218
162
  ```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
- },
163
+ hookConfig={{
164
+ api: {
165
+ baseUrl: '/api',
166
+ endpoints: { list: '/users', create: '/users', update: '/users', remove: '/users' },
237
167
  },
238
- ]}
168
+ }}
239
169
  ```
240
170
 
241
- ### 3. **Custom Hooks**
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.
242
178
 
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
- };
179
+ Failures throw `RestError`, carrying `status` and `body` so you can branch on a
180
+ 422 rather than parsing a message.
280
181
 
281
- return useCrudTable('id', config);
282
- };
283
- ```
182
+ ### localStorage
284
183
 
285
- #### Example 2: useLocalStorageCrud - Local Storage Persistence
286
184
  ```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
- };
185
+ hookConfig={{ storageKey: 'my-users', initialData: seed }}
369
186
  ```
370
187
 
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
- ```
406
-
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
- ```
188
+ Persists across reloads and stamps `createdAt` / `updatedAt`.
424
189
 
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
- ```
190
+ ### Your own
447
191
 
448
- #### Usage Examples
449
192
  ```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
- };
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),
199
+ },
200
+ }}
493
201
  ```
494
202
 
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
- - ๐Ÿช **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:
587
-
588
- ```tsx
589
- // Static data approach
590
- {
591
- staticData: T[];
592
- optimisticUpdates?: boolean;
593
- }
594
-
595
- // API approach
596
- {
597
- 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>;
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
612
221
  };
222
+ fieldEditable?: boolean; // default true
223
+ searchable?: boolean; // default true
613
224
  }
614
225
  ```
615
226
 
616
- ---
617
-
618
- ## ๐Ÿ“ Styling
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
+ | `enableImport` | `boolean` | `false` | Import menu entry (CSV / `.xls` / `.xlsx`) |
280
+ | `importConcurrency` | `number` | `5` | Max simultaneous creates during import |
281
+ | `customActions` | `(record, actions) => ReactNode[]` | โ€” | Extra row controls |
282
+ | `locale` | `PartialCrudTableLocale` | English | String overrides |
283
+
284
+ ## Localization
285
+
286
+ The table follows the antd `ConfigProvider` around it, so setting your app locale
287
+ once localizes pagination, date pickers, empty states and the ProTable chrome:
619
288
 
620
- Customize row striping using `.row-differentiator` in `CrudTable.css`:
289
+ ```tsx
290
+ import { ConfigProvider } from 'antd';
291
+ import frFR from 'antd/locale/fr_FR';
621
292
 
622
- ```css
623
- .row-differentiator {
624
- background-color: #fafafa;
625
- }
293
+ <ConfigProvider locale={frFR}>
294
+ <CrudTable {...config} />
295
+ </ConfigProvider>
626
296
  ```
627
297
 
628
- ---
629
-
630
- ## ๐Ÿ“Œ Notes
631
-
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.
298
+ **With no `ConfigProvider` in the tree the table supplies English itself** โ€” antd
299
+ components otherwise fall back to their own built-in defaults.
635
300
 
636
- ---
301
+ The library's own wording comes from the `locale` prop. Supply only what you want
302
+ to change; anything omitted stays English:
637
303
 
638
- ## ๐ŸŽจ Column Type Examples
639
-
640
- ### String Field
641
304
  ```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
- ]
651
- },
652
- }
305
+ <CrudTable
306
+ locale={{
307
+ actions: 'Aktionen',
308
+ edit: 'Bearbeiten',
309
+ delete: 'Lรถschen',
310
+ create: 'Neu',
311
+ confirmDeleteTitle: 'Wirklich lรถschen?',
312
+ deleteSelected: (count) => `${count} entfernen`,
313
+ }}
314
+ {...config}
315
+ />
653
316
  ```
654
317
 
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
- },
666
- }
667
- ```
318
+ Interpolated strings are functions rather than templates with placeholders, so a
319
+ translation cannot silently drop a value or reorder its arguments. The full
320
+ contract is `CrudTableLocale`; `enUS` is the exported default.
668
321
 
669
- ### Date Field
670
- ```tsx
671
- {
672
- dataIndex: 'createdAt',
673
- title: 'Created Date',
674
- fieldType: 'date',
675
- searchable: false, // Exclude from search
676
- }
677
- ```
322
+ ## Export
678
323
 
679
- ### Boolean Field
680
- ```tsx
681
- {
682
- dataIndex: 'isActive',
683
- title: 'Active Status',
684
- fieldType: 'boolean',
685
- }
686
- ```
324
+ The toolbar menu writes **the whole filtered result set**, not just the visible
325
+ page, using the data source's `listAll`. The labels state which they will do โ€”
326
+ `Export all as CSV` or `Export page as CSV` when the source cannot list without
327
+ pagination. Set `exportScope: 'page'` to opt out.
687
328
 
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
- }
700
- ```
329
+ | Format | Output |
330
+ |---|---|
331
+ | `csv` | `.csv`, formula-injection safe |
332
+ | `json` | `.json`, the raw records |
333
+ | `excel` | `.xls`, Excel 2003 SpreadsheetML |
701
334
 
702
- ### Custom Field
703
- ```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
- }
718
- ```
335
+ Cells beginning `=`, `+`, `-`, `@`, tab or CR are prefixed so spreadsheets treat
336
+ them as text. Quoting alone does not prevent evaluation, and row content in a CRUD
337
+ table is exactly the untrusted input an attacker controls. Genuine numbers are
338
+ left alone.
719
339
 
720
- ---
340
+ > **Excel note:** `excel` emits SpreadsheetML in a `.xls` file rather than OOXML,
341
+ > which would mean taking on a ZIP implementation. Excel 2016+ shows a
342
+ > format/extension mismatch prompt on open; the file is intact. Use CSV if you
343
+ > need a prompt-free export.
721
344
 
722
- ## ๐Ÿงช Testing
345
+ ## Import
723
346
 
724
- The hook-based architecture enables easy testing:
347
+ Set `enableImport` to add an **Import** entry to the toolbar menu. It opens a
348
+ dialog that walks a file in: pick it, map its headers onto columns (auto-matched
349
+ on header text, and editable), preview the parsed rows with **per-row validation**
350
+ using the same `formConfig.rules` the create form uses, then create the valid rows.
725
351
 
726
352
  ```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
- });
353
+ <CrudTable<User, 'id'>
354
+ title="Users"
355
+ rowKey="id"
356
+ columns={columns}
357
+ hookConfig={{ storageKey: 'users' }}
358
+ enableImport
359
+ />
751
360
  ```
752
361
 
753
- ---
362
+ | Format | Read via |
363
+ |---|---|
364
+ | `.csv` | built-in RFC 4180 parser, no dependency |
365
+ | `.xls` | the Excel 2003 SpreadsheetML this library exports, parsed as plain XML |
366
+ | `.xlsx` | real OOXML, via [`to-spreadsheet`](https://github.com/maifeeulasad/to-spreadsheet)'s `readExcel` |
754
367
 
755
- ## ๐Ÿš€ Performance Tips
368
+ Behaviour worth knowing:
756
369
 
757
- ### 1. **Use Static Data for Prototyping**
758
- ```tsx
759
- // Perfect for demos and development
760
- hookConfig={{ staticData: mockData }}
761
- ```
370
+ - **Values round-trip.** Export then import reproduces the original records. Enum
371
+ columns accept both the stored key and the exported label.
372
+ - **Partial success is honest.** Rows failing validation are skipped and reported
373
+ per row; a row that fails to create does not abort the rest.
374
+ - **Bounded concurrency.** A large file does not fire one request per row. A data
375
+ source may implement the optional `createMany(drafts)` to create server-side in
376
+ a batch; otherwise creates run through a small pool (`importConcurrency`, default
377
+ 5).
378
+ - **Passwords are never imported**, the same way they are never exported.
762
379
 
763
- ### 2. **Enable Optimistic Updates**
764
- ```tsx
765
- // For better UX with reliable backends
766
- hookConfig={{
767
- api: {...},
768
- optimisticUpdates: true
769
- }}
770
- ```
380
+ ## Using the hook directly
771
381
 
772
- ### 3. **Implement Proper Caching**
773
- ```tsx
774
- // Custom hook with caching
775
- const useUserCrud = () => {
776
- return useCrudTable('id', {
777
- enableCache: true,
778
- // ... other config
779
- });
780
- };
781
- ```
382
+ `useCrudTable` works without the table when you want your own UI:
782
383
 
783
- ### 4. **Lazy Load Components**
784
384
  ```tsx
785
- import { CrudTableLazy } from 'antd-crud-table';
786
- // Component will be loaded when needed
787
- ```
788
-
789
- ---
790
-
791
- ## ๐Ÿ”ฎ Roadmap
385
+ const crud = useCrudTable<User, 'id'>('id', { storageKey: 'users' });
792
386
 
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
387
+ crud.state; // { loading, error, data, total, page, pageSize }
388
+ await crud.create({ name: 'Ada' });
389
+ await crud.update(1, { name: 'Ada L.' });
390
+ await crud.remove(1);
391
+ ```
800
392
 
801
- ### Community Requests
802
- - ๐Ÿ”ง **Plugin System**: Extensible architecture
803
- - ๐Ÿ“ˆ **Analytics Integration**: Built-in tracking
804
- - ๐ŸŒ **i18n Support**: Multi-language support
393
+ `onSuccess` and `onError` fire for every operation โ€” including list failures, with
394
+ the original `Error` rather than a flattened string.
805
395
 
806
- ---
396
+ ## Documentation
807
397
 
808
- ## ๐Ÿค Contributing
398
+ | | |
399
+ |---|---|
400
+ | [Live demo](https://maifeeulasad.github.io/antd-crud-table/) | Every strategy, running |
401
+ | [Storybook](https://maifeeulasad.github.io/antd-crud-table/storybook/) | One story per field type |
402
+ | [API reference](https://maifeeulasad.github.io/antd-crud-table/api/) | Generated from the types |
403
+ | [REST recipes](./docs/rest-recipes.md) | Non-default API dialects |
404
+ | [Migration guide](./MIGRATION.md) | Upgrading from 0.5.x |
405
+ | [Changelog](./CHANGELOG.md) | Release history |
809
406
 
810
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md).
407
+ ## Development
811
408
 
812
- ### Development Setup
813
409
  ```bash
814
- git clone https://github.com/maifeeulasad/antd-crud-table
815
- cd antd-crud-table
816
- npm install
817
- npm run dev
410
+ pnpm install
411
+ pnpm dev # demo app
412
+ pnpm storybook # component playground
413
+ pnpm test # unit and component tests
414
+ pnpm test:coverage # with enforced thresholds
415
+ pnpm lint
416
+ pnpm build:lib # the published package
417
+ pnpm build:site # demo + storybook + api reference
818
418
  ```
819
419
 
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
- ---
420
+ Contributions are welcome. Tests and lint run on every pull request against both
421
+ React 18 and 19.
832
422
 
833
- ## References
423
+ ## License
834
424
 
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/
425
+ [MIT](./LICENSE) ยฉ [Maifee Ul Asad](https://github.com/maifeeulasad)