@xcelsior/ui-spreadsheets 1.0.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.
Files changed (37) hide show
  1. package/.storybook/main.ts +27 -0
  2. package/.storybook/preview.tsx +28 -0
  3. package/.turbo/turbo-build.log +22 -0
  4. package/CHANGELOG.md +9 -0
  5. package/biome.json +3 -0
  6. package/dist/index.d.mts +687 -0
  7. package/dist/index.d.ts +687 -0
  8. package/dist/index.js +3459 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/index.mjs +3417 -0
  11. package/dist/index.mjs.map +1 -0
  12. package/package.json +51 -0
  13. package/postcss.config.js +5 -0
  14. package/src/components/ColorPickerPopover.tsx +73 -0
  15. package/src/components/ColumnHeaderActions.tsx +139 -0
  16. package/src/components/CommentModals.tsx +137 -0
  17. package/src/components/KeyboardShortcutsModal.tsx +119 -0
  18. package/src/components/RowIndexColumnHeader.tsx +70 -0
  19. package/src/components/Spreadsheet.stories.tsx +1146 -0
  20. package/src/components/Spreadsheet.tsx +1005 -0
  21. package/src/components/SpreadsheetCell.tsx +341 -0
  22. package/src/components/SpreadsheetFilterDropdown.tsx +341 -0
  23. package/src/components/SpreadsheetHeader.tsx +111 -0
  24. package/src/components/SpreadsheetSettingsModal.tsx +555 -0
  25. package/src/components/SpreadsheetToolbar.tsx +346 -0
  26. package/src/hooks/index.ts +40 -0
  27. package/src/hooks/useSpreadsheetComments.ts +132 -0
  28. package/src/hooks/useSpreadsheetFiltering.ts +379 -0
  29. package/src/hooks/useSpreadsheetHighlighting.ts +201 -0
  30. package/src/hooks/useSpreadsheetKeyboardShortcuts.ts +149 -0
  31. package/src/hooks/useSpreadsheetPinning.ts +203 -0
  32. package/src/hooks/useSpreadsheetUndoRedo.ts +167 -0
  33. package/src/index.ts +31 -0
  34. package/src/types.ts +612 -0
  35. package/src/utils.ts +16 -0
  36. package/tsconfig.json +30 -0
  37. package/tsup.config.ts +12 -0
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@xcelsior/ui-spreadsheets",
3
+ "version": "1.0.1",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "dependencies": {
7
+ "tailwind-merge": "^2.2.2",
8
+ "clsx": "^2.1.0"
9
+ },
10
+ "devDependencies": {
11
+ "@types/react": "^18.2.0",
12
+ "@types/react-dom": "^18.2.0",
13
+ "@storybook/addon-essentials": "^8.6.14",
14
+ "@storybook/addon-interactions": "^8.6.14",
15
+ "@storybook/addon-links": "^8.6.14",
16
+ "@storybook/blocks": "^8.6.14",
17
+ "@storybook/react": "^8.6.14",
18
+ "@storybook/react-vite": "^8.6.14",
19
+ "@storybook/testing-library": "^0.2.2",
20
+ "storybook": "^8.6.14",
21
+ "react-icons": "^4.12.0",
22
+ "tsup": "^8.5.0",
23
+ "typescript": "^5.8.3",
24
+ "tailwindcss": "^4.0.0",
25
+ "@xcelsior/design-system": "1.0.6"
26
+ },
27
+ "peerDependencies": {
28
+ "react": "^18.2.0",
29
+ "react-dom": "^18.2.0",
30
+ "react-hook-form": "^7",
31
+ "@xcelsior/design-system": "^1.0.6",
32
+ "flowbite": "^1.5.3",
33
+ "flowbite-react": "^0.12"
34
+ },
35
+ "exports": {
36
+ ".": {
37
+ "import": "./src/index.ts",
38
+ "require": "./dist/index.js"
39
+ }
40
+ },
41
+ "scripts": {
42
+ "build": "tsup && tsc --noEmit",
43
+ "prepublish": "npm run build",
44
+ "build:watch": "tsup --watch",
45
+ "dev": "tsup --watch",
46
+ "storybook": "storybook dev -p 6007",
47
+ "build-storybook": "storybook build",
48
+ "lint": "biome check . && tsc",
49
+ "type-check": "tsc --noEmit"
50
+ }
51
+ }
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ plugins: {
3
+ '@tailwindcss/postcss': {},
4
+ },
5
+ };
@@ -0,0 +1,73 @@
1
+ import { cn } from '../utils';
2
+ import { HIGHLIGHT_COLORS } from '../hooks/useSpreadsheetHighlighting';
3
+
4
+ export type ColorPaletteType = 'row' | 'column';
5
+
6
+ export interface ColorPickerPopoverProps {
7
+ /** Title displayed in the popover */
8
+ title: string;
9
+ /** Type of color palette to use */
10
+ paletteType?: ColorPaletteType;
11
+ /** Custom colors array (overrides paletteType) */
12
+ colors?: (string | null)[];
13
+ /** Callback when a color is selected */
14
+ onSelectColor: (color: string | null) => void;
15
+ /** Callback when the popover is closed/cancelled */
16
+ onClose: () => void;
17
+ /** Additional className for the container */
18
+ className?: string;
19
+ }
20
+
21
+ /**
22
+ * A reusable color picker popover component for highlighting.
23
+ * Supports both row (darker) and column (lighter) color palettes.
24
+ */
25
+ export function ColorPickerPopover({
26
+ title,
27
+ paletteType = 'column',
28
+ colors,
29
+ onSelectColor,
30
+ onClose,
31
+ className,
32
+ }: ColorPickerPopoverProps) {
33
+ // Use custom colors if provided, otherwise use palette based on type
34
+ const colorPalette = colors ?? [...HIGHLIGHT_COLORS[paletteType], null];
35
+
36
+ return (
37
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
38
+ <div className={cn('bg-white rounded-lg shadow-xl p-4 w-64', className)}>
39
+ <h3 className="text-sm font-semibold mb-3">{title}</h3>
40
+ <div className="grid grid-cols-5 gap-2">
41
+ {colorPalette.map((color) => (
42
+ <button
43
+ key={color || 'clear'}
44
+ type="button"
45
+ onClick={() => onSelectColor(color)}
46
+ className={cn(
47
+ 'w-8 h-8 rounded border-2 transition-transform hover:scale-110',
48
+ color
49
+ ? 'border-gray-300'
50
+ : 'border-gray-300 bg-white flex items-center justify-center text-gray-400 text-xs'
51
+ )}
52
+ style={color ? { backgroundColor: color } : undefined}
53
+ title={color || 'Clear highlight'}
54
+ >
55
+ {!color && '✕'}
56
+ </button>
57
+ ))}
58
+ </div>
59
+ <div className="flex justify-end mt-4">
60
+ <button
61
+ type="button"
62
+ onClick={onClose}
63
+ className="px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-100 rounded transition-colors"
64
+ >
65
+ Cancel
66
+ </button>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ );
71
+ }
72
+
73
+ ColorPickerPopover.displayName = 'ColorPickerPopover';
@@ -0,0 +1,139 @@
1
+ import type React from 'react';
2
+ import { HiColorSwatch, HiFilter } from 'react-icons/hi';
3
+ import { MdOutlinePushPin, MdPushPin } from 'react-icons/md';
4
+ import { cn } from '../utils';
5
+
6
+ export interface ColumnHeaderActionsProps {
7
+ /** Whether filtering is enabled for this column */
8
+ enableFiltering?: boolean;
9
+ /** Whether highlighting is enabled for this column */
10
+ enableHighlighting?: boolean;
11
+ /** Whether pinning is enabled for this column */
12
+ enablePinning?: boolean;
13
+ /** Whether the column currently has an active filter */
14
+ hasActiveFilter?: boolean;
15
+ /** Whether the column currently has an active highlight */
16
+ hasActiveHighlight?: boolean;
17
+ /** Whether the column is currently pinned */
18
+ isPinned?: boolean;
19
+ /** Callback when filter button is clicked */
20
+ onFilterClick?: () => void;
21
+ /** Callback when highlight button is clicked */
22
+ onHighlightClick?: () => void;
23
+ /** Callback when pin button is clicked */
24
+ onPinClick?: () => void;
25
+ /** Title for the filter button */
26
+ filterTitle?: string;
27
+ /** Title for the highlight button */
28
+ highlightTitle?: string;
29
+ /** Title for the pin button when pinned */
30
+ pinnedTitle?: string;
31
+ /** Title for the pin button when unpinned */
32
+ unpinnedTitle?: string;
33
+ /** Additional className */
34
+ className?: string;
35
+ }
36
+
37
+ /**
38
+ * Reusable action buttons for column headers (filter, highlight, pin).
39
+ * Works for both regular columns and the row index column.
40
+ */
41
+ export function ColumnHeaderActions({
42
+ enableFiltering = false,
43
+ enableHighlighting = false,
44
+ enablePinning = true,
45
+ hasActiveFilter = false,
46
+ hasActiveHighlight = false,
47
+ isPinned = false,
48
+ onFilterClick,
49
+ onHighlightClick,
50
+ onPinClick,
51
+ filterTitle = 'Filter column',
52
+ highlightTitle = 'Highlight column',
53
+ pinnedTitle = 'Unpin column',
54
+ unpinnedTitle = 'Pin column',
55
+ className,
56
+ }: ColumnHeaderActionsProps) {
57
+ const handleClick = (e: React.MouseEvent) => {
58
+ e.stopPropagation();
59
+ };
60
+
61
+ const handleKeyDown = (e: React.KeyboardEvent) => {
62
+ e.stopPropagation();
63
+ };
64
+
65
+ return (
66
+ <button
67
+ type="button"
68
+ className={cn('flex items-center gap-0.5', className)}
69
+ onClick={handleClick}
70
+ onKeyDown={handleKeyDown}
71
+ >
72
+ {/* Filter button */}
73
+ {enableFiltering && onFilterClick && (
74
+ <button
75
+ type="button"
76
+ onClick={(e) => {
77
+ e.stopPropagation();
78
+ onFilterClick();
79
+ }}
80
+ className={cn(
81
+ 'p-0.5 hover:bg-gray-200 rounded transition-opacity',
82
+ hasActiveFilter
83
+ ? 'text-blue-600 opacity-100'
84
+ : 'text-gray-400 opacity-0 group-hover:opacity-100'
85
+ )}
86
+ title={filterTitle}
87
+ >
88
+ <HiFilter className="h-3 w-3" />
89
+ </button>
90
+ )}
91
+
92
+ {/* Highlight button */}
93
+ {enableHighlighting && onHighlightClick && (
94
+ <button
95
+ type="button"
96
+ onClick={(e) => {
97
+ e.stopPropagation();
98
+ onHighlightClick();
99
+ }}
100
+ className={cn(
101
+ 'p-0.5 hover:bg-gray-200 rounded transition-opacity',
102
+ hasActiveHighlight
103
+ ? 'text-amber-500 opacity-100'
104
+ : 'text-gray-400 opacity-0 group-hover:opacity-100'
105
+ )}
106
+ title={highlightTitle}
107
+ >
108
+ <HiColorSwatch className="h-3 w-3" />
109
+ </button>
110
+ )}
111
+
112
+ {/* Pin button */}
113
+ {enablePinning && onPinClick && (
114
+ <button
115
+ type="button"
116
+ onClick={(e) => {
117
+ e.stopPropagation();
118
+ onPinClick();
119
+ }}
120
+ className={cn(
121
+ 'p-0.5 hover:bg-gray-200 rounded transition-opacity',
122
+ isPinned
123
+ ? 'text-blue-600 opacity-100'
124
+ : 'text-gray-400 opacity-0 group-hover:opacity-100'
125
+ )}
126
+ title={isPinned ? pinnedTitle : unpinnedTitle}
127
+ >
128
+ {isPinned ? (
129
+ <MdPushPin className="h-3 w-3" />
130
+ ) : (
131
+ <MdOutlinePushPin className="h-3 w-3" />
132
+ )}
133
+ </button>
134
+ )}
135
+ </button>
136
+ );
137
+ }
138
+
139
+ ColumnHeaderActions.displayName = 'ColumnHeaderActions';
@@ -0,0 +1,137 @@
1
+ import { cn } from '../utils';
2
+ import type { CellComment } from '../types';
3
+
4
+ // ==================== Add Comment Modal ====================
5
+
6
+ export interface AddCommentModalProps {
7
+ /** Whether the modal is open */
8
+ isOpen: boolean;
9
+ /** Current comment text */
10
+ commentText: string;
11
+ /** Callback to update comment text */
12
+ onCommentTextChange: (text: string) => void;
13
+ /** Callback to add the comment */
14
+ onAdd: () => void;
15
+ /** Callback to close/cancel */
16
+ onClose: () => void;
17
+ }
18
+
19
+ export function AddCommentModal({
20
+ isOpen,
21
+ commentText,
22
+ onCommentTextChange,
23
+ onAdd,
24
+ onClose,
25
+ }: AddCommentModalProps) {
26
+ if (!isOpen) return null;
27
+
28
+ return (
29
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
30
+ <div className="bg-white rounded-lg shadow-xl p-6 w-96 max-w-full mx-4">
31
+ <h3 className="text-lg font-semibold mb-4">Add Row Comment</h3>
32
+ <textarea
33
+ value={commentText}
34
+ onChange={(e) => onCommentTextChange(e.target.value)}
35
+ placeholder="Enter your comment..."
36
+ className="w-full h-24 p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none"
37
+ />
38
+ <div className="flex justify-end gap-2 mt-4">
39
+ <button
40
+ type="button"
41
+ onClick={onClose}
42
+ className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
43
+ >
44
+ Cancel
45
+ </button>
46
+ <button
47
+ type="button"
48
+ onClick={onAdd}
49
+ disabled={!commentText.trim()}
50
+ className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
51
+ >
52
+ Add Comment
53
+ </button>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ );
58
+ }
59
+
60
+ AddCommentModal.displayName = 'AddCommentModal';
61
+
62
+ // ==================== View Comments Modal ====================
63
+
64
+ export interface ViewCommentsModalProps {
65
+ /** Whether the modal is open */
66
+ isOpen: boolean;
67
+ /** Comments to display */
68
+ comments: CellComment[];
69
+ /** Callback to toggle comment resolved status */
70
+ onToggleResolved: (commentId: string) => void;
71
+ /** Callback to close the modal */
72
+ onClose: () => void;
73
+ }
74
+
75
+ export function ViewCommentsModal({
76
+ isOpen,
77
+ comments,
78
+ onToggleResolved,
79
+ onClose,
80
+ }: ViewCommentsModalProps) {
81
+ if (!isOpen) return null;
82
+
83
+ return (
84
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
85
+ <div className="bg-white rounded-lg shadow-xl p-6 w-[480px] max-w-full mx-4 max-h-[80vh] flex flex-col">
86
+ <div className="flex items-center justify-between mb-4">
87
+ <h3 className="text-lg font-semibold">Row Comments</h3>
88
+ <button
89
+ type="button"
90
+ onClick={onClose}
91
+ className="p-1 hover:bg-gray-100 rounded-lg transition-colors"
92
+ >
93
+
94
+ </button>
95
+ </div>
96
+ <div className="flex-1 overflow-y-auto space-y-3">
97
+ {comments.map((comment) => (
98
+ <div
99
+ key={comment.id}
100
+ className={cn(
101
+ 'p-3 rounded-lg border',
102
+ comment.resolved
103
+ ? 'bg-gray-50 border-gray-200'
104
+ : 'bg-yellow-50 border-yellow-200'
105
+ )}
106
+ >
107
+ <div className="flex items-start justify-between gap-2">
108
+ <p className="text-sm text-gray-700">{comment.text}</p>
109
+ <button
110
+ type="button"
111
+ onClick={() => onToggleResolved(comment.id)}
112
+ className={cn(
113
+ 'flex-shrink-0 px-2 py-1 text-xs rounded transition-colors',
114
+ comment.resolved
115
+ ? 'bg-gray-200 text-gray-600 hover:bg-gray-300'
116
+ : 'bg-green-100 text-green-700 hover:bg-green-200'
117
+ )}
118
+ >
119
+ {comment.resolved ? 'Reopen' : 'Resolve'}
120
+ </button>
121
+ </div>
122
+ <div className="flex items-center gap-2 mt-2 text-xs text-gray-500">
123
+ {comment.author && <span>{comment.author}</span>}
124
+ <span>{new Date(comment.timestamp).toLocaleString()}</span>
125
+ </div>
126
+ </div>
127
+ ))}
128
+ {comments.length === 0 && (
129
+ <p className="text-center text-gray-500 py-8">No comments for this row.</p>
130
+ )}
131
+ </div>
132
+ </div>
133
+ </div>
134
+ );
135
+ }
136
+
137
+ ViewCommentsModal.displayName = 'ViewCommentsModal';
@@ -0,0 +1,119 @@
1
+ import React from 'react';
2
+
3
+ export interface KeyboardShortcutsModalProps {
4
+ /** Whether the modal is open */
5
+ isOpen: boolean;
6
+ /** Callback to close the modal */
7
+ onClose: () => void;
8
+ /** Shortcut definitions */
9
+ shortcuts: {
10
+ general: Array<{ label: string; keys: string[] }>;
11
+ rowSelection: Array<{ label: string; keys: string[] }>;
12
+ editing: Array<{ label: string; keys: string[] }>;
13
+ rowActions: Array<{ label: string; description: string }>;
14
+ };
15
+ }
16
+
17
+ /**
18
+ * Modal component displaying keyboard shortcuts for the spreadsheet.
19
+ */
20
+ export function KeyboardShortcutsModal({
21
+ isOpen,
22
+ onClose,
23
+ shortcuts,
24
+ }: KeyboardShortcutsModalProps) {
25
+ if (!isOpen) return null;
26
+
27
+ return (
28
+ <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
29
+ <div className="bg-white rounded-lg shadow-xl p-6 w-full max-w-2xl max-h-[80vh] overflow-y-auto mx-4">
30
+ <div className="flex items-center justify-between mb-4">
31
+ <h3 className="text-xl font-bold text-gray-900">Keyboard Shortcuts</h3>
32
+ <button
33
+ type="button"
34
+ onClick={onClose}
35
+ className="p-1 hover:bg-gray-100 rounded"
36
+ >
37
+ <span className="text-gray-500 text-xl">✕</span>
38
+ </button>
39
+ </div>
40
+
41
+ <div className="space-y-6">
42
+ {/* General Section */}
43
+ <ShortcutSection title="General">
44
+ {shortcuts.general.map((shortcut, index) => (
45
+ <ShortcutRow key={index} label={shortcut.label} keys={shortcut.keys} />
46
+ ))}
47
+ </ShortcutSection>
48
+
49
+ {/* Row Selection Section */}
50
+ <ShortcutSection title="Row Selection">
51
+ {shortcuts.rowSelection.map((shortcut, index) => (
52
+ <ShortcutRow key={index} label={shortcut.label} keys={shortcut.keys} />
53
+ ))}
54
+ </ShortcutSection>
55
+
56
+ {/* Editing Section */}
57
+ <ShortcutSection title="Editing">
58
+ {shortcuts.editing.map((shortcut, index) => (
59
+ <ShortcutRow key={index} label={shortcut.label} keys={shortcut.keys} />
60
+ ))}
61
+ </ShortcutSection>
62
+
63
+ {/* Row Actions Section */}
64
+ <ShortcutSection title="Row Actions (hover over row #)">
65
+ {shortcuts.rowActions.map((action, index) => (
66
+ <div key={index} className="flex items-center justify-between">
67
+ <span className="text-gray-600 text-sm">{action.label}</span>
68
+ <span className="text-gray-500 text-xs">{action.description}</span>
69
+ </div>
70
+ ))}
71
+ </ShortcutSection>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ );
76
+ }
77
+
78
+ interface ShortcutSectionProps {
79
+ title: string;
80
+ children: React.ReactNode;
81
+ }
82
+
83
+ function ShortcutSection({ title, children }: ShortcutSectionProps) {
84
+ return (
85
+ <div>
86
+ <h4 className="text-gray-900 font-semibold mb-3">{title}</h4>
87
+ <div className="space-y-2">{children}</div>
88
+ </div>
89
+ );
90
+ }
91
+
92
+ interface ShortcutRowProps {
93
+ label: string;
94
+ keys: string[];
95
+ }
96
+
97
+ function ShortcutRow({ label, keys }: ShortcutRowProps) {
98
+ return (
99
+ <div className="flex items-center justify-between">
100
+ <span className="text-gray-600 text-sm">{label}</span>
101
+ <div className="flex items-center gap-1">
102
+ {keys.map((key, index) => (
103
+ <React.Fragment key={index}>
104
+ {index > 0 && <span className="text-gray-400">+</span>}
105
+ {key.includes('Click') ? (
106
+ <span className="text-gray-500 text-xs">{key}</span>
107
+ ) : (
108
+ <kbd className="px-2 py-1 bg-gray-100 text-gray-800 rounded text-xs border border-gray-200">
109
+ {key}
110
+ </kbd>
111
+ )}
112
+ </React.Fragment>
113
+ ))}
114
+ </div>
115
+ </div>
116
+ );
117
+ }
118
+
119
+ KeyboardShortcutsModal.displayName = 'KeyboardShortcutsModal';
@@ -0,0 +1,70 @@
1
+ import { cn } from '../utils';
2
+ import { ColumnHeaderActions } from './ColumnHeaderActions';
3
+ import { ROW_INDEX_COLUMN_WIDTH } from '../hooks/useSpreadsheetPinning';
4
+
5
+ export interface RowIndexColumnHeaderProps {
6
+ /** Whether highlighting is enabled */
7
+ enableHighlighting?: boolean;
8
+ /** Current highlight color (if any) */
9
+ highlightColor?: string;
10
+ /** Whether the column is pinned */
11
+ isPinned?: boolean;
12
+ /** Callback when highlight button is clicked */
13
+ onHighlightClick?: () => void;
14
+ /** Callback when pin button is clicked */
15
+ onPinClick?: () => void;
16
+ /** Whether this is in the column groups row (needs rowSpan=2) */
17
+ hasColumnGroups?: boolean;
18
+ /** Additional className */
19
+ className?: string;
20
+ }
21
+
22
+ /**
23
+ * Row index column header (#) with highlight and pin actions.
24
+ * Uses the same ColumnHeaderActions component as regular columns for consistency.
25
+ */
26
+ export function RowIndexColumnHeader({
27
+ enableHighlighting = false,
28
+ highlightColor,
29
+ isPinned = false,
30
+ onHighlightClick,
31
+ onPinClick,
32
+ hasColumnGroups = false,
33
+ className,
34
+ }: RowIndexColumnHeaderProps) {
35
+ return (
36
+ <th
37
+ className={cn(
38
+ 'border border-gray-200 px-2 py-1.5 text-center font-bold text-gray-700 group',
39
+ isPinned ? 'z-30' : 'z-20',
40
+ className
41
+ )}
42
+ rowSpan={hasColumnGroups ? 2 : undefined}
43
+ style={{
44
+ minWidth: `${ROW_INDEX_COLUMN_WIDTH}px`,
45
+ width: `${ROW_INDEX_COLUMN_WIDTH}px`,
46
+ position: 'sticky',
47
+ top: 0,
48
+ left: isPinned ? 0 : undefined,
49
+ backgroundColor: highlightColor || 'rgb(243 244 246)',
50
+ }}
51
+ >
52
+ <div className="flex items-center justify-center gap-1">
53
+ <span>#</span>
54
+ <ColumnHeaderActions
55
+ enableHighlighting={enableHighlighting}
56
+ enablePinning={true}
57
+ hasActiveHighlight={!!highlightColor}
58
+ isPinned={isPinned}
59
+ onHighlightClick={onHighlightClick}
60
+ onPinClick={onPinClick}
61
+ highlightTitle="Highlight row index column"
62
+ pinnedTitle="Unpin row index column"
63
+ unpinnedTitle="Pin row index column"
64
+ />
65
+ </div>
66
+ </th>
67
+ );
68
+ }
69
+
70
+ RowIndexColumnHeader.displayName = 'RowIndexColumnHeader';