react-live-data-table 1.0.1
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/package.json +37 -0
- package/readme.md +80 -0
- package/src/ReactDataTable.jsx +281 -0
- package/src/index.css +3 -0
- package/src/index.js +1 -0
package/package.json
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
{
|
2
|
+
"name": "react-live-data-table",
|
3
|
+
"version": "1.0.1",
|
4
|
+
"description": "Your React component package with Tailwind",
|
5
|
+
"main": "src/index.js",
|
6
|
+
"module": "dist/index.esm.js",
|
7
|
+
"files": [
|
8
|
+
"src"
|
9
|
+
],
|
10
|
+
"type": "module",
|
11
|
+
"scripts": {
|
12
|
+
"build": "rollup -c --bundleConfigAsCjs",
|
13
|
+
"prepare": "npm run build"
|
14
|
+
},
|
15
|
+
"peerDependencies": {
|
16
|
+
"react": "^18.0.0",
|
17
|
+
"react-dom": "^18.0.0",
|
18
|
+
"tailwindcss": ">=3.0.0"
|
19
|
+
},
|
20
|
+
"devDependencies": {
|
21
|
+
"@babel/core": "^7.26.7",
|
22
|
+
"@babel/preset-env": "^7.26.7",
|
23
|
+
"@babel/preset-react": "^7.26.3",
|
24
|
+
"@rollup/plugin-babel": "^6.0.4",
|
25
|
+
"@rollup/plugin-commonjs": "^25.0.8",
|
26
|
+
"@rollup/plugin-node-resolve": "^15.3.1",
|
27
|
+
"@rollup/plugin-terser": "^0.4.4",
|
28
|
+
"autoprefixer": "^10.4.20",
|
29
|
+
"babel-loader": "^9.2.1",
|
30
|
+
"postcss": "^8.5.1",
|
31
|
+
"rollup": "^3.29.5",
|
32
|
+
"rollup-plugin-peer-deps-external": "^2.2.4",
|
33
|
+
"rollup-plugin-postcss": "^4.0.2",
|
34
|
+
"tailwindcss": "^3.4.17"
|
35
|
+
},
|
36
|
+
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
37
|
+
}
|
package/readme.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# React Data Grid Table
|
2
|
+
|
3
|
+
A highly customizable and efficient data grid table for React. It supports features like **live pagination**, **checkbox selection**, **dynamic data loading**, **scrollable body**, and more. This component is designed to make handling tabular data easier and more interactive.
|
4
|
+
|
5
|
+
---
|
6
|
+
|
7
|
+
## 🚀 Installation
|
8
|
+
|
9
|
+
To install the package, run one of the following commands:
|
10
|
+
|
11
|
+
```bash
|
12
|
+
npm install react-data-grid-table
|
13
|
+
|
14
|
+
# React Data Grid
|
15
|
+
|
16
|
+
A flexible React data grid component with support for pagination, row selection, and dynamic data handling.
|
17
|
+
|
18
|
+
## Props
|
19
|
+
|
20
|
+
### Required Props
|
21
|
+
|
22
|
+
| Prop | Type | Description |
|
23
|
+
|------|------|-------------|
|
24
|
+
| `dataSource` | `Array \| Function` | The data to display in the grid. Can be an array for static data or a function for server-side pagination |
|
25
|
+
| `columns` | `Array` | Defines the columns configuration |
|
26
|
+
|
27
|
+
### Optional Props
|
28
|
+
|
29
|
+
| Prop | Type | Default | Description |
|
30
|
+
|------|------|---------|-------------|
|
31
|
+
| `loading` | `boolean` | `false` | Shows loading state |
|
32
|
+
| `maxHeight` | `string` | `"600px"` | Maximum height of the grid |
|
33
|
+
| `height` | `string` | `"600px"` | Fixed height of the grid |
|
34
|
+
| `showCheckbox` | `boolean` | `false` | Enable row selection |
|
35
|
+
| `onSelectionChange` | `Function` | - | Callback when row selection changes |
|
36
|
+
| `defaultLimit` | `number` | `50` | Rows per page |
|
37
|
+
| `onRowClick` | `Function` | - | Callback when row is clicked |
|
38
|
+
| `livePagination` | `boolean` | `false` | Enable server-side pagination |
|
39
|
+
| `staticData` | `Array` | `null` | Static data array |
|
40
|
+
| `emptyText` | `string` | - | Text shown when no data |
|
41
|
+
| `rowHeights` | `number` | `40` | Height of each row |
|
42
|
+
| `headerProps` | `object` | `{}` | Custom header properties |
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
### Column Definition
|
48
|
+
|
49
|
+
```typescript
|
50
|
+
interface ColumnDefinition {
|
51
|
+
field: string; // Key of the data field to display
|
52
|
+
headerName: string; // Text to show in column header
|
53
|
+
width?: number; // Column width in pixels
|
54
|
+
sortable?: boolean; // Enable column sorting
|
55
|
+
render?: (value: any, rowData: Object) => React.ReactNode; // Custom cell renderer
|
56
|
+
|
57
|
+
|
58
|
+
## Basic Usage
|
59
|
+
|
60
|
+
```jsx
|
61
|
+
import { ReactDataTable } from 'react-data-grid';
|
62
|
+
|
63
|
+
const App = () => {
|
64
|
+
const columns = [
|
65
|
+
{ field: 'id', headerName: 'ID' },
|
66
|
+
{ field: 'name', headerName: 'Name' }
|
67
|
+
];
|
68
|
+
|
69
|
+
const data = [
|
70
|
+
{ id: 1, name: 'John' },
|
71
|
+
{ id: 2, name: 'Jane' }
|
72
|
+
];
|
73
|
+
|
74
|
+
return (
|
75
|
+
<ReactDataTable
|
76
|
+
columns={columns}
|
77
|
+
dataSource={data}
|
78
|
+
/>
|
79
|
+
);
|
80
|
+
};
|
@@ -0,0 +1,281 @@
|
|
1
|
+
import React, { useEffect } from 'react';
|
2
|
+
import "./index.css";
|
3
|
+
|
4
|
+
function ReactDataTable({
|
5
|
+
dataSource,
|
6
|
+
columns,
|
7
|
+
loading = false,
|
8
|
+
maxHeight = "600px",
|
9
|
+
height = "600px",
|
10
|
+
showCheckbox = false,
|
11
|
+
onSelectionChange,
|
12
|
+
defaultLimit = 50,
|
13
|
+
onRowClick,
|
14
|
+
livePagination = false,
|
15
|
+
staticData = null,
|
16
|
+
emptyText,
|
17
|
+
rowHeights = 40,
|
18
|
+
headerProps = {} // Added default value
|
19
|
+
}) {
|
20
|
+
const tableContainerRef = React.useRef(null);
|
21
|
+
const [data, setData] = React.useState({ pages: [], meta: { totalPages: 1 } });
|
22
|
+
const [isFetching, setIsFetching] = React.useState(false);
|
23
|
+
const [pageParam, setPageParam] = React.useState(1);
|
24
|
+
const [selectedRows, setSelectedRows] = React.useState({});
|
25
|
+
|
26
|
+
|
27
|
+
useEffect(() => {
|
28
|
+
setData({ pages: [], meta: { totalPages: 1 } });
|
29
|
+
setPageParam(1);
|
30
|
+
|
31
|
+
if (staticData) {
|
32
|
+
setData({
|
33
|
+
pages: [{
|
34
|
+
data: staticData,
|
35
|
+
meta: {
|
36
|
+
totalPages: 1,
|
37
|
+
totalCount: staticData.length
|
38
|
+
}
|
39
|
+
}],
|
40
|
+
meta: {
|
41
|
+
totalPages: 1,
|
42
|
+
totalCount: staticData.length
|
43
|
+
}
|
44
|
+
});
|
45
|
+
} else {
|
46
|
+
loadInitialData();
|
47
|
+
}
|
48
|
+
}, [dataSource, staticData]);
|
49
|
+
|
50
|
+
const loadInitialData = async () => {
|
51
|
+
if (!dataSource) return;
|
52
|
+
|
53
|
+
try {
|
54
|
+
const initialData = await dataSource({ skip: 0, limit: defaultLimit });
|
55
|
+
setData({
|
56
|
+
pages: [initialData],
|
57
|
+
meta: initialData.meta
|
58
|
+
});
|
59
|
+
setPageParam(1);
|
60
|
+
} catch (error) {
|
61
|
+
console.error('Error loading initial data:', error);
|
62
|
+
}
|
63
|
+
};
|
64
|
+
|
65
|
+
const fetchNextPage = async () => {
|
66
|
+
if (isFetching || !livePagination || staticData) return;
|
67
|
+
|
68
|
+
setIsFetching(true);
|
69
|
+
try {
|
70
|
+
const nextData = await dataSource({
|
71
|
+
skip: pageParam * defaultLimit,
|
72
|
+
limit: defaultLimit
|
73
|
+
});
|
74
|
+
setData(prev => ({
|
75
|
+
pages: [...prev.pages, nextData],
|
76
|
+
meta: nextData.meta
|
77
|
+
}));
|
78
|
+
setPageParam(prev => prev + 1);
|
79
|
+
} catch (error) {
|
80
|
+
console.error('Error fetching next page:', error);
|
81
|
+
} finally {
|
82
|
+
setIsFetching(false);
|
83
|
+
}
|
84
|
+
};
|
85
|
+
|
86
|
+
const handleSelectAll = (checked, flatData) => {
|
87
|
+
if (checked) {
|
88
|
+
const newSelected = {};
|
89
|
+
flatData.forEach(row => {
|
90
|
+
newSelected[row.id] = { ...row };
|
91
|
+
});
|
92
|
+
setSelectedRows(newSelected);
|
93
|
+
onSelectionChange?.({
|
94
|
+
data: flatData,
|
95
|
+
selected: true,
|
96
|
+
originalData: flatData,
|
97
|
+
unselected: null
|
98
|
+
});
|
99
|
+
} else {
|
100
|
+
setSelectedRows({});
|
101
|
+
onSelectionChange?.({
|
102
|
+
data: [],
|
103
|
+
selected: {},
|
104
|
+
originalData: data.pages.data
|
105
|
+
});
|
106
|
+
}
|
107
|
+
};
|
108
|
+
|
109
|
+
const handleSelectRow = (checked, row, flatData) => {
|
110
|
+
const rowId = row.id;
|
111
|
+
if (checked) {
|
112
|
+
setSelectedRows(prev => ({
|
113
|
+
...prev,
|
114
|
+
[rowId]: { ...row }
|
115
|
+
}));
|
116
|
+
onSelectionChange?.({
|
117
|
+
data: { ...row },
|
118
|
+
selected: { ...selectedRows, [rowId]: { ...row } },
|
119
|
+
[rowId]: { ...row },
|
120
|
+
originalData: [...flatData]
|
121
|
+
});
|
122
|
+
} else {
|
123
|
+
const updatedSelectedRows = { ...selectedRows };
|
124
|
+
delete updatedSelectedRows[rowId];
|
125
|
+
|
126
|
+
setSelectedRows(updatedSelectedRows);
|
127
|
+
onSelectionChange?.({
|
128
|
+
data: { ...row },
|
129
|
+
selected: { ...updatedSelectedRows },
|
130
|
+
originalData: [...flatData]
|
131
|
+
});
|
132
|
+
}
|
133
|
+
};
|
134
|
+
|
135
|
+
const handleRowClick = (row, index, flatData) => {
|
136
|
+
onRowClick?.({
|
137
|
+
data: { ...row },
|
138
|
+
dataSourceArray: flatData,
|
139
|
+
rowIndex: index
|
140
|
+
});
|
141
|
+
};
|
142
|
+
|
143
|
+
const handleScroll = (containerRefElement) => {
|
144
|
+
if (containerRefElement) {
|
145
|
+
const { scrollHeight, scrollTop, clientHeight } = containerRefElement;
|
146
|
+
if (
|
147
|
+
scrollHeight - scrollTop - clientHeight < 500 &&
|
148
|
+
!isFetching &&
|
149
|
+
pageParam < data.meta.totalPages
|
150
|
+
) {
|
151
|
+
fetchNextPage();
|
152
|
+
}
|
153
|
+
}
|
154
|
+
};
|
155
|
+
|
156
|
+
useEffect(() => {
|
157
|
+
handleScroll(tableContainerRef.current);
|
158
|
+
}, [data]);
|
159
|
+
|
160
|
+
const flatData = data.pages.flatMap(page => page.data);
|
161
|
+
|
162
|
+
const checkboxColumn = {
|
163
|
+
id: 'select',
|
164
|
+
size: 50,
|
165
|
+
minWidth: 50,
|
166
|
+
textAlign: "center",
|
167
|
+
header: ({ data }) => (
|
168
|
+
<div className="flex items-center justify-center h-[40px]">
|
169
|
+
<input
|
170
|
+
type="checkbox"
|
171
|
+
checked={Object.keys(selectedRows).length > 0 && data.every(row => selectedRows[row.id])}
|
172
|
+
onChange={(e) => handleSelectAll(e.target.checked, flatData)}
|
173
|
+
/>
|
174
|
+
</div>
|
175
|
+
),
|
176
|
+
cell: ({ row }) => (
|
177
|
+
<div className="flex items-center justify-center h-[40px]">
|
178
|
+
<input
|
179
|
+
type="checkbox"
|
180
|
+
checked={!!selectedRows[row.id]}
|
181
|
+
onChange={(e) => handleSelectRow(e.target.checked, row, flatData)}
|
182
|
+
/>
|
183
|
+
</div>
|
184
|
+
),
|
185
|
+
};
|
186
|
+
|
187
|
+
const enhancedColumns = showCheckbox ? [checkboxColumn, ...columns] : columns;
|
188
|
+
|
189
|
+
return (
|
190
|
+
<div className="bg-white relative w-full">
|
191
|
+
{loading && <div className="absolute inset-0 bg-white/50 z-20 flex items-center justify-center">
|
192
|
+
<div role="status" className="p-2">
|
193
|
+
<svg
|
194
|
+
aria-hidden="true"
|
195
|
+
className="inline w-8 h-8 mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-green-500"
|
196
|
+
viewBox="0 0 100 101"
|
197
|
+
fill="none"
|
198
|
+
xmlns="http://www.w3.org/2000/svg"
|
199
|
+
>
|
200
|
+
<path
|
201
|
+
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908Z"
|
202
|
+
fill="#555e68"
|
203
|
+
/>
|
204
|
+
</svg>
|
205
|
+
</div>
|
206
|
+
</div>}
|
207
|
+
{flatData.length === 0 && !loading ? (
|
208
|
+
<div className="flex items-center justify-center" style={{ height }}>
|
209
|
+
<div className="text-gray-500">
|
210
|
+
{emptyText || 'No data available'}
|
211
|
+
</div>
|
212
|
+
</div>
|
213
|
+
) : (
|
214
|
+
<div className="overflow-hidden">
|
215
|
+
<div
|
216
|
+
ref={tableContainerRef}
|
217
|
+
className="overflow-auto w-full"
|
218
|
+
style={{ maxHeight, height }}
|
219
|
+
onScroll={(e) => handleScroll(e.currentTarget)}
|
220
|
+
>
|
221
|
+
<table className="w-full border-collapse">
|
222
|
+
<thead
|
223
|
+
className="sticky top-0 z-1 bg-blue-300"
|
224
|
+
style={{ ...headerProps.style }}
|
225
|
+
>
|
226
|
+
<tr>
|
227
|
+
{enhancedColumns.map(column => (
|
228
|
+
<th
|
229
|
+
key={column.accessorKey || column.id}
|
230
|
+
className="text-left font-normal h-[40px]"
|
231
|
+
style={{
|
232
|
+
width: column.size,
|
233
|
+
minWidth: column.minWidth,
|
234
|
+
textAlign: column.textAlign,
|
235
|
+
}}
|
236
|
+
>
|
237
|
+
{typeof column.header === 'function' ? column.header({ data: flatData }) : column.header}
|
238
|
+
</th>
|
239
|
+
))}
|
240
|
+
</tr>
|
241
|
+
</thead>
|
242
|
+
<tbody>
|
243
|
+
{flatData.length > 0 ? (
|
244
|
+
flatData.map((row, index) => (
|
245
|
+
<tr
|
246
|
+
key={row.id}
|
247
|
+
className={`border-t border-x border-gray-200 hover:bg-[#dee1f2] h-[${rowHeights}px] ${selectedRows[row.id] ? 'bg-[#dee1f2]' : ''}`}
|
248
|
+
onClick={() => handleRowClick(row, index, flatData)}
|
249
|
+
>
|
250
|
+
{enhancedColumns.map(column => (
|
251
|
+
<td
|
252
|
+
key={column.accessorKey || column.id}
|
253
|
+
className={`text-left font-normal border-r ${column?.cellProps?.className || ''}`}
|
254
|
+
style={{
|
255
|
+
minWidth: `${column.minWidth}px`,
|
256
|
+
textAlign: column?.textAlign,
|
257
|
+
...column?.cellProps?.style,
|
258
|
+
}}
|
259
|
+
>
|
260
|
+
{typeof column.cell === 'function' ? column.cell({ row }) : null}
|
261
|
+
</td>
|
262
|
+
))}
|
263
|
+
</tr>
|
264
|
+
))
|
265
|
+
) : (
|
266
|
+
<tr>
|
267
|
+
<td colSpan={enhancedColumns.length} className="text-center py-4">
|
268
|
+
{emptyText || 'No data available'}
|
269
|
+
</td>
|
270
|
+
</tr>
|
271
|
+
)}
|
272
|
+
</tbody>
|
273
|
+
</table>
|
274
|
+
</div>
|
275
|
+
</div>
|
276
|
+
)}
|
277
|
+
</div>
|
278
|
+
);
|
279
|
+
}
|
280
|
+
|
281
|
+
export default ReactDataTable;
|
package/src/index.css
ADDED
package/src/index.js
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export { default as ReactDataTable } from './ReactDataTable.jsx';
|