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 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 custom styling
17
- - Row headers with custom styling
18
- - Keyboard shortcuts (Delete/Backspace to clear)
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 | Required | Description |
49
- | ---------------- | ---------------------------- | -------- | --------------------------- |
50
- | `data` | `string[][]` | Yes | 2D array of strings |
51
- | `onChange` | `(data: string[][]) => void` | Yes | Callback when data changes |
52
- | `rowHeaders` | `HeaderGroup[]` | No | Row header definitions |
53
- | `colHeaders` | `HeaderGroup[]` | No | Grouped column headers |
54
- | `className` | `string` | No | CSS class for container |
55
- | `rowHeaderTitle` | `string` | No | Title for row header column |
56
- | `styles` | `GridStyles` | No | Style configuration object |
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="Product"
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
- { key: "regionA", label: "Region A", className: "bg-slate-700 text-white" },
223
- { key: "regionB", label: "Region B", className: "bg-slate-600 text-white" },
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
- | `Ctrl+C` / `Cmd+C` | Copy selected cells |
243
- | `Ctrl+V` / `Cmd+V` | Paste from clipboard |
244
- | `Delete` / `Backspace` | Clear selected cells |
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: string;
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,GACP,EAAE,cAAc,2CAoShB"}
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,GACP,EAAE,aAAa,2CA4Kf"}
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"}