react-excel-lite 0.2.0 → 0.4.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 +147 -26
- package/dist/components/excel-grid.d.ts +1 -1
- package/dist/components/excel-grid.d.ts.map +1 -1
- package/dist/components/grid-cell.d.ts +1 -1
- package/dist/components/grid-cell.d.ts.map +1 -1
- package/dist/react-excel-lite.js +479 -474
- package/dist/react-excel-lite.umd.cjs +2 -2
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,15 +8,22 @@
|
|
|
8
8
|
|
|
9
9
|
A lightweight, Excel-like editable grid component for React.
|
|
10
10
|
|
|
11
|
+
## Demo
|
|
12
|
+
|
|
13
|
+
[react-excel-lite-demo](https://prkgnt.github.io/react-excel-lite/)
|
|
14
|
+
|
|
11
15
|
## Features
|
|
12
16
|
|
|
13
17
|
- Excel-like cell selection (click & drag)
|
|
18
|
+
- Keyboard navigation (Arrow keys to move, Shift+Arrow to extend selection)
|
|
14
19
|
- Copy/Paste support (Ctrl+C / Ctrl+V)
|
|
15
20
|
- Auto Fill with arithmetic sequence detection (drag fill handle)
|
|
16
|
-
- Grouped column headers with
|
|
17
|
-
-
|
|
18
|
-
-
|
|
21
|
+
- Grouped column headers with rowSpan support
|
|
22
|
+
- Grouped row headers with rowSpan support
|
|
23
|
+
- Click outside to clear selection
|
|
24
|
+
- Double-click or type to edit cells
|
|
19
25
|
- Expandable input field when editing (text overflow handling)
|
|
26
|
+
- Keyboard shortcuts (Delete/Backspace to clear)
|
|
20
27
|
- **Styling-agnostic**: Works with Tailwind CSS, CSS Modules, plain CSS, or any styling solution
|
|
21
28
|
- Zero external dependencies
|
|
22
29
|
|
|
@@ -45,15 +52,16 @@ function App() {
|
|
|
45
52
|
|
|
46
53
|
## Props
|
|
47
54
|
|
|
48
|
-
| Prop | Type
|
|
49
|
-
| ---------------- |
|
|
50
|
-
| `data` | `string[][]`
|
|
51
|
-
| `onChange` | `(data: string[][]) => void`
|
|
52
|
-
| `rowHeaders` | `HeaderGroup[]`
|
|
53
|
-
| `colHeaders` | `HeaderGroup[]`
|
|
54
|
-
| `className` | `string`
|
|
55
|
-
| `rowHeaderTitle` | `string`
|
|
56
|
-
| `styles` | `GridStyles`
|
|
55
|
+
| Prop | Type | Required | Description |
|
|
56
|
+
| ---------------- | ----------------------------------------- | -------- | ---------------------------------- |
|
|
57
|
+
| `data` | `string[][]` | Yes | 2D array of strings |
|
|
58
|
+
| `onChange` | `(data: string[][]) => void` | Yes | Callback when data changes |
|
|
59
|
+
| `rowHeaders` | `HeaderGroup[]` | No | Grouped row headers |
|
|
60
|
+
| `colHeaders` | `HeaderGroup[]` | No | Grouped column headers |
|
|
61
|
+
| `className` | `string` | No | CSS class for container |
|
|
62
|
+
| `rowHeaderTitle` | `string` | No | Title for row header column |
|
|
63
|
+
| `styles` | `GridStyles` | No | Style configuration object |
|
|
64
|
+
| `cellStyles` | `(coord: CellCoord) => string|undefined` | No | Function to style individual cells |
|
|
57
65
|
|
|
58
66
|
## With Headers
|
|
59
67
|
|
|
@@ -71,16 +79,18 @@ function App() {
|
|
|
71
79
|
const colHeaders: HeaderGroup[] = [
|
|
72
80
|
{
|
|
73
81
|
label: "Q1",
|
|
82
|
+
description: "First quarter",
|
|
74
83
|
headers: [
|
|
75
|
-
{ key: "jan", label: "Jan" },
|
|
76
|
-
{ key: "feb", label: "Feb" },
|
|
84
|
+
{ key: "jan", label: "Jan", description: "January" },
|
|
85
|
+
{ key: "feb", label: "Feb", description: "February" },
|
|
77
86
|
],
|
|
78
87
|
},
|
|
79
88
|
{
|
|
80
89
|
label: "Q2",
|
|
90
|
+
description: "Second quarter",
|
|
81
91
|
headers: [
|
|
82
|
-
{ key: "mar", label: "Mar" },
|
|
83
|
-
{ key: "apr", label: "Apr" },
|
|
92
|
+
{ key: "mar", label: "Mar", description: "March" },
|
|
93
|
+
{ key: "apr", label: "Apr", description: "April" },
|
|
84
94
|
],
|
|
85
95
|
},
|
|
86
96
|
];
|
|
@@ -88,6 +98,7 @@ function App() {
|
|
|
88
98
|
const rowHeaders: HeaderGroup[] = [
|
|
89
99
|
{
|
|
90
100
|
label: "Products",
|
|
101
|
+
description: "Product categories",
|
|
91
102
|
headers: [
|
|
92
103
|
{ key: "prodA", label: "Product A", description: "Main product line" },
|
|
93
104
|
{ key: "prodB", label: "Product B", description: "Secondary product" },
|
|
@@ -101,12 +112,53 @@ function App() {
|
|
|
101
112
|
onChange={setData}
|
|
102
113
|
colHeaders={colHeaders}
|
|
103
114
|
rowHeaders={rowHeaders}
|
|
104
|
-
rowHeaderTitle="
|
|
115
|
+
rowHeaderTitle="Category"
|
|
105
116
|
/>
|
|
106
117
|
);
|
|
107
118
|
}
|
|
108
119
|
```
|
|
109
120
|
|
|
121
|
+
## Simple Headers (Without Group Labels)
|
|
122
|
+
|
|
123
|
+
When all HeaderGroups have no `label`, the grid displays a single header row/column instead of two levels:
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
const colHeaders: HeaderGroup[] = [
|
|
127
|
+
{
|
|
128
|
+
// No label - single header row
|
|
129
|
+
headers: [
|
|
130
|
+
{ key: "jan", label: "Jan" },
|
|
131
|
+
{ key: "feb", label: "Feb" },
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
headers: [
|
|
136
|
+
{ key: "mar", label: "Mar" },
|
|
137
|
+
{ key: "apr", label: "Apr" },
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
const rowHeaders: HeaderGroup[] = [
|
|
143
|
+
{
|
|
144
|
+
// No label - single header column
|
|
145
|
+
headers: [
|
|
146
|
+
{ key: "row1", label: "Row 1" },
|
|
147
|
+
{ key: "row2", label: "Row 2" },
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
<ExcelGrid
|
|
153
|
+
data={data}
|
|
154
|
+
onChange={setData}
|
|
155
|
+
colHeaders={colHeaders}
|
|
156
|
+
rowHeaders={rowHeaders}
|
|
157
|
+
/>;
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
If at least one group has a `label`, the grid shows the two-level layout (group labels + individual headers).
|
|
161
|
+
|
|
110
162
|
## Styling
|
|
111
163
|
|
|
112
164
|
The component comes with sensible default styles built-in. You can customize styles using the `styles` prop with CSS class strings from any styling solution.
|
|
@@ -218,14 +270,77 @@ Style individual row headers:
|
|
|
218
270
|
const rowHeaders: HeaderGroup[] = [
|
|
219
271
|
{
|
|
220
272
|
label: "Regions",
|
|
273
|
+
className: "bg-slate-700 text-white",
|
|
221
274
|
headers: [
|
|
222
|
-
{
|
|
223
|
-
|
|
275
|
+
{
|
|
276
|
+
key: "regionA",
|
|
277
|
+
label: "Region A",
|
|
278
|
+
className: "bg-slate-600 text-white",
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
key: "regionB",
|
|
282
|
+
label: "Region B",
|
|
283
|
+
className: "bg-slate-500 text-white",
|
|
284
|
+
},
|
|
224
285
|
],
|
|
225
286
|
},
|
|
226
287
|
];
|
|
227
288
|
```
|
|
228
289
|
|
|
290
|
+
### Styling Individual Cells
|
|
291
|
+
|
|
292
|
+
Use the `cellStyles` prop to apply styles to specific cells based on their coordinates:
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
import { useCallback } from "react";
|
|
296
|
+
import type { CellCoord } from "react-excel-lite";
|
|
297
|
+
|
|
298
|
+
function App() {
|
|
299
|
+
const [data, setData] = useState([
|
|
300
|
+
["100", "200", "300"],
|
|
301
|
+
["400", "500", "600"],
|
|
302
|
+
["700", "800", "900"],
|
|
303
|
+
]);
|
|
304
|
+
|
|
305
|
+
// Memoize to prevent unnecessary re-renders
|
|
306
|
+
const cellStyles = useCallback((coord: CellCoord) => {
|
|
307
|
+
// Highlight first row
|
|
308
|
+
if (coord.row === 0) return "bg-yellow-100";
|
|
309
|
+
// Highlight specific cell
|
|
310
|
+
if (coord.row === 1 && coord.col === 1) return "bg-red-100 font-bold";
|
|
311
|
+
// Highlight cells with negative values (check data)
|
|
312
|
+
return undefined;
|
|
313
|
+
}, []);
|
|
314
|
+
|
|
315
|
+
return <ExcelGrid data={data} onChange={setData} cellStyles={cellStyles} />;
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Common use cases:
|
|
320
|
+
|
|
321
|
+
- Highlight header rows or columns
|
|
322
|
+
- Show validation errors (e.g., red background for invalid cells)
|
|
323
|
+
- Conditional formatting based on cell values
|
|
324
|
+
- Alternating row colors
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
// Alternating row colors
|
|
328
|
+
const cellStyles = useCallback((coord: CellCoord) => {
|
|
329
|
+
return coord.row % 2 === 0 ? "bg-gray-50" : "bg-white";
|
|
330
|
+
}, []);
|
|
331
|
+
|
|
332
|
+
// Value-based styling (check data in callback)
|
|
333
|
+
const cellStyles = useCallback(
|
|
334
|
+
(coord: CellCoord) => {
|
|
335
|
+
const value = Number(data[coord.row]?.[coord.col]);
|
|
336
|
+
if (value < 0) return "bg-red-100 text-red-700";
|
|
337
|
+
if (value > 1000) return "bg-green-100 text-green-700";
|
|
338
|
+
return undefined;
|
|
339
|
+
},
|
|
340
|
+
[data],
|
|
341
|
+
);
|
|
342
|
+
```
|
|
343
|
+
|
|
229
344
|
## Auto Fill (Arithmetic Sequence)
|
|
230
345
|
|
|
231
346
|
Select cells with a numeric pattern and drag the fill handle to auto-fill:
|
|
@@ -237,11 +352,16 @@ Select cells with a numeric pattern and drag the fill handle to auto-fill:
|
|
|
237
352
|
|
|
238
353
|
## Keyboard Shortcuts
|
|
239
354
|
|
|
240
|
-
| Shortcut | Action
|
|
241
|
-
| ---------------------- |
|
|
242
|
-
| `
|
|
243
|
-
| `
|
|
244
|
-
| `
|
|
355
|
+
| Shortcut | Action |
|
|
356
|
+
| ---------------------- | --------------------------------- |
|
|
357
|
+
| `Arrow Keys` | Move selection |
|
|
358
|
+
| `Shift + Arrow Keys` | Extend selection range |
|
|
359
|
+
| `Enter` | Enter edit mode (select all text) |
|
|
360
|
+
| `Any character` | Enter edit mode and start typing |
|
|
361
|
+
| `Escape` | Exit edit mode |
|
|
362
|
+
| `Ctrl+C` / `Cmd+C` | Copy selected cells |
|
|
363
|
+
| `Ctrl+V` / `Cmd+V` | Paste from clipboard |
|
|
364
|
+
| `Delete` / `Backspace` | Clear selected cells |
|
|
245
365
|
|
|
246
366
|
## Exports
|
|
247
367
|
|
|
@@ -253,7 +373,7 @@ Select cells with a numeric pattern and drag the fill handle to auto-fill:
|
|
|
253
373
|
### Hooks
|
|
254
374
|
|
|
255
375
|
- `useGridSelection` - Cell selection logic
|
|
256
|
-
- `useGridClipboard` - Copy/paste logic
|
|
376
|
+
- `useGridClipboard` - Copy/paste and keyboard navigation logic
|
|
257
377
|
- `useGridDragFill` - Fill handle logic
|
|
258
378
|
|
|
259
379
|
### Utilities
|
|
@@ -289,7 +409,7 @@ interface Header {
|
|
|
289
409
|
}
|
|
290
410
|
|
|
291
411
|
interface HeaderGroup {
|
|
292
|
-
label
|
|
412
|
+
label?: string;
|
|
293
413
|
headers: Header[];
|
|
294
414
|
description?: string;
|
|
295
415
|
className?: string;
|
|
@@ -313,6 +433,7 @@ interface ExcelGridProps {
|
|
|
313
433
|
className?: string;
|
|
314
434
|
rowHeaderTitle?: string;
|
|
315
435
|
styles?: GridStyles;
|
|
436
|
+
cellStyles?: (coord: CellCoord) => string | undefined;
|
|
316
437
|
}
|
|
317
438
|
```
|
|
318
439
|
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { ExcelGridProps } from '../types';
|
|
2
|
-
export declare function ExcelGrid({ data, onChange, rowHeaders, colHeaders, className, rowHeaderTitle, styles, }: ExcelGridProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function ExcelGrid({ data, onChange, rowHeaders, colHeaders, className, rowHeaderTitle, styles, cellStyles, }: ExcelGridProps): import("react/jsx-runtime").JSX.Element;
|
|
3
3
|
//# sourceMappingURL=excel-grid.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"excel-grid.d.ts","sourceRoot":"","sources":["../../src/components/excel-grid.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAa,MAAM,UAAU,CAAC;AAsE1D,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,QAAQ,EACR,UAAU,EACV,UAAU,EACV,SAAS,EACT,cAAmB,EACnB,MAAM,
|
|
1
|
+
{"version":3,"file":"excel-grid.d.ts","sourceRoot":"","sources":["../../src/components/excel-grid.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAa,MAAM,UAAU,CAAC;AAsE1D,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,QAAQ,EACR,UAAU,EACV,UAAU,EACV,SAAS,EACT,cAAmB,EACnB,MAAM,EACN,UAAU,GACX,EAAE,cAAc,2CAmThB"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { GridCellProps } from '../types';
|
|
2
|
-
export declare function GridCell({ coord, value, isSelected, isFillTarget, showFillHandle, onMouseDown, onMouseEnter, onChange, onFillHandleMouseDown, styles, }: GridCellProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function GridCell({ coord, value, isSelected, isFillTarget, showFillHandle, onMouseDown, onMouseEnter, onChange, onFillHandleMouseDown, styles, cellClassName, }: GridCellProps): import("react/jsx-runtime").JSX.Element;
|
|
3
3
|
//# sourceMappingURL=grid-cell.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grid-cell.d.ts","sourceRoot":"","sources":["../../src/components/grid-cell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AA8E9C,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,KAAK,EACL,UAAU,EACV,YAAY,EACZ,cAAc,EACd,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,qBAAqB,EACrB,MAAM,
|
|
1
|
+
{"version":3,"file":"grid-cell.d.ts","sourceRoot":"","sources":["../../src/components/grid-cell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AA8E9C,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,KAAK,EACL,UAAU,EACV,YAAY,EACZ,cAAc,EACd,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,qBAAqB,EACrB,MAAM,EACN,aAAa,GACd,EAAE,aAAa,2CA6Kf"}
|