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.
- package/README.md +293 -783
- package/dist/CrudTable-Btf58zof.js +610 -0
- package/dist/CrudTable-DRN70sET.cjs +1 -0
- package/dist/CrudTable.cjs +1 -1
- package/dist/CrudTable.d.ts +85 -18
- package/dist/CrudTable.js +3 -306
- package/dist/CrudTableLazy.cjs +1 -1
- package/dist/CrudTableLazy.d.ts +4 -3
- package/dist/CrudTableLazy.js +8 -7
- package/dist/core/CustomDataSource.d.ts +35 -0
- package/dist/core/InMemoryDataSource.d.ts +30 -0
- package/dist/core/LocalStorageDataSource.d.ts +30 -0
- package/dist/core/RestDataSource.d.ts +120 -0
- package/dist/core/StaticDataSource.d.ts +20 -0
- package/dist/core/identity.d.ts +28 -0
- package/dist/core/inMemoryQuery.d.ts +26 -0
- package/dist/core/index.d.ts +11 -0
- package/dist/core/types.d.ts +86 -0
- package/dist/en_US-D3Kl6g3n.cjs +1 -0
- package/dist/en_US-DGCMDOdX.js +41 -0
- package/dist/exportData.cjs +13 -13
- package/dist/exportData.js +48 -39
- package/dist/fields/registry.d.ts +38 -0
- package/dist/fields/types.d.ts +29 -0
- package/dist/hooks/useCrudTable.d.ts +114 -57
- package/dist/hooks/useLocalStorageCrud.d.ts +14 -9
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +14 -6
- package/dist/index.js +7 -0
- package/dist/locale/en_US.d.ts +10 -0
- package/dist/locale/index.d.ts +4 -0
- package/dist/locale/types.d.ts +89 -0
- package/dist/locale/useResolvedLocale.d.ts +35 -0
- package/dist/useCrudTable-40jpmCkr.cjs +1 -0
- package/dist/useCrudTable-DxjnXVlQ.js +460 -0
- package/dist/useCrudTable.cjs +1 -1
- package/dist/useCrudTable.js +2 -235
- package/dist/useLocalStorageCrud.cjs +1 -1
- package/dist/useLocalStorageCrud.js +12 -198
- package/dist/utils/exportData.d.ts +37 -12
- 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
|
-
|
|
5
|
+
**A typed, schema-driven CRUD table for React โ built on [antd](https://ant.design) and [ProComponents](https://procomponents.ant.design).**
|
|
4
6
|
|
|
5
|
-
|
|
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
|
-
|
|
11
|
+
[](https://www.npmjs.com/package/antd-crud-table)
|
|
12
|
+
[](./LICENSE)
|
|
13
|
+
[](https://react.dev)
|
|
14
|
+
[](https://www.typescriptlang.org)
|
|
8
15
|
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
25
|
+
## Why
|
|
43
26
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
title
|
|
55
|
-
|
|
56
|
-
|
|
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: '
|
|
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
|
-
|
|
166
|
-
|
|
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
|
-
|
|
52
|
+
## Highlights
|
|
188
53
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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
|
-
|
|
218
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
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
|
-
|
|
426
|
-
|
|
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
|
-
|
|
449
|
-
|
|
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
|
-
|
|
565
|
-
{
|
|
566
|
-
|
|
567
|
-
|
|
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
|
-
|
|
571
|
-
{
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
151
|
+
### Static data
|
|
604
152
|
|
|
605
|
-
|
|
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
|
-
###
|
|
160
|
+
### REST API
|
|
626
161
|
|
|
627
|
-
**Original (Service-Based):**
|
|
628
162
|
```tsx
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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
|
-
|
|
179
|
+
Failures throw `RestError`, carrying `status` and `body` so you can branch on a
|
|
180
|
+
422 rather than parsing a message.
|
|
659
181
|
|
|
660
|
-
|
|
182
|
+
### localStorage
|
|
661
183
|
|
|
662
184
|
```tsx
|
|
663
|
-
|
|
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
|
-
|
|
190
|
+
### Your own
|
|
680
191
|
|
|
681
|
-
### String Field
|
|
682
192
|
```tsx
|
|
683
|
-
{
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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
|
-
|
|
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
|
-
|
|
730
|
-
|
|
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
|
-
|
|
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
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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
|
-
##
|
|
320
|
+
## Export
|
|
764
321
|
|
|
765
|
-
The
|
|
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
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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
|
-
|
|
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
|
-
|
|
799
|
-
```tsx
|
|
800
|
-
// Perfect for demos and development
|
|
801
|
-
hookConfig={{ staticData: mockData }}
|
|
802
|
-
```
|
|
343
|
+
## Using the hook directly
|
|
803
344
|
|
|
804
|
-
|
|
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
|
-
|
|
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
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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
|
-
|
|
843
|
-
- ๐ง **Plugin System**: Extensible architecture
|
|
844
|
-
- ๐ **Analytics Integration**: Built-in tracking
|
|
845
|
-
- ๐ **i18n Support**: Multi-language support
|
|
846
|
-
|
|
847
|
-
---
|
|
359
|
+
## Documentation
|
|
848
360
|
|
|
849
|
-
|
|
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
|
-
|
|
370
|
+
## Development
|
|
852
371
|
|
|
853
|
-
### Development Setup
|
|
854
372
|
```bash
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
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
|
-
##
|
|
386
|
+
## License
|
|
875
387
|
|
|
876
|
-
|
|
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)
|