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