@xcelsior/ui-spreadsheets 1.0.17 → 1.1.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/dist/index.d.mts +28 -20
- package/dist/index.d.ts +28 -20
- package/dist/index.js +62 -125
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +62 -125
- package/dist/index.mjs.map +1 -1
- package/dist/styles/globals.css +14 -49
- package/dist/styles/globals.css.map +1 -1
- package/package.json +1 -1
- package/src/components/Spreadsheet.stories.tsx +0 -26
- package/src/components/Spreadsheet.tsx +190 -211
- package/src/components/SpreadsheetCell.tsx +7 -7
- package/src/components/SpreadsheetSettingsModal.tsx +5 -112
- package/src/hooks/index.ts +0 -2
- package/src/hooks/useSpreadsheetHighlighting.ts +0 -3
- package/src/hooks/useSpreadsheetPinning.ts +31 -4
- package/src/types.ts +25 -11
|
@@ -15,11 +15,12 @@ import { AddCommentModal, ViewCommentsModal } from './CommentModals';
|
|
|
15
15
|
import { KeyboardShortcutsModal } from './KeyboardShortcutsModal';
|
|
16
16
|
import { Pagination } from '@xcelsior/design-system';
|
|
17
17
|
import { useSpreadsheetFiltering } from '../hooks/useSpreadsheetFiltering';
|
|
18
|
+
import { useSpreadsheetHighlighting } from '../hooks/useSpreadsheetHighlighting';
|
|
18
19
|
import {
|
|
19
|
-
|
|
20
|
+
useSpreadsheetPinning,
|
|
21
|
+
ROW_INDEX_COLUMN_WIDTH,
|
|
20
22
|
ROW_INDEX_COLUMN_ID,
|
|
21
|
-
} from '../hooks/
|
|
22
|
-
import { useSpreadsheetPinning, ROW_INDEX_COLUMN_WIDTH } from '../hooks/useSpreadsheetPinning';
|
|
23
|
+
} from '../hooks/useSpreadsheetPinning';
|
|
23
24
|
import { useSpreadsheetComments } from '../hooks/useSpreadsheetComments';
|
|
24
25
|
import { useSpreadsheetUndoRedo } from '../hooks/useSpreadsheetUndoRedo';
|
|
25
26
|
import { useSpreadsheetKeyboardShortcuts } from '../hooks/useSpreadsheetKeyboardShortcuts';
|
|
@@ -89,18 +90,15 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
89
90
|
onRowHighlight,
|
|
90
91
|
showToolbar = true,
|
|
91
92
|
showPagination = true,
|
|
92
|
-
showRowIndex = true,
|
|
93
93
|
enableRowSelection = true,
|
|
94
94
|
enableCellEditing = true,
|
|
95
95
|
enableComments = true,
|
|
96
96
|
enableHighlighting = true,
|
|
97
97
|
enableUndoRedo = true,
|
|
98
|
-
defaultPageSize = 25,
|
|
99
98
|
pageSizeOptions = [25, 50, 100, 200],
|
|
100
|
-
defaultZoom = 100,
|
|
101
|
-
autoSave = true,
|
|
102
99
|
onSave,
|
|
103
|
-
|
|
100
|
+
settings: initialSettings,
|
|
101
|
+
onSettingsChange,
|
|
104
102
|
isLoading = false,
|
|
105
103
|
className,
|
|
106
104
|
emptyMessage = 'No data available',
|
|
@@ -114,11 +112,20 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
114
112
|
currentPage: controlledCurrentPage,
|
|
115
113
|
pageSize: controlledPageSize,
|
|
116
114
|
onPageChange,
|
|
117
|
-
sortConfig: controlledSortConfig,
|
|
118
115
|
filters: controlledFilters,
|
|
119
116
|
}: SpreadsheetProps<T>) {
|
|
120
117
|
// ==================== HOOKS ====================
|
|
121
118
|
|
|
119
|
+
// Settings state - declare early so it can be used in hooks
|
|
120
|
+
const [spreadsheetSettings, setSpreadsheetSettings] = useState<SpreadsheetSettings>({
|
|
121
|
+
defaultPinnedColumns: initialSettings?.defaultPinnedColumns ?? [],
|
|
122
|
+
defaultSort: initialSettings?.defaultSort ?? null,
|
|
123
|
+
defaultPageSize: initialSettings?.defaultPageSize ?? 25,
|
|
124
|
+
defaultZoom: initialSettings?.defaultZoom ?? 100,
|
|
125
|
+
autoSave: initialSettings?.autoSave ?? true,
|
|
126
|
+
compactView: initialSettings?.compactView ?? false,
|
|
127
|
+
});
|
|
128
|
+
|
|
122
129
|
// Filtering and sorting hook
|
|
123
130
|
const {
|
|
124
131
|
filters,
|
|
@@ -138,7 +145,7 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
138
145
|
onSortChange,
|
|
139
146
|
serverSide,
|
|
140
147
|
controlledFilters,
|
|
141
|
-
controlledSortConfig,
|
|
148
|
+
controlledSortConfig: spreadsheetSettings?.defaultSort,
|
|
142
149
|
});
|
|
143
150
|
|
|
144
151
|
// Highlighting hook
|
|
@@ -167,15 +174,14 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
167
174
|
collapsedGroups,
|
|
168
175
|
visibleColumns,
|
|
169
176
|
handleTogglePin,
|
|
170
|
-
handleToggleRowIndexPin,
|
|
171
177
|
handleToggleGroupCollapse,
|
|
178
|
+
setPinnedColumnsFromIds,
|
|
172
179
|
getColumnLeftOffset,
|
|
173
180
|
isColumnPinned,
|
|
174
181
|
getColumnPinSide,
|
|
175
182
|
} = useSpreadsheetPinning({
|
|
176
183
|
columns,
|
|
177
184
|
columnGroups,
|
|
178
|
-
showRowIndex,
|
|
179
185
|
});
|
|
180
186
|
|
|
181
187
|
// Comments hook
|
|
@@ -199,22 +205,6 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
199
205
|
// Modal state
|
|
200
206
|
const [showSettingsModal, setShowSettingsModal] = useState(false);
|
|
201
207
|
|
|
202
|
-
// Settings state - declare early so it can be used in hooks
|
|
203
|
-
const [spreadsheetSettings, setSpreadsheetSettings] = useState<SpreadsheetSettings>({
|
|
204
|
-
defaultPinnedColumns: [],
|
|
205
|
-
defaultSort: null,
|
|
206
|
-
defaultPageSize,
|
|
207
|
-
defaultZoom,
|
|
208
|
-
autoSave,
|
|
209
|
-
compactView: compactMode,
|
|
210
|
-
showRowIndex: showRowIndex,
|
|
211
|
-
pinRowIndex: false,
|
|
212
|
-
rowIndexHighlightColor: undefined,
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
// Effective values from settings
|
|
216
|
-
const effectiveAutoSave = spreadsheetSettings.autoSave ?? autoSave;
|
|
217
|
-
|
|
218
208
|
// Undo/Redo hook
|
|
219
209
|
const {
|
|
220
210
|
canUndo,
|
|
@@ -230,7 +220,7 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
230
220
|
markAsChanged,
|
|
231
221
|
} = useSpreadsheetUndoRedo<SpreadsheetUndoEntry>({
|
|
232
222
|
enabled: enableUndoRedo,
|
|
233
|
-
autoSave:
|
|
223
|
+
autoSave: spreadsheetSettings.autoSave,
|
|
234
224
|
onSave,
|
|
235
225
|
});
|
|
236
226
|
|
|
@@ -241,8 +231,8 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
241
231
|
|
|
242
232
|
// Pagination state (supports both controlled and uncontrolled modes)
|
|
243
233
|
const [internalCurrentPage, setInternalCurrentPage] = useState(1);
|
|
244
|
-
const [internalPageSize, setInternalPageSize] = useState(defaultPageSize);
|
|
245
|
-
const [zoom, setZoom] = useState(defaultZoom);
|
|
234
|
+
const [internalPageSize, setInternalPageSize] = useState(spreadsheetSettings.defaultPageSize);
|
|
235
|
+
const [zoom, setZoom] = useState(spreadsheetSettings.defaultZoom);
|
|
246
236
|
|
|
247
237
|
// Use controlled state if provided, otherwise use internal state
|
|
248
238
|
const currentPage = controlledCurrentPage ?? internalCurrentPage;
|
|
@@ -331,7 +321,6 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
331
321
|
// Cell selection hook (for multi-cell selection, copy/paste)
|
|
332
322
|
const {
|
|
333
323
|
focusedCell,
|
|
334
|
-
setFocusedCell,
|
|
335
324
|
editingCell,
|
|
336
325
|
setEditingCell,
|
|
337
326
|
handleCellMouseDown,
|
|
@@ -464,8 +453,7 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
464
453
|
|
|
465
454
|
// ==================== COMPUTED VALUES ====================
|
|
466
455
|
|
|
467
|
-
const
|
|
468
|
-
const effectiveCompactMode = spreadsheetSettings.compactView ?? compactMode;
|
|
456
|
+
const effectiveCompactMode = spreadsheetSettings.compactView ?? false;
|
|
469
457
|
const rowIndexHighlightColor = getColumnHighlight(ROW_INDEX_COLUMN_ID);
|
|
470
458
|
|
|
471
459
|
// Refs
|
|
@@ -633,7 +621,7 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
633
621
|
selectedRowCount={selectedRows.size}
|
|
634
622
|
hasUnsavedChanges={hasUnsavedChanges}
|
|
635
623
|
saveStatus={saveStatus}
|
|
636
|
-
autoSave={
|
|
624
|
+
autoSave={spreadsheetSettings.autoSave}
|
|
637
625
|
hasActiveFilters={hasActiveFilters}
|
|
638
626
|
onClearFilters={clearAllFilters}
|
|
639
627
|
onZoomIn={() => setZoom((z) => Math.min(z + 10, 200))}
|
|
@@ -668,17 +656,15 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
668
656
|
{columnGroups && (
|
|
669
657
|
<tr>
|
|
670
658
|
{/* Row index column header (rowSpan=2 for groups) */}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
/>
|
|
681
|
-
)}
|
|
659
|
+
<RowIndexColumnHeader
|
|
660
|
+
enableHighlighting={enableHighlighting}
|
|
661
|
+
highlightColor={rowIndexHighlightColor}
|
|
662
|
+
isPinned={isRowIndexPinned}
|
|
663
|
+
onHighlightClick={handleRowIndexHighlightClick}
|
|
664
|
+
onPinClick={() => handleTogglePin(ROW_INDEX_COLUMN_ID)}
|
|
665
|
+
hasColumnGroups={true}
|
|
666
|
+
compactMode={effectiveCompactMode}
|
|
667
|
+
/>
|
|
682
668
|
{columnGroups.map((group) => {
|
|
683
669
|
const groupColumns = (columns || []).filter((c) =>
|
|
684
670
|
group.columns.includes(c.id)
|
|
@@ -728,13 +714,13 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
728
714
|
{/* Column Headers */}
|
|
729
715
|
<tr>
|
|
730
716
|
{/* Row index column header (when no groups) */}
|
|
731
|
-
{
|
|
717
|
+
{!columnGroups && (
|
|
732
718
|
<RowIndexColumnHeader
|
|
733
719
|
enableHighlighting={enableHighlighting}
|
|
734
720
|
highlightColor={rowIndexHighlightColor}
|
|
735
721
|
isPinned={isRowIndexPinned}
|
|
736
722
|
onHighlightClick={handleRowIndexHighlightClick}
|
|
737
|
-
onPinClick={
|
|
723
|
+
onPinClick={() => handleTogglePin(ROW_INDEX_COLUMN_ID)}
|
|
738
724
|
hasColumnGroups={false}
|
|
739
725
|
compactMode={effectiveCompactMode}
|
|
740
726
|
/>
|
|
@@ -792,9 +778,7 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
792
778
|
{isLoading ? (
|
|
793
779
|
<tr>
|
|
794
780
|
<td
|
|
795
|
-
colSpan={
|
|
796
|
-
visibleColumns.length + (effectiveShowRowIndex ? 1 : 0)
|
|
797
|
-
}
|
|
781
|
+
colSpan={visibleColumns.length + 1}
|
|
798
782
|
className="text-center py-8 text-gray-500"
|
|
799
783
|
>
|
|
800
784
|
<div className="flex items-center justify-center gap-2">
|
|
@@ -806,9 +790,7 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
806
790
|
) : paginatedData.length === 0 ? (
|
|
807
791
|
<tr>
|
|
808
792
|
<td
|
|
809
|
-
colSpan={
|
|
810
|
-
visibleColumns.length + (effectiveShowRowIndex ? 1 : 0)
|
|
811
|
-
}
|
|
793
|
+
colSpan={visibleColumns.length + 1}
|
|
812
794
|
className="text-center py-8 text-gray-500"
|
|
813
795
|
>
|
|
814
796
|
{emptyMessage}
|
|
@@ -832,187 +814,180 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
832
814
|
onRowClick?.(row, rowIndex);
|
|
833
815
|
}}
|
|
834
816
|
onDoubleClick={() => onRowDoubleClick?.(row, rowIndex)}
|
|
835
|
-
className="transition-colors"
|
|
836
817
|
>
|
|
837
818
|
{/* Row Index Column */}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
819
|
+
<td
|
|
820
|
+
onClick={(e) => handleRowSelect(rowId, e)}
|
|
821
|
+
className={cn(
|
|
822
|
+
'border border-gray-200 text-center font-semibold cursor-pointer group',
|
|
823
|
+
effectiveCompactMode
|
|
824
|
+
? 'text-[10px] px-1 py-px'
|
|
825
|
+
: 'text-xs px-2 py-1',
|
|
826
|
+
isRowIndexPinned ? 'z-20' : 'z-0',
|
|
827
|
+
isRowSelected && 'bg-blue-100',
|
|
828
|
+
!isRowSelected && rowHighlight && '',
|
|
829
|
+
isRowHovered &&
|
|
830
|
+
!isRowSelected &&
|
|
831
|
+
!rowHighlight &&
|
|
832
|
+
'bg-gray-50'
|
|
833
|
+
)}
|
|
834
|
+
style={{
|
|
835
|
+
backgroundColor:
|
|
836
|
+
rowHighlight?.color ||
|
|
837
|
+
(isRowSelected
|
|
838
|
+
? '#dbeafe'
|
|
839
|
+
: isRowHovered
|
|
840
|
+
? '#f9fafb'
|
|
841
|
+
: rowIndexHighlightColor || 'white'),
|
|
842
|
+
minWidth: `${ROW_INDEX_COLUMN_WIDTH}px`,
|
|
843
|
+
width: `${ROW_INDEX_COLUMN_WIDTH}px`,
|
|
844
|
+
...(isRowIndexPinned && {
|
|
845
|
+
position: 'sticky' as const,
|
|
846
|
+
left: 0,
|
|
847
|
+
}),
|
|
848
|
+
}}
|
|
849
|
+
>
|
|
850
|
+
<div className="relative flex items-center justify-center">
|
|
851
|
+
{/* Row number - always centered */}
|
|
852
|
+
<span>{displayIndex}</span>
|
|
853
|
+
|
|
854
|
+
{/* Action buttons - absolute positioned to the right */}
|
|
855
|
+
<div className="absolute right-0 flex items-center gap-0.5">
|
|
856
|
+
{/* Clone/Duplicate Row */}
|
|
857
|
+
{onRowClone && (
|
|
858
|
+
<button
|
|
859
|
+
type="button"
|
|
860
|
+
onClick={(e) => {
|
|
861
|
+
e.stopPropagation();
|
|
862
|
+
handleRowClone(row, rowId);
|
|
863
|
+
}}
|
|
864
|
+
className="opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-gray-100 hover:bg-gray-200 rounded"
|
|
865
|
+
title="Duplicate row"
|
|
866
|
+
>
|
|
867
|
+
<HiDuplicate className="h-2.5 w-2.5 text-gray-500" />
|
|
868
|
+
</button>
|
|
869
|
+
)}
|
|
870
|
+
|
|
871
|
+
{/* Delete Row */}
|
|
872
|
+
{onRowDelete && (
|
|
873
|
+
<button
|
|
874
|
+
type="button"
|
|
875
|
+
onClick={(e) => {
|
|
876
|
+
e.stopPropagation();
|
|
877
|
+
handleRowDelete(row, rowId);
|
|
878
|
+
}}
|
|
879
|
+
className="opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-gray-100 hover:bg-red-100 rounded"
|
|
880
|
+
title="Delete row"
|
|
881
|
+
>
|
|
882
|
+
<HiTrash className="h-2.5 w-2.5 text-gray-500 hover:text-red-500" />
|
|
883
|
+
</button>
|
|
884
|
+
)}
|
|
885
|
+
|
|
886
|
+
{/* Highlight Row */}
|
|
887
|
+
{enableHighlighting && (
|
|
888
|
+
<button
|
|
889
|
+
type="button"
|
|
890
|
+
onClick={(e) => {
|
|
891
|
+
e.stopPropagation();
|
|
892
|
+
setHighlightPickerRow(rowId);
|
|
893
|
+
}}
|
|
894
|
+
className="opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-gray-100 hover:bg-gray-200 rounded"
|
|
895
|
+
title="Highlight row"
|
|
896
|
+
>
|
|
897
|
+
<AiFillHighlight
|
|
898
|
+
className={cn(
|
|
899
|
+
'h-2.5 w-2.5',
|
|
900
|
+
rowHighlight
|
|
901
|
+
? 'text-amber-500'
|
|
902
|
+
: 'text-gray-500'
|
|
903
|
+
)}
|
|
904
|
+
/>
|
|
905
|
+
</button>
|
|
906
|
+
)}
|
|
907
|
+
|
|
908
|
+
{/* Comment button */}
|
|
909
|
+
{enableComments &&
|
|
910
|
+
(cellHasComments(
|
|
911
|
+
rowId,
|
|
912
|
+
ROW_INDEX_COLUMN_ID
|
|
913
|
+
) ? (
|
|
877
914
|
<button
|
|
878
915
|
type="button"
|
|
879
916
|
onClick={(e) => {
|
|
880
917
|
e.stopPropagation();
|
|
881
|
-
|
|
918
|
+
setViewCommentsCell({
|
|
919
|
+
rowId,
|
|
920
|
+
columnId:
|
|
921
|
+
ROW_INDEX_COLUMN_ID,
|
|
922
|
+
});
|
|
882
923
|
}}
|
|
883
|
-
className="
|
|
884
|
-
title=
|
|
924
|
+
className="p-0.5 bg-amber-100 hover:bg-amber-200 rounded transition-colors flex items-center gap-0.5"
|
|
925
|
+
title={`${getCellUnresolvedCommentCount(rowId, ROW_INDEX_COLUMN_ID)} comment(s) - click to view`}
|
|
885
926
|
>
|
|
886
|
-
<
|
|
927
|
+
<FaComment className="h-2.5 w-2.5 text-amber-500" />
|
|
928
|
+
{getCellUnresolvedCommentCount(
|
|
929
|
+
rowId,
|
|
930
|
+
ROW_INDEX_COLUMN_ID
|
|
931
|
+
) > 0 && (
|
|
932
|
+
<span className="text-[9px] font-medium text-amber-600">
|
|
933
|
+
{getCellUnresolvedCommentCount(
|
|
934
|
+
rowId,
|
|
935
|
+
ROW_INDEX_COLUMN_ID
|
|
936
|
+
) > 99
|
|
937
|
+
? '99+'
|
|
938
|
+
: getCellUnresolvedCommentCount(
|
|
939
|
+
rowId,
|
|
940
|
+
ROW_INDEX_COLUMN_ID
|
|
941
|
+
)}
|
|
942
|
+
</span>
|
|
943
|
+
)}
|
|
887
944
|
</button>
|
|
888
|
-
)
|
|
889
|
-
|
|
890
|
-
{/* Delete Row - hover only */}
|
|
891
|
-
{onRowDelete && (
|
|
945
|
+
) : (
|
|
892
946
|
<button
|
|
893
947
|
type="button"
|
|
894
948
|
onClick={(e) => {
|
|
895
949
|
e.stopPropagation();
|
|
896
|
-
|
|
950
|
+
setCommentModalCell({
|
|
951
|
+
rowId,
|
|
952
|
+
columnId:
|
|
953
|
+
ROW_INDEX_COLUMN_ID,
|
|
954
|
+
});
|
|
897
955
|
}}
|
|
898
|
-
className="opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-gray-100 hover:bg-
|
|
899
|
-
title="
|
|
956
|
+
className="opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-gray-100 hover:bg-gray-200 rounded"
|
|
957
|
+
title="Add comment"
|
|
900
958
|
>
|
|
901
|
-
<
|
|
959
|
+
<FaRegComment className="h-2.5 w-2.5 text-gray-500" />
|
|
902
960
|
</button>
|
|
903
|
-
)}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
961
|
+
))}
|
|
962
|
+
|
|
963
|
+
{/* Custom Row Actions */}
|
|
964
|
+
{rowActions?.map((action) => {
|
|
965
|
+
if (
|
|
966
|
+
action.visible &&
|
|
967
|
+
!action.visible(row)
|
|
968
|
+
)
|
|
969
|
+
return null;
|
|
970
|
+
return (
|
|
907
971
|
<button
|
|
972
|
+
key={action.id}
|
|
908
973
|
type="button"
|
|
909
974
|
onClick={(e) => {
|
|
910
975
|
e.stopPropagation();
|
|
911
|
-
|
|
912
|
-
rowId
|
|
913
|
-
);
|
|
976
|
+
action.onClick(row, rowId);
|
|
914
977
|
}}
|
|
915
|
-
className=
|
|
916
|
-
|
|
978
|
+
className={cn(
|
|
979
|
+
'opacity-0 group-hover:opacity-100 transition-opacity p-0.5 hover:bg-gray-200 rounded',
|
|
980
|
+
action.className
|
|
981
|
+
)}
|
|
982
|
+
title={action.tooltip}
|
|
917
983
|
>
|
|
918
|
-
|
|
919
|
-
className={cn(
|
|
920
|
-
'h-2.5 w-2.5',
|
|
921
|
-
rowHighlight
|
|
922
|
-
? 'text-amber-500'
|
|
923
|
-
: 'text-gray-500'
|
|
924
|
-
)}
|
|
925
|
-
/>
|
|
984
|
+
{action.icon}
|
|
926
985
|
</button>
|
|
927
|
-
)
|
|
928
|
-
|
|
929
|
-
{/* Comment button - always visible when has comments, hover only when adding */}
|
|
930
|
-
{enableComments &&
|
|
931
|
-
(cellHasComments(
|
|
932
|
-
rowId,
|
|
933
|
-
ROW_INDEX_COLUMN_ID
|
|
934
|
-
) ? (
|
|
935
|
-
<button
|
|
936
|
-
type="button"
|
|
937
|
-
onClick={(e) => {
|
|
938
|
-
e.stopPropagation();
|
|
939
|
-
setViewCommentsCell({
|
|
940
|
-
rowId,
|
|
941
|
-
columnId:
|
|
942
|
-
ROW_INDEX_COLUMN_ID,
|
|
943
|
-
});
|
|
944
|
-
}}
|
|
945
|
-
className="p-0.5 bg-amber-100 hover:bg-amber-200 rounded transition-colors flex items-center gap-0.5"
|
|
946
|
-
title={`${getCellUnresolvedCommentCount(rowId, ROW_INDEX_COLUMN_ID)} comment(s) - click to view`}
|
|
947
|
-
>
|
|
948
|
-
<FaComment className="h-2.5 w-2.5 text-amber-500" />
|
|
949
|
-
{getCellUnresolvedCommentCount(
|
|
950
|
-
rowId,
|
|
951
|
-
ROW_INDEX_COLUMN_ID
|
|
952
|
-
) > 0 && (
|
|
953
|
-
<span className="text-[9px] font-medium text-amber-600">
|
|
954
|
-
{getCellUnresolvedCommentCount(
|
|
955
|
-
rowId,
|
|
956
|
-
ROW_INDEX_COLUMN_ID
|
|
957
|
-
) > 99
|
|
958
|
-
? '99+'
|
|
959
|
-
: getCellUnresolvedCommentCount(
|
|
960
|
-
rowId,
|
|
961
|
-
ROW_INDEX_COLUMN_ID
|
|
962
|
-
)}
|
|
963
|
-
</span>
|
|
964
|
-
)}
|
|
965
|
-
</button>
|
|
966
|
-
) : (
|
|
967
|
-
<button
|
|
968
|
-
type="button"
|
|
969
|
-
onClick={(e) => {
|
|
970
|
-
e.stopPropagation();
|
|
971
|
-
setCommentModalCell({
|
|
972
|
-
rowId,
|
|
973
|
-
columnId:
|
|
974
|
-
ROW_INDEX_COLUMN_ID,
|
|
975
|
-
});
|
|
976
|
-
}}
|
|
977
|
-
className="opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-gray-100 hover:bg-gray-200 rounded"
|
|
978
|
-
title="Add comment"
|
|
979
|
-
>
|
|
980
|
-
<FaRegComment className="h-2.5 w-2.5 text-gray-500" />
|
|
981
|
-
</button>
|
|
982
|
-
))}
|
|
983
|
-
|
|
984
|
-
{/* Custom Row Actions - hover only */}
|
|
985
|
-
{rowActions?.map((action) => {
|
|
986
|
-
if (
|
|
987
|
-
action.visible &&
|
|
988
|
-
!action.visible(row)
|
|
989
|
-
)
|
|
990
|
-
return null;
|
|
991
|
-
return (
|
|
992
|
-
<button
|
|
993
|
-
key={action.id}
|
|
994
|
-
type="button"
|
|
995
|
-
onClick={(e) => {
|
|
996
|
-
e.stopPropagation();
|
|
997
|
-
action.onClick(
|
|
998
|
-
row,
|
|
999
|
-
rowId
|
|
1000
|
-
);
|
|
1001
|
-
}}
|
|
1002
|
-
className={cn(
|
|
1003
|
-
'opacity-0 group-hover:opacity-100 transition-opacity p-0.5 hover:bg-gray-200 rounded',
|
|
1004
|
-
action.className
|
|
1005
|
-
)}
|
|
1006
|
-
title={action.tooltip}
|
|
1007
|
-
>
|
|
1008
|
-
{action.icon}
|
|
1009
|
-
</button>
|
|
1010
|
-
);
|
|
1011
|
-
})}
|
|
1012
|
-
</div>
|
|
986
|
+
);
|
|
987
|
+
})}
|
|
1013
988
|
</div>
|
|
1014
|
-
</
|
|
1015
|
-
|
|
989
|
+
</div>
|
|
990
|
+
</td>
|
|
1016
991
|
|
|
1017
992
|
{/* Data Cells */}
|
|
1018
993
|
{visibleColumns.map((column) => {
|
|
@@ -1249,6 +1224,10 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
1249
1224
|
) {
|
|
1250
1225
|
setSortConfig(newSettings.defaultSort);
|
|
1251
1226
|
}
|
|
1227
|
+
// Apply pinned columns changes from settings
|
|
1228
|
+
setPinnedColumnsFromIds(newSettings.defaultPinnedColumns);
|
|
1229
|
+
// Notify consumer of settings changes
|
|
1230
|
+
onSettingsChange?.(newSettings);
|
|
1252
1231
|
}}
|
|
1253
1232
|
columns={columns || []}
|
|
1254
1233
|
title="Spreadsheet Settings"
|
|
@@ -136,8 +136,8 @@ const SpreadsheetCell: React.FC<SpreadsheetCellProps> = ({
|
|
|
136
136
|
onKeyDown={handleKeyDown}
|
|
137
137
|
onBlur={() => onConfirm?.(localValue)}
|
|
138
138
|
className={cn(
|
|
139
|
-
'w-full border
|
|
140
|
-
compactMode ? '
|
|
139
|
+
'w-full border-0 bg-blue-50 focus:outline-none focus:ring-1 focus:ring-blue-500 rounded-sm',
|
|
140
|
+
compactMode ? 'text-[10px]' : 'text-xs'
|
|
141
141
|
)}
|
|
142
142
|
>
|
|
143
143
|
{column.options.map((option) => (
|
|
@@ -171,8 +171,8 @@ const SpreadsheetCell: React.FC<SpreadsheetCellProps> = ({
|
|
|
171
171
|
autoCapitalize="off"
|
|
172
172
|
spellCheck={false}
|
|
173
173
|
className={cn(
|
|
174
|
-
'w-full border
|
|
175
|
-
compactMode ? '
|
|
174
|
+
'w-full border-0 bg-blue-50 focus:outline-none focus:ring-1 focus:ring-blue-500 rounded-sm',
|
|
175
|
+
compactMode ? 'text-[10px]' : 'text-xs'
|
|
176
176
|
)}
|
|
177
177
|
/>
|
|
178
178
|
);
|
|
@@ -231,7 +231,7 @@ const SpreadsheetCell: React.FC<SpreadsheetCellProps> = ({
|
|
|
231
231
|
onKeyDown={handleCellKeyDown}
|
|
232
232
|
data-cell-id={`${rowId}-${column.id}`}
|
|
233
233
|
className={cn(
|
|
234
|
-
'border border-gray-200 group cursor-pointer
|
|
234
|
+
'border border-gray-200 group cursor-pointer select-none',
|
|
235
235
|
compactMode ? 'text-[10px]' : 'text-xs',
|
|
236
236
|
cellPadding,
|
|
237
237
|
column.align === 'right' && 'text-right',
|
|
@@ -257,8 +257,7 @@ const SpreadsheetCell: React.FC<SpreadsheetCellProps> = ({
|
|
|
257
257
|
<div
|
|
258
258
|
className={cn(
|
|
259
259
|
'flex-1 truncate',
|
|
260
|
-
isEditable &&
|
|
261
|
-
'cursor-text hover:bg-gray-50 px-0.5 rounded min-h-[18px] flex items-center bg-blue-50/50'
|
|
260
|
+
isEditable && 'cursor-text bg-blue-50/50 rounded'
|
|
262
261
|
)}
|
|
263
262
|
title={String(value ?? '')}
|
|
264
263
|
>
|
|
@@ -351,6 +350,7 @@ export const MemoizedSpreadsheetCell = memo(SpreadsheetCell, (prevProps, nextPro
|
|
|
351
350
|
if (prevProps.isPinned !== nextProps.isPinned) return false;
|
|
352
351
|
if (prevProps.leftOffset !== nextProps.leftOffset) return false;
|
|
353
352
|
if (prevProps.rightOffset !== nextProps.rightOffset) return false;
|
|
353
|
+
if (prevProps.compactMode !== nextProps.compactMode) return false;
|
|
354
354
|
|
|
355
355
|
// Check selection edge changes
|
|
356
356
|
const prevEdge = prevProps.selectionEdge;
|