@reactorui/datagrid 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +778 -0
- package/dist/components/DataGrid/DataGrid.d.ts +5 -0
- package/dist/components/DataGrid/DataGrid.d.ts.map +1 -0
- package/dist/components/DataGrid/DataGrid.js +87 -0
- package/dist/components/DataGrid/index.d.ts +3 -0
- package/dist/components/DataGrid/index.d.ts.map +1 -0
- package/dist/components/DataGrid/index.js +1 -0
- package/dist/components/Filter/FilterControls.d.ts +11 -0
- package/dist/components/Filter/FilterControls.d.ts.map +1 -0
- package/dist/components/Filter/FilterControls.js +78 -0
- package/dist/components/Filter/index.d.ts +2 -0
- package/dist/components/Filter/index.d.ts.map +1 -0
- package/dist/components/Filter/index.js +1 -0
- package/dist/components/Pagination/Pagination.d.ts +17 -0
- package/dist/components/Pagination/Pagination.d.ts.map +1 -0
- package/dist/components/Pagination/Pagination.js +12 -0
- package/dist/components/Pagination/index.d.ts +2 -0
- package/dist/components/Pagination/index.d.ts.map +1 -0
- package/dist/components/Pagination/index.js +1 -0
- package/dist/components/Search/SearchInput.d.ts +11 -0
- package/dist/components/Search/SearchInput.d.ts.map +1 -0
- package/dist/components/Search/SearchInput.js +9 -0
- package/dist/components/Search/index.d.ts +2 -0
- package/dist/components/Search/index.d.ts.map +1 -0
- package/dist/components/Search/index.js +1 -0
- package/dist/components/Table/TableBody.d.ts +20 -0
- package/dist/components/Table/TableBody.d.ts.map +1 -0
- package/dist/components/Table/TableBody.js +56 -0
- package/dist/components/Table/TableHeader.d.ts +13 -0
- package/dist/components/Table/TableHeader.d.ts.map +1 -0
- package/dist/components/Table/TableHeader.js +24 -0
- package/dist/components/Table/index.d.ts +3 -0
- package/dist/components/Table/index.d.ts.map +1 -0
- package/dist/components/Table/index.js +2 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useDataGrid.d.ts +49 -0
- package/dist/hooks/useDataGrid.d.ts.map +1 -0
- package/dist/hooks/useDataGrid.js +356 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/setupTests.d.ts +12 -0
- package/dist/setupTests.d.ts.map +1 -0
- package/dist/setupTests.js +1 -0
- package/dist/themes/index.d.ts +22 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +31 -0
- package/dist/types/index.d.ts +108 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +209 -0
- package/package.json +80 -0
package/README.md
ADDED
|
@@ -0,0 +1,778 @@
|
|
|
1
|
+
# @reactorui/datagrid
|
|
2
|
+
|
|
3
|
+
A high-performance, feature-rich React data grid component with TypeScript support, server-side integration, and advanced filtering capabilities.
|
|
4
|
+
|
|
5
|
+
## โจ Features
|
|
6
|
+
|
|
7
|
+
- ๐ **High Performance** - Optimized rendering and data processing
|
|
8
|
+
- ๐ **Advanced Filtering** - Type-aware filters with multiple operators (string, number, date, boolean)
|
|
9
|
+
- ๐ **Flexible Data Sources** - Static data or server-side with any API
|
|
10
|
+
- ๐ฑ **Responsive Design** - Mobile-first with touch-friendly interactions
|
|
11
|
+
- ๐จ **Customizable Theming** - Multiple built-in variants and custom styling
|
|
12
|
+
- ๐ **Dark Mode Ready** - Built-in dark mode support with CSS variables
|
|
13
|
+
- โฟ **Accessibility First** - WCAG compliant with keyboard navigation and ARIA labels
|
|
14
|
+
- ๐ง **TypeScript Native** - Full type safety and comprehensive IntelliSense support
|
|
15
|
+
- ๐ฏ **Rich Event System** - 15+ events covering every user interaction
|
|
16
|
+
- ๐ **Secure Authentication** - Bearer token, API key, and custom header support
|
|
17
|
+
- โก **Zero Dependencies** - Only React as peer dependency
|
|
18
|
+
|
|
19
|
+
## ๐ฆ Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @reactorui/datagrid
|
|
23
|
+
# or
|
|
24
|
+
yarn add @reactorui/datagrid
|
|
25
|
+
# or
|
|
26
|
+
pnpm add @reactorui/datagrid
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## ๐ Basic Usage
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
import { DataGrid } from '@reactorui/datagrid';
|
|
33
|
+
|
|
34
|
+
const data = [
|
|
35
|
+
{ id: 1, name: 'John Doe', email: 'john@example.com', age: 28 },
|
|
36
|
+
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', age: 34 },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
function App() {
|
|
40
|
+
return <DataGrid data={data} />;
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## ๐ With Custom Columns & Styling
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { DataGrid, Column } from '@reactorui/datagrid';
|
|
48
|
+
|
|
49
|
+
interface User {
|
|
50
|
+
id: number;
|
|
51
|
+
name: string;
|
|
52
|
+
email: string;
|
|
53
|
+
status: 'active' | 'inactive';
|
|
54
|
+
joinDate: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const columns: Column<User>[] = [
|
|
58
|
+
{
|
|
59
|
+
key: 'name',
|
|
60
|
+
label: 'Full Name',
|
|
61
|
+
sortable: true,
|
|
62
|
+
render: (value, row) => (
|
|
63
|
+
<div className="flex items-center gap-2">
|
|
64
|
+
<div className="w-8 h-8 bg-blue-500 text-white rounded-full flex items-center justify-center text-sm">
|
|
65
|
+
{value.charAt(0)}
|
|
66
|
+
</div>
|
|
67
|
+
{value}
|
|
68
|
+
</div>
|
|
69
|
+
),
|
|
70
|
+
},
|
|
71
|
+
{ key: 'email', label: 'Email Address', sortable: true },
|
|
72
|
+
{
|
|
73
|
+
key: 'status',
|
|
74
|
+
label: 'Status',
|
|
75
|
+
dataType: 'string',
|
|
76
|
+
render: (status) => (
|
|
77
|
+
<span
|
|
78
|
+
className={`px-2 py-1 text-xs font-medium rounded-full ${
|
|
79
|
+
status === 'active' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
|
80
|
+
}`}
|
|
81
|
+
>
|
|
82
|
+
{status}
|
|
83
|
+
</span>
|
|
84
|
+
),
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
key: 'joinDate',
|
|
88
|
+
label: 'Join Date',
|
|
89
|
+
dataType: 'date',
|
|
90
|
+
sortable: true,
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
function App() {
|
|
95
|
+
return (
|
|
96
|
+
<DataGrid
|
|
97
|
+
data={users}
|
|
98
|
+
columns={columns}
|
|
99
|
+
variant="bordered"
|
|
100
|
+
size="comfortable"
|
|
101
|
+
enableSelection={true}
|
|
102
|
+
onSelectionChange={(selected) => console.log('Selected:', selected)}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## ๐ Server-Side Data
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
import { DataGrid } from '@reactorui/datagrid';
|
|
112
|
+
|
|
113
|
+
function App() {
|
|
114
|
+
return (
|
|
115
|
+
<DataGrid
|
|
116
|
+
endpoint="/api/users"
|
|
117
|
+
httpConfig={{
|
|
118
|
+
bearerToken: 'your-jwt-token',
|
|
119
|
+
method: 'POST',
|
|
120
|
+
postDataFormat: 'json',
|
|
121
|
+
customHeaders: {
|
|
122
|
+
'X-Custom-Header': 'value',
|
|
123
|
+
},
|
|
124
|
+
}}
|
|
125
|
+
serverPageSize={100}
|
|
126
|
+
pageSize={25}
|
|
127
|
+
onDataLoad={(data) => console.log(`Loaded ${data.items.length} items`)}
|
|
128
|
+
onDataError={(error, context) => console.error(`Error in ${context}:`, error)}
|
|
129
|
+
/>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## ๐ฏ Comprehensive Event System
|
|
135
|
+
|
|
136
|
+
The DataGrid provides 15+ events covering every user interaction:
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
<DataGrid
|
|
140
|
+
data={users}
|
|
141
|
+
// Data loading events
|
|
142
|
+
onDataLoad={(data) => {
|
|
143
|
+
console.log('Data loaded:', data.items.length);
|
|
144
|
+
hideLoadingSpinner();
|
|
145
|
+
}}
|
|
146
|
+
onDataError={(error, context) => {
|
|
147
|
+
console.error(`Error in ${context}:`, error.message);
|
|
148
|
+
showErrorToast(error.message);
|
|
149
|
+
}}
|
|
150
|
+
onLoadingStateChange={(loading, context) => {
|
|
151
|
+
setIsLoading(loading);
|
|
152
|
+
console.log(`${context} loading: ${loading}`);
|
|
153
|
+
}}
|
|
154
|
+
// Pagination events
|
|
155
|
+
onPageChange={(page, paginationInfo) => {
|
|
156
|
+
console.log(`Page ${page} of ${paginationInfo.totalPages}`);
|
|
157
|
+
updateUrl(`?page=${page}`);
|
|
158
|
+
}}
|
|
159
|
+
onPageSizeChange={(pageSize, paginationInfo) => {
|
|
160
|
+
console.log(`Showing ${pageSize} items per page`);
|
|
161
|
+
saveUserPreference('pageSize', pageSize);
|
|
162
|
+
}}
|
|
163
|
+
// Interaction events
|
|
164
|
+
onSortChange={(sortConfig) => {
|
|
165
|
+
console.log(`Sorted by ${sortConfig.column} ${sortConfig.direction}`);
|
|
166
|
+
updateUrl(`?sort=${sortConfig.column}&order=${sortConfig.direction}`);
|
|
167
|
+
}}
|
|
168
|
+
onFilterChange={(filters) => {
|
|
169
|
+
console.log(`${filters.length} filters active`);
|
|
170
|
+
setBadgeCount(filters.length);
|
|
171
|
+
}}
|
|
172
|
+
onSearchChange={(searchTerm) => {
|
|
173
|
+
console.log(`Searching for: "${searchTerm}"`);
|
|
174
|
+
trackSearchQuery(searchTerm);
|
|
175
|
+
}}
|
|
176
|
+
onTableRefresh={() => {
|
|
177
|
+
console.log('Table refreshed');
|
|
178
|
+
showSuccessMessage('Data refreshed');
|
|
179
|
+
}}
|
|
180
|
+
/>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Row & Cell Interaction Events**<br>
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
<DataGrid
|
|
187
|
+
data={users}
|
|
188
|
+
// Row interaction events
|
|
189
|
+
onTableRowClick={(row, event) => {
|
|
190
|
+
console.log('Row clicked:', row.name);
|
|
191
|
+
highlightRow(row.id);
|
|
192
|
+
}}
|
|
193
|
+
onTableRowDoubleClick={(row, event) => {
|
|
194
|
+
console.log('Row double-clicked:', row.name);
|
|
195
|
+
openEditModal(row);
|
|
196
|
+
return false; // Prevent default selection behavior
|
|
197
|
+
}}
|
|
198
|
+
onTableRowHover={(row, event) => {
|
|
199
|
+
if (row) {
|
|
200
|
+
console.log('Hovering over:', row.name);
|
|
201
|
+
showPreviewTooltip(row);
|
|
202
|
+
} else {
|
|
203
|
+
hidePreviewTooltip();
|
|
204
|
+
}
|
|
205
|
+
}}
|
|
206
|
+
// Selection events
|
|
207
|
+
onRowSelect={(row, isSelected) => {
|
|
208
|
+
console.log(`${row.name} ${isSelected ? 'selected' : 'deselected'}`);
|
|
209
|
+
updateRowActions(row, isSelected);
|
|
210
|
+
}}
|
|
211
|
+
onSelectionChange={(selectedRows) => {
|
|
212
|
+
console.log(`${selectedRows.length} rows selected`);
|
|
213
|
+
setBulkActionsEnabled(selectedRows.length > 0);
|
|
214
|
+
updateSelectionToolbar(selectedRows);
|
|
215
|
+
}}
|
|
216
|
+
// Cell interaction events
|
|
217
|
+
onCellClick={(value, row, column, event) => {
|
|
218
|
+
console.log(`Clicked ${column.label}: ${value}`);
|
|
219
|
+
if (column.key === 'email') {
|
|
220
|
+
window.open(`mailto:${value}`);
|
|
221
|
+
}
|
|
222
|
+
}}
|
|
223
|
+
/>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Real-World Event Usage Example
|
|
227
|
+
|
|
228
|
+
```tsx
|
|
229
|
+
import React, { useState } from 'react';
|
|
230
|
+
import { DataGrid } from '@reactorui/datagrid';
|
|
231
|
+
|
|
232
|
+
function UserManagement() {
|
|
233
|
+
const [loading, setLoading] = useState(false);
|
|
234
|
+
const [selectedUsers, setSelectedUsers] = useState([]);
|
|
235
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
236
|
+
|
|
237
|
+
return (
|
|
238
|
+
<div>
|
|
239
|
+
{/* Bulk Actions Toolbar */}
|
|
240
|
+
{selectedUsers.length > 0 && (
|
|
241
|
+
<div className="bg-blue-50 p-4 mb-4 rounded">
|
|
242
|
+
<span className="font-medium">{selectedUsers.length} users selected</span>
|
|
243
|
+
<div className="ml-4 space-x-2">
|
|
244
|
+
<button onClick={() => bulkExport(selectedUsers)}>Export</button>
|
|
245
|
+
<button onClick={() => bulkDeactivate(selectedUsers)}>Deactivate</button>
|
|
246
|
+
<button onClick={() => bulkDelete(selectedUsers)}>Delete</button>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
)}
|
|
250
|
+
|
|
251
|
+
{/* DataGrid with comprehensive event handling */}
|
|
252
|
+
<DataGrid
|
|
253
|
+
endpoint="/api/users"
|
|
254
|
+
enableSelection={true}
|
|
255
|
+
// Sync with component state
|
|
256
|
+
onLoadingStateChange={(loading) => setLoading(loading)}
|
|
257
|
+
onPageChange={(page) => setCurrentPage(page)}
|
|
258
|
+
onSelectionChange={(users) => setSelectedUsers(users)}
|
|
259
|
+
// User interaction handlers
|
|
260
|
+
onTableRowDoubleClick={(user) => openUserProfile(user.id)}
|
|
261
|
+
onDataError={(error) => showErrorNotification(error.message)}
|
|
262
|
+
onTableRefresh={() => showSuccessMessage('Users refreshed')}
|
|
263
|
+
// Analytics tracking
|
|
264
|
+
onSearchChange={(term) => analytics.track('users_searched', { term })}
|
|
265
|
+
onSortChange={(sort) => analytics.track('users_sorted', { column: sort.column })}
|
|
266
|
+
onFilterChange={(filters) => analytics.track('users_filtered', { count: filters.length })}
|
|
267
|
+
/>
|
|
268
|
+
|
|
269
|
+
{/* Status indicators */}
|
|
270
|
+
<div className="mt-4 text-sm text-gray-500">
|
|
271
|
+
Page {currentPage} โข {selectedUsers.length} selected โข {loading ? 'Loading...' : 'Ready'}
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## ๐ Complete API Reference
|
|
279
|
+
|
|
280
|
+
**DataGrid Props**
|
|
281
|
+
|
|
282
|
+
| **Prop** | **Type** | **Default** | **Description** |
|
|
283
|
+
| ---------------------- | -------------------------------------- | ------------------ | ---------------------------------------- |
|
|
284
|
+
| **Data Configuration** | | | |
|
|
285
|
+
| `data` | `T[]` | โ | Static data array for client-side mode |
|
|
286
|
+
| `endpoint` | `string` | โ | API endpoint for server-side data |
|
|
287
|
+
| `columns` | `Column<T>[]` | Auto-detected | Column configuration array |
|
|
288
|
+
| **Feature Toggles** | | | |
|
|
289
|
+
| `enableSearch` | `boolean` | `true` | Enable global search functionality |
|
|
290
|
+
| `enableSorting` | `boolean` | `true` | Enable column sorting |
|
|
291
|
+
| `enableFilters` | `boolean` | `true` | Enable advanced filtering |
|
|
292
|
+
| `enableSelection` | `boolean` | `true` | Enable row selection with checkboxes |
|
|
293
|
+
| **Pagination** | | | |
|
|
294
|
+
| `pageSize` | `number` | `10` | Client-side pagination size |
|
|
295
|
+
| `serverPageSize` | `number` | `100` | Server request batch size |
|
|
296
|
+
| `pageSizeOptions` | `number[]` | `[5,10,25,50,100]` | Available page size options |
|
|
297
|
+
| **Styling** | | | |
|
|
298
|
+
| `variant` | `'default' \| 'striped' \| 'bordered'` | `'default'` | Visual theme variant |
|
|
299
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size variant for padding and text |
|
|
300
|
+
| `className` | `string` | `''` | Additional CSS classes |
|
|
301
|
+
| **HTTP Configuration** | | | |
|
|
302
|
+
| `httpConfig` | `HttpConfig` | โ | Authentication and request configuration |
|
|
303
|
+
|
|
304
|
+
## Column Configuration
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
interface Column<T> {
|
|
308
|
+
key: keyof T | string; // Data property key
|
|
309
|
+
label: string; // Display header label
|
|
310
|
+
sortable?: boolean; // Enable sorting (default: true)
|
|
311
|
+
filterable?: boolean; // Enable in advanced filters (default: true)
|
|
312
|
+
dataType?: 'string' | 'number' | 'boolean' | 'date' | 'datetime';
|
|
313
|
+
width?: string | number; // Fixed column width
|
|
314
|
+
minWidth?: string | number; // Minimum column width
|
|
315
|
+
maxWidth?: string | number; // Maximum column width
|
|
316
|
+
align?: 'left' | 'center' | 'right';
|
|
317
|
+
render?: (value: any, row: T, index: number) => ReactNode;
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## HTTP Configuration
|
|
322
|
+
|
|
323
|
+
```tsx
|
|
324
|
+
interface HttpConfig {
|
|
325
|
+
bearerToken?: string; // Authorization: Bearer <token>
|
|
326
|
+
apiKey?: string; // X-API-Key header
|
|
327
|
+
customHeaders?: Record<string, string>;
|
|
328
|
+
method?: 'GET' | 'POST'; // HTTP method (default: GET)
|
|
329
|
+
postDataFormat?: 'form' | 'json'; // POST body format
|
|
330
|
+
withCredentials?: boolean; // Include cookies
|
|
331
|
+
timeout?: number; // Request timeout in ms
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
# Server Request & Response Format
|
|
336
|
+
|
|
337
|
+
**Request sent to your API:**
|
|
338
|
+
|
|
339
|
+
```tsx
|
|
340
|
+
interface ServerRequest {
|
|
341
|
+
page: number; // Current page number
|
|
342
|
+
pageSize: number; // Items per page
|
|
343
|
+
search: string; // Global search term
|
|
344
|
+
sortColumn: string; // Column to sort by
|
|
345
|
+
filters: ActiveFilter[]; // Applied filters
|
|
346
|
+
continuationToken: // Token to grab more records (For server side pagination, if enabled)
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**Expected response format:**
|
|
351
|
+
|
|
352
|
+
```tsx
|
|
353
|
+
interface ServerResponse<T> {
|
|
354
|
+
items: T[]; // Data array for current page
|
|
355
|
+
count: number; // Total number of records
|
|
356
|
+
hasMore: boolean; // Whether more pages available
|
|
357
|
+
continuationToken: string; // Token to grab more records (For server side pagination, if enabled)
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Event Callbacks
|
|
362
|
+
|
|
363
|
+
| **Event** | **Signature** | **Description** |
|
|
364
|
+
| ----------------------- | -------------------------------------------------------------------- | -------------------------------------------- |
|
|
365
|
+
| **Data & State Events** | | |
|
|
366
|
+
| `onDataLoad` | `(data: ServerResponse<T>) => void` | Called when server data loads successfully |
|
|
367
|
+
| `onDataError` | `(error: Error, context: string) => void` | Called when any error occurs |
|
|
368
|
+
| `onLoadingStateChange` | `(loading: boolean, context: string) => void` | Called when loading state changes |
|
|
369
|
+
| `onPageChange` | `(page: number, paginationInfo: PaginationInfo) => void` | Called when user navigates pages |
|
|
370
|
+
| `onPageSizeChange` | `(size: number, paginationInfo: PaginationInfo) => void` | Called when page size changes |
|
|
371
|
+
| `onSortChange` | `(sortConfig: SortConfig) => void` | Called when sorting changes |
|
|
372
|
+
| `onFilterChange` | `(filters: ActiveFilter[]) => void` | Called when filters change |
|
|
373
|
+
| `onSearchChange` | `(searchTerm: string) => void` | Called when search term changes |
|
|
374
|
+
| `onTableRefresh` | `() => void` | Called when refresh is triggered |
|
|
375
|
+
| **Row & Cell Events** | | |
|
|
376
|
+
| `onTableRowClick` | `(row: T, event: MouseEvent) => void` | Called on single row click |
|
|
377
|
+
| `onTableRowDoubleClick` | `(row: T, event: MouseEvent) => boolean \| void` | Called on row double-click |
|
|
378
|
+
| `onRowSelect` | `(row: T, isSelected: boolean) => void` | Called when individual row selection changes |
|
|
379
|
+
| `onSelectionChange` | `(selectedRows: T[]) => void` | Called when overall selection changes |
|
|
380
|
+
| `onTableRowHover` | `(row: T \| null, event: MouseEvent) => void` | Called when hovering over rows |
|
|
381
|
+
| `onCellClick` | `(value: any, row: T, column: Column<T>, event: MouseEvent) => void` | Called when clicking individual cells |
|
|
382
|
+
|
|
383
|
+
|
|
|
384
|
+
|
|
385
|
+
## ๐ Complete API Reference
|
|
386
|
+
|
|
387
|
+
**DataGrid Props**
|
|
388
|
+
|
|
389
|
+
| **Prop** | **Type** | **Default** | **Description** |
|
|
390
|
+
| ---------------------- | -------------------------------------- | ---------------------- | ---------------------------------------- |
|
|
391
|
+
| **Data Configuration** | | | |
|
|
392
|
+
| `data` | `T[]` | `-` | Static data array for client-side mode |
|
|
393
|
+
| `endpoint` | `string` | `-` | API endpoint for server-side data |
|
|
394
|
+
| `columns` | `Column<T>[]` | auto-detected | Column configuration array |
|
|
395
|
+
| **Feature Toggles** | | | |
|
|
396
|
+
| `enableSearch` | `boolean` | `true` | Enable global search functionality |
|
|
397
|
+
| `enableSorting` | `boolean` | `true` | Enable column sorting |
|
|
398
|
+
| `enableFilters` | `boolean` | `true` | Enable advanced filtering |
|
|
399
|
+
| `enableSelection` | `boolean` | `true` | Enable row selection with checkboxes |
|
|
400
|
+
| **Pagination** | | | |
|
|
401
|
+
| `pageSize` | `number` | `10` | Client-side pagination size |
|
|
402
|
+
| `serverPageSize` | `number` | `100` | Server request batch size |
|
|
403
|
+
| `pageSizeOptions` | `number[]` | `[5, 10, 25, 50, 100]` | Available page size options |
|
|
404
|
+
| **Styling** | | | |
|
|
405
|
+
| `variant` | `'default' \| 'striped' \| 'bordered'` | `'default'` | Visual theme variant |
|
|
406
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size variant for padding and text |
|
|
407
|
+
| `className` | `string` | `''` | Additional CSS classes |
|
|
408
|
+
| **HTTP Configuration** | | | |
|
|
409
|
+
| `httpConfig` | `HttpConfig` | `-` | Authentication and request configuration |
|
|
410
|
+
|
|
411
|
+
## ๐จ Theming & Styling
|
|
412
|
+
|
|
413
|
+
```tsx
|
|
414
|
+
// Clean, minimal design
|
|
415
|
+
<DataGrid variant="default" data={data} />
|
|
416
|
+
|
|
417
|
+
// Alternating row colors
|
|
418
|
+
<DataGrid variant="striped" data={data} />
|
|
419
|
+
|
|
420
|
+
// Full borders around cells
|
|
421
|
+
<DataGrid variant="bordered" data={data} />
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
**Size Variants**
|
|
425
|
+
|
|
426
|
+
```tsx
|
|
427
|
+
// Compact spacing
|
|
428
|
+
<DataGrid size="sm" data={data} />
|
|
429
|
+
|
|
430
|
+
// Standard spacing (default)
|
|
431
|
+
<DataGrid size="md" data={data} />
|
|
432
|
+
|
|
433
|
+
// Comfortable spacing
|
|
434
|
+
<DataGrid size="lg" data={data} />
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
**Dark Mode Support**<br>
|
|
438
|
+
The DataGrid automatically adapts to dark mode when using Tailwind CSS:
|
|
439
|
+
|
|
440
|
+
```tsx
|
|
441
|
+
// Wrap in dark mode provider
|
|
442
|
+
<div className="dark">
|
|
443
|
+
<DataGrid data={data} />
|
|
444
|
+
</div>
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**Custom Styling**<br>
|
|
448
|
+
|
|
449
|
+
```tsx
|
|
450
|
+
<DataGrid
|
|
451
|
+
data={data}
|
|
452
|
+
className="shadow-xl rounded-xl overflow-hidden"
|
|
453
|
+
// Add custom CSS classes for complete control
|
|
454
|
+
/>
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## ๐ง Advanced Usage
|
|
458
|
+
|
|
459
|
+
**Custom Styling**<br>
|
|
460
|
+
|
|
461
|
+
```tsx
|
|
462
|
+
const columns: Column<Employee>[] = [
|
|
463
|
+
{
|
|
464
|
+
key: 'employee',
|
|
465
|
+
label: 'Employee',
|
|
466
|
+
render: (_, employee) => (
|
|
467
|
+
<div className="flex items-center space-x-3">
|
|
468
|
+
<img src={employee.avatar} alt={employee.name} className="w-10 h-10 rounded-full" />
|
|
469
|
+
<div>
|
|
470
|
+
<div className="font-medium">{employee.name}</div>
|
|
471
|
+
<div className="text-sm text-gray-500">{employee.title}</div>
|
|
472
|
+
</div>
|
|
473
|
+
</div>
|
|
474
|
+
),
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
key: 'performance',
|
|
478
|
+
label: 'Performance',
|
|
479
|
+
render: (score) => (
|
|
480
|
+
<div className="flex items-center">
|
|
481
|
+
<div className="flex-1 bg-gray-200 rounded-full h-2 mr-2">
|
|
482
|
+
<div className="bg-blue-500 h-2 rounded-full" style={{ width: `${score}%` }} />
|
|
483
|
+
</div>
|
|
484
|
+
<span className="text-sm font-medium">{score}%</span>
|
|
485
|
+
</div>
|
|
486
|
+
),
|
|
487
|
+
},
|
|
488
|
+
];
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
**Advanced Filtering**
|
|
492
|
+
|
|
493
|
+
```tsx
|
|
494
|
+
// The DataGrid automatically provides appropriate filter inputs:
|
|
495
|
+
// - String: text input with contains/equals/starts with/ends with
|
|
496
|
+
// - Number: number input with comparison operators
|
|
497
|
+
// - Date: date picker with before/after/on
|
|
498
|
+
// - Boolean: dropdown with true/false options
|
|
499
|
+
|
|
500
|
+
const columns = [
|
|
501
|
+
{ key: 'name', label: 'Name', dataType: 'string' },
|
|
502
|
+
{ key: 'salary', label: 'Salary', dataType: 'number' },
|
|
503
|
+
{ key: 'startDate', label: 'Start Date', dataType: 'date' },
|
|
504
|
+
{ key: 'isActive', label: 'Active', dataType: 'boolean' },
|
|
505
|
+
];
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
**Real-time Data with WebSockets**
|
|
509
|
+
|
|
510
|
+
```tsx
|
|
511
|
+
function LiveDataGrid() {
|
|
512
|
+
const [data, setData] = useState([]);
|
|
513
|
+
|
|
514
|
+
useEffect(() => {
|
|
515
|
+
const ws = new WebSocket('ws://localhost:8080');
|
|
516
|
+
ws.onmessage = (event) => {
|
|
517
|
+
const updatedData = JSON.parse(event.data);
|
|
518
|
+
setData(updatedData);
|
|
519
|
+
};
|
|
520
|
+
return () => ws.close();
|
|
521
|
+
}, []);
|
|
522
|
+
|
|
523
|
+
return (
|
|
524
|
+
<DataGrid
|
|
525
|
+
data={data}
|
|
526
|
+
onTableRefresh={() => {
|
|
527
|
+
// Trigger server refresh
|
|
528
|
+
fetch('/api/refresh', { method: 'POST' });
|
|
529
|
+
}}
|
|
530
|
+
/>
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
## ๐ Server Integration Examples
|
|
536
|
+
|
|
537
|
+
**Node.js/Expresse**
|
|
538
|
+
|
|
539
|
+
```tsx
|
|
540
|
+
app.post('/api/users', async (req, res) => {
|
|
541
|
+
const { page, pageSize, search, filters, continuationToken } = JSON.parse(req.body.request);
|
|
542
|
+
|
|
543
|
+
let query = User.find();
|
|
544
|
+
|
|
545
|
+
// Apply search
|
|
546
|
+
if (search) {
|
|
547
|
+
query = query.where({
|
|
548
|
+
$or: [
|
|
549
|
+
{ name: { $regex: search, $options: 'i' } },
|
|
550
|
+
{ email: { $regex: search, $options: 'i' } },
|
|
551
|
+
],
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Apply filters
|
|
556
|
+
filters.forEach((filter) => {
|
|
557
|
+
query = query.where(filter.column)[getOperator(filter.operator)](filter.value);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// Handle continuation token (simple ID-based cursor)
|
|
561
|
+
if (continuationToken) {
|
|
562
|
+
const { lastId } = JSON.parse(Buffer.from(continuationToken, 'base64').toString());
|
|
563
|
+
query = query.where('_id').gt(lastId);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Execute query
|
|
567
|
+
const items = await query.limit(pageSize + 1);
|
|
568
|
+
const hasMore = items.length > pageSize;
|
|
569
|
+
const resultItems = hasMore ? items.slice(0, pageSize) : items;
|
|
570
|
+
|
|
571
|
+
// Generate next token
|
|
572
|
+
let nextToken;
|
|
573
|
+
if (hasMore && resultItems.length > 0) {
|
|
574
|
+
const lastItem = resultItems[resultItems.length - 1];
|
|
575
|
+
nextToken = Buffer.from(JSON.stringify({ lastId: lastItem._id })).toString('base64');
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
res.json({
|
|
579
|
+
items: resultItems, // lowercase works
|
|
580
|
+
continuationToken: nextToken, // camelCase works
|
|
581
|
+
hasMore: hasMore, // camelCase works
|
|
582
|
+
count: resultItems.length, // lowercase works
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
**ASP.Net Core**
|
|
588
|
+
|
|
589
|
+
```tsx
|
|
590
|
+
[HttpPost("api/users")]
|
|
591
|
+
public async Task<IActionResult> GetUsers([FromBody] DataTableRequest request)
|
|
592
|
+
{
|
|
593
|
+
var query = _context.Users.AsQueryable();
|
|
594
|
+
|
|
595
|
+
// Apply search
|
|
596
|
+
if (!string.IsNullOrEmpty(request.Search))
|
|
597
|
+
query = query.Where(u => u.Name.Contains(request.Search) || u.Email.Contains(request.Search));
|
|
598
|
+
|
|
599
|
+
// Apply filters
|
|
600
|
+
foreach (var filter in request.Filters)
|
|
601
|
+
query = ApplyFilter(query, filter);
|
|
602
|
+
|
|
603
|
+
// Handle continuation token
|
|
604
|
+
if (!string.IsNullOrEmpty(request.ContinuationToken))
|
|
605
|
+
{
|
|
606
|
+
var token = JsonSerializer.Deserialize<ContinuationToken>(
|
|
607
|
+
Encoding.UTF8.GetString(Convert.FromBase64String(request.ContinuationToken)));
|
|
608
|
+
query = query.Where(u => u.Id > token.LastId);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
query = query.OrderBy(u => u.Id);
|
|
612
|
+
var items = await query.Take(request.PageSize + 1).ToListAsync();
|
|
613
|
+
var hasMore = items.Count > request.PageSize;
|
|
614
|
+
var resultItems = hasMore ? items.Take(request.PageSize).ToList() : items;
|
|
615
|
+
|
|
616
|
+
string nextToken = null;
|
|
617
|
+
if (hasMore && resultItems.Any())
|
|
618
|
+
{
|
|
619
|
+
var lastItem = resultItems.Last();
|
|
620
|
+
var tokenData = new ContinuationToken { LastId = lastItem.Id };
|
|
621
|
+
nextToken = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(tokenData)));
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
return Ok(new
|
|
625
|
+
{
|
|
626
|
+
Items = resultItems, // PascalCase works
|
|
627
|
+
ContinuationToken = nextToken, // PascalCase works
|
|
628
|
+
HasMore = hasMore, // PascalCase works
|
|
629
|
+
Count = resultItems.Count // PascalCase works
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
**Laravel/PHP**
|
|
635
|
+
|
|
636
|
+
```tsx
|
|
637
|
+
Route::post('/api/users', function (Request $request) {
|
|
638
|
+
$requestData = json_decode($request->input('request'), true);
|
|
639
|
+
$query = User::query();
|
|
640
|
+
|
|
641
|
+
// Apply search
|
|
642
|
+
if (!empty($requestData['search'])) {
|
|
643
|
+
$query->where(function($q) use ($requestData) {
|
|
644
|
+
$q->where('name', 'like', "%{$requestData['search']}%")
|
|
645
|
+
->orWhere('email', 'like', "%{$requestData['search']}%");
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Apply filters
|
|
650
|
+
foreach ($requestData['filters'] as $filter) {
|
|
651
|
+
$query->where($filter['column'], $filter['operator'], $filter['value']);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Handle continuation token
|
|
655
|
+
if (!empty($requestData['continuationToken'])) {
|
|
656
|
+
$tokenData = json_decode(base64_decode($requestData['continuationToken']), true);
|
|
657
|
+
$query->where('id', '>', $tokenData['lastId']);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
$query->orderBy('id', 'asc');
|
|
661
|
+
$items = $query->take($requestData['pageSize'] + 1)->get();
|
|
662
|
+
$hasMore = $items->count() > $requestData['pageSize'];
|
|
663
|
+
$resultItems = $hasMore ? $items->take($requestData['pageSize']) : $items;
|
|
664
|
+
|
|
665
|
+
$nextToken = null;
|
|
666
|
+
if ($hasMore && $resultItems->isNotEmpty()) {
|
|
667
|
+
$lastItem = $resultItems->last();
|
|
668
|
+
$nextToken = base64_encode(json_encode(['lastId' => $lastItem->id]));
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return response()->json([
|
|
672
|
+
'data' => $resultItems->values(), // Laravel convention works
|
|
673
|
+
'continuation_token' => $nextToken, // snake_case works
|
|
674
|
+
'has_more' => $hasMore, // snake_case works
|
|
675
|
+
'total' => $resultItems->count() // Laravel convention works
|
|
676
|
+
]);
|
|
677
|
+
});
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
## ๐งช Testing
|
|
681
|
+
|
|
682
|
+
```bash
|
|
683
|
+
# Run test suite
|
|
684
|
+
npm test
|
|
685
|
+
|
|
686
|
+
# Watch mode for development
|
|
687
|
+
npm run test:watch
|
|
688
|
+
|
|
689
|
+
# Coverage report
|
|
690
|
+
npm run test:coverage
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
**Testing with Jest & React Testing Library**
|
|
694
|
+
|
|
695
|
+
```tsx
|
|
696
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
697
|
+
import { DataGrid } from '@reactorui/datagrid';
|
|
698
|
+
|
|
699
|
+
test('handles user interactions', () => {
|
|
700
|
+
const onSelectionChange = jest.fn();
|
|
701
|
+
const testData = [{ id: 1, name: 'John', email: 'john@test.com' }];
|
|
702
|
+
|
|
703
|
+
render(<DataGrid data={testData} onSelectionChange={onSelectionChange} />);
|
|
704
|
+
|
|
705
|
+
// Test search
|
|
706
|
+
fireEvent.change(screen.getByPlaceholderText('Search...'), {
|
|
707
|
+
target: { value: 'John' },
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
// Test selection
|
|
711
|
+
fireEvent.click(screen.getAllByRole('checkbox')[1]);
|
|
712
|
+
|
|
713
|
+
expect(onSelectionChange).toHaveBeenCalledWith([testData[0]]);
|
|
714
|
+
});
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
## ๐ Examples
|
|
718
|
+
|
|
719
|
+
Check out the examples/ directory for complete working examples:
|
|
720
|
+
|
|
721
|
+
examples/basic/ - Simple usage with auto-detected columns
|
|
722
|
+
examples/advanced/ - Custom columns, renderers, and styling
|
|
723
|
+
examples/events/ - Comprehensive event handling demonstration
|
|
724
|
+
|
|
725
|
+
## ๐ Performance Tips
|
|
726
|
+
|
|
727
|
+
Use server-side pagination for datasets > 1000 records
|
|
728
|
+
Implement custom renderers efficiently with React.memo
|
|
729
|
+
Debounce search for better UX with large datasets
|
|
730
|
+
Use specific column keys instead of auto-detection for better performance
|
|
731
|
+
|
|
732
|
+
```tsx
|
|
733
|
+
const StatusBadge = React.memo(({ status }: { status: string }) => (
|
|
734
|
+
<span className={`badge ${status === 'active' ? 'bg-green' : 'bg-red'}`}>{status}</span>
|
|
735
|
+
));
|
|
736
|
+
|
|
737
|
+
// Debounced search
|
|
738
|
+
const [debouncedSearch] = useDebounce(searchTerm, 300);
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
## ๐ง Development
|
|
742
|
+
|
|
743
|
+
```bash
|
|
744
|
+
# Install dependencies
|
|
745
|
+
npm install
|
|
746
|
+
|
|
747
|
+
# Run tests
|
|
748
|
+
npm test
|
|
749
|
+
|
|
750
|
+
# Build library
|
|
751
|
+
npm run build
|
|
752
|
+
|
|
753
|
+
# Type checking
|
|
754
|
+
npm run typecheck
|
|
755
|
+
|
|
756
|
+
# Linting
|
|
757
|
+
npm run lint
|
|
758
|
+
|
|
759
|
+
# Format code
|
|
760
|
+
npm run format
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
## ๐ค Contributing
|
|
764
|
+
|
|
765
|
+
We welcome contributions! Please see our Contributing Guide for details.
|
|
766
|
+
|
|
767
|
+
Fork the repository
|
|
768
|
+
Create your feature branch (git checkout -b feature/amazing-feature)
|
|
769
|
+
Write tests for your changes
|
|
770
|
+
Commit your changes (git commit -m 'Add amazing feature')
|
|
771
|
+
Push to the branch (git push origin feature/amazing-feature)
|
|
772
|
+
Open a Pull Request
|
|
773
|
+
|
|
774
|
+
## ๐ License
|
|
775
|
+
|
|
776
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
777
|
+
|
|
778
|
+
Made with โค๏ธ by ReactorUI
|