@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.
@@ -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
- useSpreadsheetHighlighting,
20
+ useSpreadsheetPinning,
21
+ ROW_INDEX_COLUMN_WIDTH,
20
22
  ROW_INDEX_COLUMN_ID,
21
- } from '../hooks/useSpreadsheetHighlighting';
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
- compactMode = false,
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: effectiveAutoSave,
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 effectiveShowRowIndex = spreadsheetSettings.showRowIndex !== false;
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={effectiveAutoSave}
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
- {effectiveShowRowIndex && (
672
- <RowIndexColumnHeader
673
- enableHighlighting={enableHighlighting}
674
- highlightColor={rowIndexHighlightColor}
675
- isPinned={isRowIndexPinned}
676
- onHighlightClick={handleRowIndexHighlightClick}
677
- onPinClick={handleToggleRowIndexPin}
678
- hasColumnGroups={true}
679
- compactMode={effectiveCompactMode}
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
- {effectiveShowRowIndex && !columnGroups && (
717
+ {!columnGroups && (
732
718
  <RowIndexColumnHeader
733
719
  enableHighlighting={enableHighlighting}
734
720
  highlightColor={rowIndexHighlightColor}
735
721
  isPinned={isRowIndexPinned}
736
722
  onHighlightClick={handleRowIndexHighlightClick}
737
- onPinClick={handleToggleRowIndexPin}
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
- {effectiveShowRowIndex && (
839
- <td
840
- onClick={(e) => handleRowSelect(rowId, e)}
841
- className={cn(
842
- 'border border-gray-200 text-center font-semibold cursor-pointer group',
843
- effectiveCompactMode ? 'text-[10px] px-1 py-px' : 'text-xs px-2 py-1',
844
- isRowIndexPinned ? 'z-20' : 'z-0',
845
- isRowSelected && 'bg-blue-100',
846
- !isRowSelected && rowHighlight && '',
847
- isRowHovered &&
848
- !isRowSelected &&
849
- !rowHighlight &&
850
- 'bg-gray-50'
851
- )}
852
- style={{
853
- backgroundColor:
854
- rowHighlight?.color ||
855
- (isRowSelected
856
- ? '#dbeafe'
857
- : isRowHovered
858
- ? '#f9fafb'
859
- : rowIndexHighlightColor ||
860
- 'white'),
861
- minWidth: `${ROW_INDEX_COLUMN_WIDTH}px`,
862
- width: `${ROW_INDEX_COLUMN_WIDTH}px`,
863
- ...(isRowIndexPinned && {
864
- position: 'sticky' as const,
865
- left: 0,
866
- }),
867
- }}
868
- >
869
- <div className="relative flex items-center justify-center">
870
- {/* Row number - always centered */}
871
- <span>{displayIndex}</span>
872
-
873
- {/* Action buttons - absolute positioned to not affect centering */}
874
- <div className="absolute right-0 flex items-center gap-0.5">
875
- {/* Clone/Duplicate Row - hover only */}
876
- {onRowClone && (
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
- handleRowClone(row, rowId);
918
+ setViewCommentsCell({
919
+ rowId,
920
+ columnId:
921
+ ROW_INDEX_COLUMN_ID,
922
+ });
882
923
  }}
883
- className="opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-gray-100 hover:bg-gray-200 rounded"
884
- title="Duplicate row"
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
- <HiDuplicate className="h-2.5 w-2.5 text-gray-500" />
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
- handleRowDelete(row, rowId);
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-red-100 rounded"
899
- title="Delete row"
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
- <HiTrash className="h-2.5 w-2.5 text-gray-500 hover:text-red-500" />
959
+ <FaRegComment className="h-2.5 w-2.5 text-gray-500" />
902
960
  </button>
903
- )}
904
-
905
- {/* Highlight Row - hover only */}
906
- {enableHighlighting && (
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
- setHighlightPickerRow(
912
- rowId
913
- );
976
+ action.onClick(row, rowId);
914
977
  }}
915
- className="opacity-0 group-hover:opacity-100 transition-opacity p-0.5 bg-gray-100 hover:bg-gray-200 rounded"
916
- title="Highlight row"
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
- <AiFillHighlight
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
- </td>
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 border-gray-300 rounded text-xs focus:outline-none focus:ring-1 focus:ring-blue-500',
140
- compactMode ? 'px-1 py-0.5' : 'px-2 py-1'
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 border-gray-300 rounded text-xs focus:outline-none focus:ring-1 focus:ring-blue-500 bg-blue-50',
175
- compactMode ? 'px-1 py-0.5' : 'px-2 py-1'
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 transition-colors select-none',
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;