@xcelsior/ui-spreadsheets 1.0.3 → 1.0.5
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/.turbo/turbo-lint.log +61 -0
- package/CHANGELOG.md +6 -0
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +280 -174
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +280 -174
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/ColumnHeaderActions.tsx +3 -2
- package/src/components/CommentModals.tsx +28 -3
- package/src/components/Spreadsheet.stories.tsx +1 -2
- package/src/components/Spreadsheet.tsx +250 -145
- package/src/components/SpreadsheetCell.tsx +38 -48
- package/src/hooks/useSpreadsheetComments.ts +56 -54
- package/src/hooks/useSpreadsheetFiltering.ts +38 -4
- package/src/hooks/useSpreadsheetHighlighting.ts +27 -4
- package/src/types.ts +4 -4
- package/.turbo/turbo-build.log +0 -22
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
import { useState, useRef, useEffect } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
HiOutlineAnnotation,
|
|
7
|
-
HiOutlineChatAlt,
|
|
8
|
-
HiOutlinePencil,
|
|
9
|
-
} from 'react-icons/hi';
|
|
3
|
+
import { HiOutlineClipboardCopy, HiOutlineClipboardCheck } from 'react-icons/hi';
|
|
4
|
+
import { AiFillHighlight } from 'react-icons/ai';
|
|
5
|
+
import { FaComment, FaRegComment } from 'react-icons/fa';
|
|
10
6
|
import { cn } from '../utils';
|
|
11
7
|
import type { SpreadsheetCellProps } from '../types';
|
|
12
8
|
|
|
@@ -225,7 +221,7 @@ export const SpreadsheetCell: React.FC<SpreadsheetCellProps> = ({
|
|
|
225
221
|
{isEditing ? (
|
|
226
222
|
renderEditInput()
|
|
227
223
|
) : (
|
|
228
|
-
<div className="flex items-center gap-1">
|
|
224
|
+
<div className="flex items-center gap-1 relative">
|
|
229
225
|
{/* Main content */}
|
|
230
226
|
<div
|
|
231
227
|
className={cn(
|
|
@@ -238,34 +234,9 @@ export const SpreadsheetCell: React.FC<SpreadsheetCellProps> = ({
|
|
|
238
234
|
{renderContent()}
|
|
239
235
|
</div>
|
|
240
236
|
|
|
241
|
-
{/*
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
type="button"
|
|
245
|
-
onClick={(e) => {
|
|
246
|
-
e.stopPropagation();
|
|
247
|
-
onViewComments?.();
|
|
248
|
-
}}
|
|
249
|
-
className="p-0.5 hover:bg-gray-100 rounded relative shrink-0"
|
|
250
|
-
title={`${unresolvedCommentCount} unresolved comment(s)`}
|
|
251
|
-
>
|
|
252
|
-
<HiOutlineChatAlt
|
|
253
|
-
className={cn(
|
|
254
|
-
'h-3 w-3',
|
|
255
|
-
unresolvedCommentCount > 0 ? 'text-amber-500' : 'text-gray-400'
|
|
256
|
-
)}
|
|
257
|
-
/>
|
|
258
|
-
{unresolvedCommentCount > 0 && (
|
|
259
|
-
<span className="absolute -top-1 -right-1 bg-amber-500 text-white text-[8px] rounded-full w-3 h-3 flex items-center justify-center">
|
|
260
|
-
{unresolvedCommentCount}
|
|
261
|
-
</span>
|
|
262
|
-
)}
|
|
263
|
-
</button>
|
|
264
|
-
)}
|
|
265
|
-
|
|
266
|
-
{/* Action buttons - show on hover */}
|
|
267
|
-
<div className="opacity-0 group-hover:opacity-100 flex items-center gap-0.5 transition-opacity shrink-0">
|
|
268
|
-
{/* Copy down button */}
|
|
237
|
+
{/* Action buttons - show on hover, except comment indicator which is always visible */}
|
|
238
|
+
<div className="flex items-center gap-0.5 shrink-0">
|
|
239
|
+
{/* Copy down button - hover only */}
|
|
269
240
|
{value !== null && value !== undefined && value !== '' && onCopyDown && (
|
|
270
241
|
<button
|
|
271
242
|
type="button"
|
|
@@ -273,14 +244,14 @@ export const SpreadsheetCell: React.FC<SpreadsheetCellProps> = ({
|
|
|
273
244
|
e.stopPropagation();
|
|
274
245
|
onCopyDown();
|
|
275
246
|
}}
|
|
276
|
-
className="p-0.5 bg-gray-100 hover:bg-gray-200 rounded"
|
|
247
|
+
className="opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-gray-100 hover:bg-gray-200 rounded"
|
|
277
248
|
title="Copy value down to rows below"
|
|
278
249
|
>
|
|
279
250
|
<HiOutlineClipboardCopy className="h-2.5 w-2.5 text-gray-500" />
|
|
280
251
|
</button>
|
|
281
252
|
)}
|
|
282
253
|
|
|
283
|
-
{/* Copy to selected button */}
|
|
254
|
+
{/* Copy to selected button - hover only */}
|
|
284
255
|
{hasSelectedRows &&
|
|
285
256
|
value !== null &&
|
|
286
257
|
value !== undefined &&
|
|
@@ -292,14 +263,14 @@ export const SpreadsheetCell: React.FC<SpreadsheetCellProps> = ({
|
|
|
292
263
|
e.stopPropagation();
|
|
293
264
|
onCopyToSelected();
|
|
294
265
|
}}
|
|
295
|
-
className="p-0.5 bg-green-100 hover:bg-green-200 rounded"
|
|
266
|
+
className="opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-green-100 hover:bg-green-200 rounded"
|
|
296
267
|
title="Copy to selected rows"
|
|
297
268
|
>
|
|
298
269
|
<HiOutlineClipboardCheck className="h-2.5 w-2.5 text-green-600" />
|
|
299
270
|
</button>
|
|
300
271
|
)}
|
|
301
272
|
|
|
302
|
-
{/* Highlight button */}
|
|
273
|
+
{/* Highlight button - hover only */}
|
|
303
274
|
{onHighlight && (
|
|
304
275
|
<button
|
|
305
276
|
type="button"
|
|
@@ -307,32 +278,51 @@ export const SpreadsheetCell: React.FC<SpreadsheetCellProps> = ({
|
|
|
307
278
|
e.stopPropagation();
|
|
308
279
|
onHighlight();
|
|
309
280
|
}}
|
|
310
|
-
className="p-0.5 hover:bg-gray-
|
|
281
|
+
className="opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-gray-100 hover:bg-gray-200 rounded"
|
|
311
282
|
title="Highlight cell"
|
|
312
283
|
>
|
|
313
|
-
<
|
|
284
|
+
<AiFillHighlight
|
|
314
285
|
className={cn(
|
|
315
286
|
'h-2.5 w-2.5',
|
|
316
|
-
highlightColor ? 'text-amber-500' : 'text-gray-
|
|
287
|
+
highlightColor ? 'text-amber-500' : 'text-gray-500'
|
|
317
288
|
)}
|
|
318
289
|
/>
|
|
319
290
|
</button>
|
|
320
291
|
)}
|
|
321
292
|
|
|
322
|
-
{/*
|
|
323
|
-
{
|
|
293
|
+
{/* Comment button - always visible when has comments, hover only when adding */}
|
|
294
|
+
{hasComments && onViewComments ? (
|
|
295
|
+
<button
|
|
296
|
+
type="button"
|
|
297
|
+
onClick={(e) => {
|
|
298
|
+
e.stopPropagation();
|
|
299
|
+
onViewComments();
|
|
300
|
+
}}
|
|
301
|
+
className="p-0.5 bg-amber-100 hover:bg-amber-200 rounded transition-colors flex items-center gap-0.5"
|
|
302
|
+
title={`${unresolvedCommentCount} comment(s) - click to view`}
|
|
303
|
+
>
|
|
304
|
+
<FaComment className="h-2.5 w-2.5 text-amber-500" />
|
|
305
|
+
{unresolvedCommentCount > 0 && (
|
|
306
|
+
<span className="text-[9px] font-medium text-amber-600">
|
|
307
|
+
{unresolvedCommentCount > 99
|
|
308
|
+
? '99+'
|
|
309
|
+
: unresolvedCommentCount}
|
|
310
|
+
</span>
|
|
311
|
+
)}
|
|
312
|
+
</button>
|
|
313
|
+
) : onAddComment ? (
|
|
324
314
|
<button
|
|
325
315
|
type="button"
|
|
326
316
|
onClick={(e) => {
|
|
327
317
|
e.stopPropagation();
|
|
328
318
|
onAddComment();
|
|
329
319
|
}}
|
|
330
|
-
className="p-0.5 hover:bg-gray-
|
|
320
|
+
className="opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-gray-100 hover:bg-gray-200 rounded"
|
|
331
321
|
title="Add comment"
|
|
332
322
|
>
|
|
333
|
-
<
|
|
323
|
+
<FaRegComment className="h-2.5 w-2.5 text-gray-500" />
|
|
334
324
|
</button>
|
|
335
|
-
)}
|
|
325
|
+
) : null}
|
|
336
326
|
</div>
|
|
337
327
|
</div>
|
|
338
328
|
)}
|
|
@@ -1,92 +1,94 @@
|
|
|
1
1
|
import { useCallback, useState } from 'react';
|
|
2
|
-
import type { CellComment } from '../types';
|
|
2
|
+
import type { CellComment, CellPosition } from '../types';
|
|
3
3
|
|
|
4
4
|
export interface UseSpreadsheetCommentsOptions {
|
|
5
|
-
/** External
|
|
6
|
-
|
|
7
|
-
/** Callback when a
|
|
8
|
-
|
|
5
|
+
/** External cell comments (controlled mode) */
|
|
6
|
+
externalCellComments?: CellComment[];
|
|
7
|
+
/** Callback when a cell comment is added (controlled mode) */
|
|
8
|
+
onAddCellComment?: (rowId: string | number, columnId: string, comment: string) => void;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export interface UseSpreadsheetCommentsReturn {
|
|
12
12
|
// Comments data
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
cellComments: CellComment[];
|
|
14
|
+
getCellComments: (rowId: string | number, columnId: string) => CellComment[];
|
|
15
|
+
getCellUnresolvedCommentCount: (rowId: string | number, columnId: string) => number;
|
|
16
16
|
|
|
17
17
|
// Add comment modal state
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
commentModalCell: CellPosition | null;
|
|
19
|
+
setCommentModalCell: (cell: CellPosition | null) => void;
|
|
20
20
|
commentText: string;
|
|
21
21
|
setCommentText: (text: string) => void;
|
|
22
22
|
|
|
23
23
|
// View comments modal state
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
viewCommentsCell: CellPosition | null;
|
|
25
|
+
setViewCommentsCell: (cell: CellPosition | null) => void;
|
|
26
26
|
|
|
27
27
|
// Actions
|
|
28
|
-
|
|
28
|
+
handleAddCellComment: (rowId: string | number, columnId: string) => void;
|
|
29
29
|
handleToggleCommentResolved: (commentId: string) => void;
|
|
30
30
|
|
|
31
31
|
// Utility
|
|
32
|
-
|
|
32
|
+
cellHasComments: (rowId: string | number, columnId: string) => boolean;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export function useSpreadsheetComments({
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
externalCellComments,
|
|
37
|
+
onAddCellComment,
|
|
38
38
|
}: UseSpreadsheetCommentsOptions = {}): UseSpreadsheetCommentsReturn {
|
|
39
39
|
// Internal comments state
|
|
40
|
-
const [
|
|
40
|
+
const [cellCommentsInternal, setCellCommentsInternal] = useState<CellComment[]>([]);
|
|
41
41
|
|
|
42
42
|
// Modal states
|
|
43
|
-
const [
|
|
43
|
+
const [commentModalCell, setCommentModalCell] = useState<CellPosition | null>(null);
|
|
44
44
|
const [commentText, setCommentText] = useState('');
|
|
45
|
-
const [
|
|
45
|
+
const [viewCommentsCell, setViewCommentsCell] = useState<CellPosition | null>(null);
|
|
46
46
|
|
|
47
47
|
// Use external comments if provided, otherwise use internal
|
|
48
|
-
const
|
|
48
|
+
const cellComments = externalCellComments || cellCommentsInternal;
|
|
49
49
|
|
|
50
|
-
// Get comments for a specific
|
|
51
|
-
const
|
|
52
|
-
(rowId: string | number): CellComment[] => {
|
|
53
|
-
return
|
|
50
|
+
// Get comments for a specific cell
|
|
51
|
+
const getCellComments = useCallback(
|
|
52
|
+
(rowId: string | number, columnId: string): CellComment[] => {
|
|
53
|
+
return cellComments.filter((c) => c.rowId === rowId && c.columnId === columnId);
|
|
54
54
|
},
|
|
55
|
-
[
|
|
55
|
+
[cellComments]
|
|
56
56
|
);
|
|
57
57
|
|
|
58
|
-
// Get unresolved comment count for a
|
|
59
|
-
const
|
|
60
|
-
(rowId: string | number): number => {
|
|
61
|
-
return
|
|
62
|
-
.
|
|
58
|
+
// Get unresolved comment count for a cell
|
|
59
|
+
const getCellUnresolvedCommentCount = useCallback(
|
|
60
|
+
(rowId: string | number, columnId: string): number => {
|
|
61
|
+
return cellComments.filter(
|
|
62
|
+
(c) => c.rowId === rowId && c.columnId === columnId && !c.resolved
|
|
63
|
+
).length;
|
|
63
64
|
},
|
|
64
|
-
[
|
|
65
|
+
[cellComments]
|
|
65
66
|
);
|
|
66
67
|
|
|
67
|
-
// Check if
|
|
68
|
-
const
|
|
69
|
-
(rowId: string | number): boolean => {
|
|
70
|
-
return
|
|
68
|
+
// Check if cell has comments
|
|
69
|
+
const cellHasComments = useCallback(
|
|
70
|
+
(rowId: string | number, columnId: string): boolean => {
|
|
71
|
+
return cellComments.some((c) => c.rowId === rowId && c.columnId === columnId);
|
|
71
72
|
},
|
|
72
|
-
[
|
|
73
|
+
[cellComments]
|
|
73
74
|
);
|
|
74
75
|
|
|
75
|
-
// Add a
|
|
76
|
-
const
|
|
77
|
-
(rowId: string | number) => {
|
|
76
|
+
// Add a cell comment
|
|
77
|
+
const handleAddCellComment = useCallback(
|
|
78
|
+
(rowId: string | number, columnId: string) => {
|
|
78
79
|
if (!commentText.trim()) return;
|
|
79
80
|
|
|
80
|
-
if (
|
|
81
|
+
if (onAddCellComment) {
|
|
81
82
|
// Controlled mode
|
|
82
|
-
|
|
83
|
+
onAddCellComment(rowId, columnId, commentText);
|
|
83
84
|
} else {
|
|
84
85
|
// Uncontrolled mode
|
|
85
|
-
|
|
86
|
+
setCellCommentsInternal((prev) => [
|
|
86
87
|
...prev,
|
|
87
88
|
{
|
|
88
89
|
id: `comment-${Date.now()}`,
|
|
89
90
|
rowId,
|
|
91
|
+
columnId,
|
|
90
92
|
text: commentText,
|
|
91
93
|
timestamp: new Date(),
|
|
92
94
|
resolved: false,
|
|
@@ -94,39 +96,39 @@ export function useSpreadsheetComments({
|
|
|
94
96
|
]);
|
|
95
97
|
}
|
|
96
98
|
setCommentText('');
|
|
97
|
-
|
|
99
|
+
setCommentModalCell(null);
|
|
98
100
|
},
|
|
99
|
-
[commentText,
|
|
101
|
+
[commentText, onAddCellComment]
|
|
100
102
|
);
|
|
101
103
|
|
|
102
104
|
// Toggle comment resolved status
|
|
103
105
|
const handleToggleCommentResolved = useCallback((commentId: string) => {
|
|
104
|
-
|
|
106
|
+
setCellCommentsInternal((prev) =>
|
|
105
107
|
prev.map((c) => (c.id === commentId ? { ...c, resolved: !c.resolved } : c))
|
|
106
108
|
);
|
|
107
109
|
}, []);
|
|
108
110
|
|
|
109
111
|
return {
|
|
110
112
|
// Comments data
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
cellComments,
|
|
114
|
+
getCellComments,
|
|
115
|
+
getCellUnresolvedCommentCount,
|
|
114
116
|
|
|
115
117
|
// Add comment modal state
|
|
116
|
-
|
|
117
|
-
|
|
118
|
+
commentModalCell,
|
|
119
|
+
setCommentModalCell,
|
|
118
120
|
commentText,
|
|
119
121
|
setCommentText,
|
|
120
122
|
|
|
121
123
|
// View comments modal state
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
viewCommentsCell,
|
|
125
|
+
setViewCommentsCell,
|
|
124
126
|
|
|
125
127
|
// Actions
|
|
126
|
-
|
|
128
|
+
handleAddCellComment,
|
|
127
129
|
handleToggleCommentResolved,
|
|
128
130
|
|
|
129
131
|
// Utility
|
|
130
|
-
|
|
132
|
+
cellHasComments,
|
|
131
133
|
};
|
|
132
134
|
}
|
|
@@ -38,6 +38,8 @@ export interface UseSpreadsheetFilteringReturn<T> {
|
|
|
38
38
|
setActiveFilterColumn: (columnId: string | null) => void;
|
|
39
39
|
handleFilterChange: (columnId: string, filter: SpreadsheetColumnFilter | undefined) => void;
|
|
40
40
|
handleSort: (columnId: string) => void;
|
|
41
|
+
setSortConfig: (config: SpreadsheetSortConfig | null) => void;
|
|
42
|
+
clearSort: () => void;
|
|
41
43
|
clearAllFilters: () => void;
|
|
42
44
|
hasActiveFilters: boolean;
|
|
43
45
|
}
|
|
@@ -368,10 +370,21 @@ export function useSpreadsheetFiltering<T extends Record<string, any>>({
|
|
|
368
370
|
|
|
369
371
|
const handleSort = useCallback(
|
|
370
372
|
(columnId: string) => {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
373
|
+
let newSortConfig: SpreadsheetSortConfig | null;
|
|
374
|
+
|
|
375
|
+
if (sortConfig?.columnId === columnId) {
|
|
376
|
+
// Cycle through: asc → desc → null (clear)
|
|
377
|
+
if (sortConfig.direction === 'asc') {
|
|
378
|
+
newSortConfig = { columnId, direction: 'desc' };
|
|
379
|
+
} else {
|
|
380
|
+
// Currently desc, clear the sort
|
|
381
|
+
newSortConfig = null;
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
// Different column, start with asc
|
|
385
|
+
newSortConfig = { columnId, direction: 'asc' };
|
|
386
|
+
}
|
|
387
|
+
|
|
375
388
|
// Only update internal state if not controlled
|
|
376
389
|
if (controlledSortConfig === undefined) {
|
|
377
390
|
setInternalSortConfig(newSortConfig);
|
|
@@ -381,6 +394,25 @@ export function useSpreadsheetFiltering<T extends Record<string, any>>({
|
|
|
381
394
|
[sortConfig, onSortChange, controlledSortConfig]
|
|
382
395
|
);
|
|
383
396
|
|
|
397
|
+
const clearSort = useCallback(() => {
|
|
398
|
+
// Only update internal state if not controlled
|
|
399
|
+
if (controlledSortConfig === undefined) {
|
|
400
|
+
setInternalSortConfig(null);
|
|
401
|
+
}
|
|
402
|
+
onSortChange?.(null);
|
|
403
|
+
}, [onSortChange, controlledSortConfig]);
|
|
404
|
+
|
|
405
|
+
const setSortConfig = useCallback(
|
|
406
|
+
(config: SpreadsheetSortConfig | null) => {
|
|
407
|
+
// Only update internal state if not controlled
|
|
408
|
+
if (controlledSortConfig === undefined) {
|
|
409
|
+
setInternalSortConfig(config);
|
|
410
|
+
}
|
|
411
|
+
onSortChange?.(config);
|
|
412
|
+
},
|
|
413
|
+
[onSortChange, controlledSortConfig]
|
|
414
|
+
);
|
|
415
|
+
|
|
384
416
|
const clearAllFilters = useCallback(() => {
|
|
385
417
|
// Only update internal state if not controlled
|
|
386
418
|
if (controlledFilters === undefined) {
|
|
@@ -399,6 +431,8 @@ export function useSpreadsheetFiltering<T extends Record<string, any>>({
|
|
|
399
431
|
setActiveFilterColumn,
|
|
400
432
|
handleFilterChange,
|
|
401
433
|
handleSort,
|
|
434
|
+
setSortConfig,
|
|
435
|
+
clearSort,
|
|
402
436
|
clearAllFilters,
|
|
403
437
|
hasActiveFilters,
|
|
404
438
|
};
|
|
@@ -40,7 +40,11 @@ export interface UseSpreadsheetHighlightingReturn {
|
|
|
40
40
|
// Cell highlights
|
|
41
41
|
cellHighlights: CellHighlight[];
|
|
42
42
|
getCellHighlight: (rowId: string | number, columnId: string) => string | undefined;
|
|
43
|
-
handleCellHighlightToggle: (
|
|
43
|
+
handleCellHighlightToggle: (
|
|
44
|
+
rowId: string | number,
|
|
45
|
+
columnId: string,
|
|
46
|
+
color?: string | null
|
|
47
|
+
) => void;
|
|
44
48
|
|
|
45
49
|
// Row highlights
|
|
46
50
|
rowHighlights: CellHighlight[];
|
|
@@ -57,6 +61,8 @@ export interface UseSpreadsheetHighlightingReturn {
|
|
|
57
61
|
setHighlightPickerRow: (rowId: string | number | null) => void;
|
|
58
62
|
highlightPickerColumn: string | null;
|
|
59
63
|
setHighlightPickerColumn: (columnId: string | null) => void;
|
|
64
|
+
highlightPickerCell: { rowId: string | number; columnId: string } | null;
|
|
65
|
+
setHighlightPickerCell: (cell: { rowId: string | number; columnId: string } | null) => void;
|
|
60
66
|
|
|
61
67
|
// Utility
|
|
62
68
|
clearAllHighlights: () => void;
|
|
@@ -81,6 +87,10 @@ export function useSpreadsheetHighlighting({
|
|
|
81
87
|
// Picker states
|
|
82
88
|
const [highlightPickerRow, setHighlightPickerRow] = useState<string | number | null>(null);
|
|
83
89
|
const [highlightPickerColumn, setHighlightPickerColumn] = useState<string | null>(null);
|
|
90
|
+
const [highlightPickerCell, setHighlightPickerCell] = useState<{
|
|
91
|
+
rowId: string | number;
|
|
92
|
+
columnId: string;
|
|
93
|
+
} | null>(null);
|
|
84
94
|
|
|
85
95
|
// Use external row highlights if provided, otherwise use internal
|
|
86
96
|
const rowHighlights = externalRowHighlights || rowHighlightsInternal;
|
|
@@ -95,14 +105,25 @@ export function useSpreadsheetHighlighting({
|
|
|
95
105
|
|
|
96
106
|
// Toggle cell highlight
|
|
97
107
|
const handleCellHighlightToggle = useCallback(
|
|
98
|
-
(rowId: string | number, columnId: string, color: string = '#fef08a') => {
|
|
108
|
+
(rowId: string | number, columnId: string, color: string | null = '#fef08a') => {
|
|
99
109
|
setCellHighlights((prev) => {
|
|
100
110
|
const existing = prev.find((h) => h.rowId === rowId && h.columnId === columnId);
|
|
101
111
|
if (existing) {
|
|
102
|
-
|
|
112
|
+
if (color === null) {
|
|
113
|
+
// Remove highlight
|
|
114
|
+
return prev.filter((h) => !(h.rowId === rowId && h.columnId === columnId));
|
|
115
|
+
}
|
|
116
|
+
// Update color
|
|
117
|
+
return prev.map((h) =>
|
|
118
|
+
h.rowId === rowId && h.columnId === columnId ? { ...h, color } : h
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
if (color) {
|
|
122
|
+
return [...prev, { rowId, columnId, color }];
|
|
103
123
|
}
|
|
104
|
-
return
|
|
124
|
+
return prev;
|
|
105
125
|
});
|
|
126
|
+
setHighlightPickerCell(null);
|
|
106
127
|
},
|
|
107
128
|
[]
|
|
108
129
|
);
|
|
@@ -194,6 +215,8 @@ export function useSpreadsheetHighlighting({
|
|
|
194
215
|
setHighlightPickerRow,
|
|
195
216
|
highlightPickerColumn,
|
|
196
217
|
setHighlightPickerColumn,
|
|
218
|
+
highlightPickerCell,
|
|
219
|
+
setHighlightPickerCell,
|
|
197
220
|
|
|
198
221
|
// Utility
|
|
199
222
|
clearAllHighlights,
|
package/src/types.ts
CHANGED
|
@@ -312,8 +312,8 @@ export interface SpreadsheetProps<T = any> {
|
|
|
312
312
|
onRowDoubleClick?: (row: T, rowIndex: number) => void;
|
|
313
313
|
/** Callback when a row is cloned/duplicated */
|
|
314
314
|
onRowClone?: (row: T, rowId: string | number) => void;
|
|
315
|
-
/** Callback when a
|
|
316
|
-
|
|
315
|
+
/** Callback when a cell comment is added */
|
|
316
|
+
onAddCellComment?: (rowId: string | number, columnId: string, comment: string) => void;
|
|
317
317
|
/** Callback when row highlight is toggled */
|
|
318
318
|
onRowHighlight?: (rowId: string | number, color: string | null) => void;
|
|
319
319
|
/** Whether to show the toolbar */
|
|
@@ -350,8 +350,8 @@ export interface SpreadsheetProps<T = any> {
|
|
|
350
350
|
emptyMessage?: string;
|
|
351
351
|
/** Row highlights (externally controlled) */
|
|
352
352
|
rowHighlights?: CellHighlight[];
|
|
353
|
-
/**
|
|
354
|
-
|
|
353
|
+
/** Cell comments (externally controlled) */
|
|
354
|
+
cellComments?: CellComment[];
|
|
355
355
|
/** Custom row actions to display in the index column */
|
|
356
356
|
rowActions?: RowAction<T>[];
|
|
357
357
|
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @xcelsior/ui-spreadsheets@1.0.3 build /home/circleci/repo/packages/ui/ui-spreadsheets
|
|
3
|
-
> tsup && tsc --noEmit
|
|
4
|
-
|
|
5
|
-
[34mCLI[39m Building entry: src/index.ts
|
|
6
|
-
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
|
-
[34mCLI[39m tsup v8.5.1
|
|
8
|
-
[34mCLI[39m Using tsup config: /home/circleci/repo/packages/ui/ui-spreadsheets/tsup.config.ts
|
|
9
|
-
[34mCLI[39m Target: es2020
|
|
10
|
-
[34mCLI[39m Cleaning output folder
|
|
11
|
-
[34mCJS[39m Build start
|
|
12
|
-
[34mESM[39m Build start
|
|
13
|
-
[32mCJS[39m [1mdist/index.js [22m[32m151.12 KB[39m
|
|
14
|
-
[32mCJS[39m [1mdist/index.js.map [22m[32m2.63 MB[39m
|
|
15
|
-
[32mCJS[39m ⚡️ Build success in 576ms
|
|
16
|
-
[32mESM[39m [1mdist/index.mjs [22m[32m140.30 KB[39m
|
|
17
|
-
[32mESM[39m [1mdist/index.mjs.map [22m[32m2.63 MB[39m
|
|
18
|
-
[32mESM[39m ⚡️ Build success in 577ms
|
|
19
|
-
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in 8502ms
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m22.18 KB[39m
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[32m22.18 KB[39m
|