@xcelsior/ui-spreadsheets 1.1.7 → 1.1.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcelsior/ui-spreadsheets",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -1511,3 +1511,59 @@ export const WithCheckboxColumns: Story = {
1511
1511
  );
1512
1512
  },
1513
1513
  };
1514
+
1515
+ // With Right-Pinned Columns
1516
+ export const WithRightPinnedColumns: Story = {
1517
+ render: () => {
1518
+ const [data, setData] = useState(sampleUsers);
1519
+
1520
+ const handleCellsEdit = (edits: CellEdit[]) => {
1521
+ setData((prev) =>
1522
+ prev.map((row) => {
1523
+ const editsForRow = edits.filter((e) => e.rowId === row.id);
1524
+ if (editsForRow.length > 0) {
1525
+ const updates = editsForRow.reduce(
1526
+ (acc, edit) => ({ ...acc, [edit.columnId]: edit.value }),
1527
+ {}
1528
+ );
1529
+ return { ...row, ...updates };
1530
+ }
1531
+ return row;
1532
+ })
1533
+ );
1534
+ };
1535
+
1536
+ return (
1537
+ <div className="p-4">
1538
+ <div className="mb-4 p-4 bg-purple-50 rounded-lg border border-purple-200">
1539
+ <h3 className="font-semibold text-purple-900 mb-2">Right-Pinned Columns Demo</h3>
1540
+ <p className="text-sm text-purple-700 mb-3">
1541
+ This example demonstrates columns pinned to the right side of the spreadsheet.
1542
+ The "Status" and "Active" columns are pinned to the right and will remain visible
1543
+ when scrolling horizontally.
1544
+ </p>
1545
+ <ul className="text-sm text-purple-700 space-y-1 ml-4 list-disc">
1546
+ <li><strong>ID column</strong> is pinned to the left</li>
1547
+ <li><strong>Status and Active columns</strong> are pinned to the right</li>
1548
+ <li>Scroll horizontally to see the pinned columns stay in place</li>
1549
+ </ul>
1550
+ </div>
1551
+
1552
+ <Spreadsheet
1553
+ data={data}
1554
+ columns={userColumns}
1555
+ getRowId={(row) => row.id}
1556
+ onCellsEdit={handleCellsEdit}
1557
+ settings={{
1558
+ defaultPinnedColumns: ['id'],
1559
+ defaultPinnedRightColumns: ['status', 'isActive'],
1560
+ }}
1561
+ showToolbar
1562
+ showPagination
1563
+ enableRowSelection
1564
+ enableCellEditing
1565
+ />
1566
+ </div>
1567
+ );
1568
+ },
1569
+ };
@@ -188,12 +188,14 @@ export function Spreadsheet<T extends Record<string, any>>({
188
188
  handleToggleGroupCollapse,
189
189
  setPinnedColumnsFromIds,
190
190
  getColumnLeftOffset,
191
+ getColumnRightOffset,
191
192
  isColumnPinned,
192
193
  getColumnPinSide,
193
194
  } = useSpreadsheetPinning({
194
195
  columns,
195
196
  columnGroups,
196
197
  defaultPinnedColumns: initialSettings?.defaultPinnedColumns,
198
+ defaultPinnedRightColumns: initialSettings?.defaultPinnedRightColumns,
197
199
  });
198
200
 
199
201
  // Comments hook
@@ -755,6 +757,9 @@ export function Spreadsheet<T extends Record<string, any>>({
755
757
  const isPinnedLeft =
756
758
  isColumnPinned(column.id) &&
757
759
  getColumnPinSide(column.id) === 'left';
760
+ const isPinnedRight =
761
+ isColumnPinned(column.id) &&
762
+ getColumnPinSide(column.id) === 'right';
758
763
  return (
759
764
  <SpreadsheetHeader
760
765
  key={column.id}
@@ -766,6 +771,9 @@ export function Spreadsheet<T extends Record<string, any>>({
766
771
  leftOffset={
767
772
  isPinnedLeft ? getColumnLeftOffset(column.id) : 0
768
773
  }
774
+ rightOffset={
775
+ isPinnedRight ? getColumnRightOffset(column.id) : 0
776
+ }
769
777
  highlightColor={getColumnHighlight(column.id)}
770
778
  compactMode={effectiveCompactMode}
771
779
  onClick={() => handleSort(column.id)}
@@ -1047,6 +1055,7 @@ export function Spreadsheet<T extends Record<string, any>>({
1047
1055
  isPinned={isColPinned}
1048
1056
  pinSide={colPinSide}
1049
1057
  leftOffset={getColumnLeftOffset(column.id)}
1058
+ rightOffset={getColumnRightOffset(column.id)}
1050
1059
  onClick={(e) =>
1051
1060
  handleCellClick(rowId, column.id, e)
1052
1061
  }
@@ -297,7 +297,7 @@ const SpreadsheetCell: React.FC<SpreadsheetCellProps> = ({
297
297
  {!isInSelection && !isFocused && (
298
298
  <div className="flex items-center gap-0.5 shrink-0">
299
299
  {/* Highlight button - hover only */}
300
- {onHighlight && (
300
+ {column.highlightable !== false && onHighlight && (
301
301
  <button
302
302
  type="button"
303
303
  onClick={(e) => {
@@ -317,7 +317,7 @@ const SpreadsheetCell: React.FC<SpreadsheetCellProps> = ({
317
317
  )}
318
318
 
319
319
  {/* Comment button - always visible when has comments, hover only when adding */}
320
- {hasComments && onViewComments ? (
320
+ {column.commentable !== false && hasComments && onViewComments ? (
321
321
  <button
322
322
  type="button"
323
323
  onClick={(e) => {
@@ -336,7 +336,7 @@ const SpreadsheetCell: React.FC<SpreadsheetCellProps> = ({
336
336
  </span>
337
337
  )}
338
338
  </button>
339
- ) : onAddComment ? (
339
+ ) : column.commentable !== false && onAddComment ? (
340
340
  <button
341
341
  type="button"
342
342
  onClick={(e) => {
@@ -93,7 +93,7 @@ export const SpreadsheetHeader: React.FC<
93
93
  {/* Action buttons using unified ColumnHeaderActions */}
94
94
  <ColumnHeaderActions
95
95
  enableFiltering={column.filterable}
96
- enableHighlighting={!!onHighlightClick}
96
+ enableHighlighting={column.highlightable !== false && !!onHighlightClick}
97
97
  enablePinning={column.pinnable !== false}
98
98
  hasActiveFilter={hasActiveFilter}
99
99
  hasActiveHighlight={!!highlightColor}
@@ -10,6 +10,7 @@ export interface UseSpreadsheetPinningOptions<T> {
10
10
  columnGroups?: SpreadsheetColumnGroup[];
11
11
  showRowIndex?: boolean;
12
12
  defaultPinnedColumns?: string[];
13
+ defaultPinnedRightColumns?: string[];
13
14
  }
14
15
 
15
16
  export interface UseSpreadsheetPinningReturn<T> {
@@ -30,6 +31,7 @@ export interface UseSpreadsheetPinningReturn<T> {
30
31
 
31
32
  // Offset calculations
32
33
  getColumnLeftOffset: (columnId: string) => number;
34
+ getColumnRightOffset: (columnId: string) => number;
33
35
 
34
36
  // Utility
35
37
  isColumnPinned: (columnId: string) => boolean;
@@ -41,6 +43,7 @@ export function useSpreadsheetPinning<T>({
41
43
  columnGroups,
42
44
  showRowIndex = true,
43
45
  defaultPinnedColumns = [],
46
+ defaultPinnedRightColumns = [],
44
47
  }: UseSpreadsheetPinningOptions<T>): UseSpreadsheetPinningReturn<T> {
45
48
  // Initialize pinned columns from defaults (including row index)
46
49
  const [pinnedColumns, setPinnedColumns] = useState<Map<string, 'left' | 'right'>>(() => {
@@ -48,6 +51,9 @@ export function useSpreadsheetPinning<T>({
48
51
  defaultPinnedColumns.forEach((col) => {
49
52
  map.set(col, 'left');
50
53
  });
54
+ defaultPinnedRightColumns.forEach((col) => {
55
+ map.set(col, 'right');
56
+ });
51
57
  return map;
52
58
  });
53
59
 
@@ -196,6 +202,29 @@ export function useSpreadsheetPinning<T>({
196
202
  [pinnedColumns]
197
203
  );
198
204
 
205
+ // Calculate column offset for right sticky positioning
206
+ const getColumnRightOffset = useCallback(
207
+ (columnId: string): number => {
208
+ // Get right-pinned columns
209
+ const pinnedRight = Array.from(pinnedColumns.entries())
210
+ .filter(([, side]) => side === 'right')
211
+ .map(([id]) => id);
212
+
213
+ const index = pinnedRight.indexOf(columnId);
214
+ if (index === -1) return 0;
215
+
216
+ // Calculate offset from the right edge
217
+ // Start from 0 and add widths of columns to the right of this one
218
+ let offset = 0;
219
+ for (let i = pinnedRight.length - 1; i > index; i--) {
220
+ const col = columns.find((c) => c.id === pinnedRight[i]);
221
+ offset += col?.minWidth || col?.width || 100;
222
+ }
223
+ return offset;
224
+ },
225
+ [pinnedColumns, columns]
226
+ );
227
+
199
228
  return {
200
229
  pinnedColumns,
201
230
  isRowIndexPinned,
@@ -205,6 +234,7 @@ export function useSpreadsheetPinning<T>({
205
234
  handleToggleGroupCollapse,
206
235
  setPinnedColumnsFromIds,
207
236
  getColumnLeftOffset,
237
+ getColumnRightOffset,
208
238
  isColumnPinned,
209
239
  getColumnPinSide,
210
240
  };
package/src/types.ts CHANGED
@@ -22,6 +22,10 @@ export interface SpreadsheetColumn<T = any> {
22
22
  editable?: boolean;
23
23
  /** Whether the column can be pinned */
24
24
  pinnable?: boolean;
25
+ /** Whether the column can be highlighted (defaults to true) */
26
+ highlightable?: boolean;
27
+ /** Whether the column can have comments (defaults to true) */
28
+ commentable?: boolean;
25
29
  /** Type of data in the column (for filtering/formatting) */
26
30
  type?: 'text' | 'number' | 'date' | 'select' | 'boolean' | 'checkbox';
27
31
  /** Options for select type columns */
@@ -385,8 +389,10 @@ export interface SpreadsheetProps<T = any> {
385
389
  onSave?: () => void | Promise<void>;
386
390
  /** Initial settings (optional). All settings can be changed by user in the settings modal. */
387
391
  settings?: {
388
- /** Default pinned column IDs */
392
+ /** Default pinned column IDs (pinned to left) */
389
393
  defaultPinnedColumns?: string[];
394
+ /** Default pinned column IDs (pinned to right) */
395
+ defaultPinnedRightColumns?: string[];
390
396
  /** Default sort configuration */
391
397
  defaultSort?: SpreadsheetSortConfig | null;
392
398
  /** Default page size */
@@ -405,6 +411,7 @@ export interface SpreadsheetProps<T = any> {
405
411
  defaultPageSize?: number;
406
412
  defaultZoom?: number;
407
413
  defaultPinnedColumns?: string[];
414
+ defaultPinnedRightColumns?: string[];
408
415
  defaultSort?: SpreadsheetSortConfig | null;
409
416
  }) => void;
410
417
  /** Loading state */