@xcelsior/ui-spreadsheets 1.1.15 → 1.1.17
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 +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +32 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +32 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/Spreadsheet.tsx +39 -3
- package/src/components/SpreadsheetToolbar.tsx +212 -209
- package/src/types.ts +2 -0
package/package.json
CHANGED
|
@@ -89,6 +89,7 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
89
89
|
onSelectionChange,
|
|
90
90
|
onSortChange,
|
|
91
91
|
onFilterChange,
|
|
92
|
+
afterFiltered,
|
|
92
93
|
onRowClick,
|
|
93
94
|
onRowDoubleClick,
|
|
94
95
|
onAddCellComment,
|
|
@@ -286,6 +287,29 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
286
287
|
[controlledPageSize, controlledCurrentPage, onPageChange]
|
|
287
288
|
);
|
|
288
289
|
|
|
290
|
+
// Reset pagination to page 1 when filters change
|
|
291
|
+
const resetPaginationToFirstPage = useCallback(() => {
|
|
292
|
+
if (controlledCurrentPage === undefined) {
|
|
293
|
+
setInternalCurrentPage(1);
|
|
294
|
+
}
|
|
295
|
+
onPageChange?.(1, pageSize);
|
|
296
|
+
}, [controlledCurrentPage, onPageChange, pageSize]);
|
|
297
|
+
|
|
298
|
+
// Wrapper for handleFilterChange that resets pagination
|
|
299
|
+
const handleFilterChangeWithReset = useCallback(
|
|
300
|
+
(columnId: string, filter: Parameters<typeof handleFilterChange>[1]) => {
|
|
301
|
+
handleFilterChange(columnId, filter);
|
|
302
|
+
resetPaginationToFirstPage();
|
|
303
|
+
},
|
|
304
|
+
[handleFilterChange, resetPaginationToFirstPage]
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
// Wrapper for clearAllFilters that resets pagination
|
|
308
|
+
const clearAllFiltersWithReset = useCallback(() => {
|
|
309
|
+
clearAllFilters();
|
|
310
|
+
resetPaginationToFirstPage();
|
|
311
|
+
}, [clearAllFilters, resetPaginationToFirstPage]);
|
|
312
|
+
|
|
289
313
|
// Sync sortConfig to spreadsheetSettings when sorting changes
|
|
290
314
|
useEffect(() => {
|
|
291
315
|
setSpreadsheetSettings((prev) => ({
|
|
@@ -525,6 +549,15 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
525
549
|
}
|
|
526
550
|
}, [totalPages, currentPage, serverSide]);
|
|
527
551
|
|
|
552
|
+
// Store afterFiltered in a ref to avoid re-running effect when callback changes
|
|
553
|
+
const afterFilteredRef = useRef(afterFiltered);
|
|
554
|
+
afterFilteredRef.current = afterFiltered;
|
|
555
|
+
|
|
556
|
+
// Call afterFiltered callback when filtered data changes
|
|
557
|
+
useEffect(() => {
|
|
558
|
+
afterFilteredRef.current?.(filteredData.toArray());
|
|
559
|
+
}, [filteredData]);
|
|
560
|
+
|
|
528
561
|
// ==================== EVENT HANDLERS ====================
|
|
529
562
|
|
|
530
563
|
const handleRowSelect = useCallback(
|
|
@@ -828,10 +861,10 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
828
861
|
saveStatus={saveStatus}
|
|
829
862
|
autoSave={spreadsheetSettings.autoSave}
|
|
830
863
|
hasActiveFilters={hasActiveFilters}
|
|
831
|
-
onClearFilters={
|
|
864
|
+
onClearFilters={clearAllFiltersWithReset}
|
|
832
865
|
filters={filters}
|
|
833
866
|
columns={columns}
|
|
834
|
-
onClearFilter={(columnId) =>
|
|
867
|
+
onClearFilter={(columnId) => handleFilterChangeWithReset(columnId, undefined)}
|
|
835
868
|
showFiltersPanel={showFiltersPanel}
|
|
836
869
|
onToggleFiltersPanel={() => setShowFiltersPanel(!showFiltersPanel)}
|
|
837
870
|
onZoomIn={() => setZoom((z) => Math.min(z + 10, 200))}
|
|
@@ -1013,7 +1046,10 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
1013
1046
|
column={column}
|
|
1014
1047
|
filter={filters[column.id]}
|
|
1015
1048
|
onFilterChange={(filter) =>
|
|
1016
|
-
|
|
1049
|
+
handleFilterChangeWithReset(
|
|
1050
|
+
column.id,
|
|
1051
|
+
filter
|
|
1052
|
+
)
|
|
1017
1053
|
}
|
|
1018
1054
|
onClose={() => setActiveFilterColumn(null)}
|
|
1019
1055
|
/>
|
|
@@ -154,235 +154,238 @@ export const SpreadsheetToolbar: React.FC<SpreadsheetToolbarProps> = ({
|
|
|
154
154
|
className
|
|
155
155
|
)}
|
|
156
156
|
>
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
<button
|
|
162
|
-
type={'button'}
|
|
163
|
-
onClick={onUndo}
|
|
164
|
-
disabled={!canUndo}
|
|
165
|
-
className={cn(
|
|
166
|
-
buttonBaseClasses,
|
|
167
|
-
canUndo
|
|
168
|
-
? 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
169
|
-
: 'bg-gray-50 text-gray-400'
|
|
170
|
-
)}
|
|
171
|
-
title={`Undo (${undoCount} changes)`}
|
|
172
|
-
>
|
|
173
|
-
<HiReply className="h-4 w-4" />
|
|
174
|
-
</button>
|
|
175
|
-
<button
|
|
176
|
-
type={'button'}
|
|
177
|
-
onClick={onRedo}
|
|
178
|
-
disabled={!canRedo}
|
|
179
|
-
className={cn(
|
|
180
|
-
buttonBaseClasses,
|
|
181
|
-
canRedo
|
|
182
|
-
? 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
183
|
-
: 'bg-gray-50 text-gray-400'
|
|
184
|
-
)}
|
|
185
|
-
title={`Redo (${redoCount} changes)`}
|
|
186
|
-
style={{ transform: 'scaleX(-1)' }}
|
|
187
|
-
>
|
|
188
|
-
<HiReply className="h-4 w-4" />
|
|
189
|
-
</button>
|
|
190
|
-
</div>
|
|
191
|
-
|
|
192
|
-
{/* Zoom controls */}
|
|
193
|
-
<div className="flex items-center gap-1 px-1.5 py-1 bg-gray-100 rounded">
|
|
194
|
-
<button
|
|
195
|
-
type={'button'}
|
|
196
|
-
onClick={onZoomOut}
|
|
197
|
-
className="p-1 hover:bg-white rounded"
|
|
198
|
-
title="Zoom out"
|
|
199
|
-
>
|
|
200
|
-
<HiZoomOut className="h-4 w-4 text-gray-600" />
|
|
201
|
-
</button>
|
|
202
|
-
<button
|
|
203
|
-
type={'button'}
|
|
204
|
-
onClick={onZoomReset}
|
|
205
|
-
className="px-2 py-0.5 hover:bg-white rounded text-xs min-w-[45px] text-center text-gray-600"
|
|
206
|
-
title="Reset zoom"
|
|
207
|
-
>
|
|
208
|
-
{zoom}%
|
|
209
|
-
</button>
|
|
210
|
-
<button
|
|
211
|
-
type={'button'}
|
|
212
|
-
onClick={onZoomIn}
|
|
213
|
-
className="p-1 hover:bg-white rounded"
|
|
214
|
-
title="Zoom in"
|
|
215
|
-
>
|
|
216
|
-
<HiZoomIn className="h-4 w-4 text-gray-600" />
|
|
217
|
-
</button>
|
|
218
|
-
</div>
|
|
219
|
-
</div>
|
|
220
|
-
|
|
221
|
-
{/* Center section: Status indicators */}
|
|
222
|
-
<div className="flex items-center gap-2 flex-1 min-w-0">
|
|
223
|
-
{/* Selected rows indicator */}
|
|
224
|
-
{selectedRowCount > 0 && (
|
|
225
|
-
<div className="flex items-center gap-2 px-2.5 py-1.5 bg-blue-600 text-white rounded">
|
|
226
|
-
<span className="text-xs font-medium whitespace-nowrap">
|
|
227
|
-
{selectedRowCount} row{selectedRowCount !== 1 ? 's' : ''} selected
|
|
228
|
-
</span>
|
|
157
|
+
{/* Left section: Primary actions */}
|
|
158
|
+
<div className="flex items-center gap-2">
|
|
159
|
+
{/* Undo/Redo buttons */}
|
|
160
|
+
<div className="flex items-center gap-1">
|
|
229
161
|
<button
|
|
230
162
|
type={'button'}
|
|
231
|
-
onClick={
|
|
232
|
-
|
|
233
|
-
|
|
163
|
+
onClick={onUndo}
|
|
164
|
+
disabled={!canUndo}
|
|
165
|
+
className={cn(
|
|
166
|
+
buttonBaseClasses,
|
|
167
|
+
canUndo
|
|
168
|
+
? 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
169
|
+
: 'bg-gray-50 text-gray-400'
|
|
170
|
+
)}
|
|
171
|
+
title={`Undo (${undoCount} changes)`}
|
|
234
172
|
>
|
|
235
|
-
<
|
|
173
|
+
<HiReply className="h-4 w-4" />
|
|
174
|
+
</button>
|
|
175
|
+
<button
|
|
176
|
+
type={'button'}
|
|
177
|
+
onClick={onRedo}
|
|
178
|
+
disabled={!canRedo}
|
|
179
|
+
className={cn(
|
|
180
|
+
buttonBaseClasses,
|
|
181
|
+
canRedo
|
|
182
|
+
? 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
183
|
+
: 'bg-gray-50 text-gray-400'
|
|
184
|
+
)}
|
|
185
|
+
title={`Redo (${redoCount} changes)`}
|
|
186
|
+
style={{ transform: 'scaleX(-1)' }}
|
|
187
|
+
>
|
|
188
|
+
<HiReply className="h-4 w-4" />
|
|
236
189
|
</button>
|
|
237
190
|
</div>
|
|
238
|
-
)}
|
|
239
|
-
|
|
240
|
-
{/* Show filters button */}
|
|
241
|
-
{hasActiveFilters && onToggleFiltersPanel && (
|
|
242
|
-
<button
|
|
243
|
-
type={'button'}
|
|
244
|
-
onClick={onToggleFiltersPanel}
|
|
245
|
-
className={cn(
|
|
246
|
-
'flex items-center gap-2 px-2.5 py-1.5 rounded transition-colors',
|
|
247
|
-
showFiltersPanel
|
|
248
|
-
? 'bg-amber-600 text-white hover:bg-amber-700'
|
|
249
|
-
: 'bg-amber-500 text-white hover:bg-amber-600'
|
|
250
|
-
)}
|
|
251
|
-
title={showFiltersPanel ? 'Hide active filters' : 'Show active filters'}
|
|
252
|
-
>
|
|
253
|
-
<HiFilter className="h-3.5 w-3.5" />
|
|
254
|
-
<span className="text-xs font-medium whitespace-nowrap">
|
|
255
|
-
{activeFilterCount} filter{activeFilterCount !== 1 ? 's' : ''} active
|
|
256
|
-
</span>
|
|
257
|
-
{showFiltersPanel ? (
|
|
258
|
-
<HiChevronUp className="h-3 w-3" />
|
|
259
|
-
) : (
|
|
260
|
-
<HiChevronDown className="h-3 w-3" />
|
|
261
|
-
)}
|
|
262
|
-
</button>
|
|
263
|
-
)}
|
|
264
191
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
192
|
+
{/* Zoom controls */}
|
|
193
|
+
<div className="flex items-center gap-1 px-1.5 py-1 bg-gray-100 rounded">
|
|
194
|
+
<button
|
|
195
|
+
type={'button'}
|
|
196
|
+
onClick={onZoomOut}
|
|
197
|
+
className="p-1 hover:bg-white rounded"
|
|
198
|
+
title="Zoom out"
|
|
199
|
+
>
|
|
200
|
+
<HiZoomOut className="h-4 w-4 text-gray-600" />
|
|
201
|
+
</button>
|
|
202
|
+
<button
|
|
203
|
+
type={'button'}
|
|
204
|
+
onClick={onZoomReset}
|
|
205
|
+
className="px-2 py-0.5 hover:bg-white rounded text-xs min-w-[45px] text-center text-gray-600"
|
|
206
|
+
title="Reset zoom"
|
|
207
|
+
>
|
|
208
|
+
{zoom}%
|
|
209
|
+
</button>
|
|
210
|
+
<button
|
|
211
|
+
type={'button'}
|
|
212
|
+
onClick={onZoomIn}
|
|
213
|
+
className="p-1 hover:bg-white rounded"
|
|
214
|
+
title="Zoom in"
|
|
215
|
+
>
|
|
216
|
+
<HiZoomIn className="h-4 w-4 text-gray-600" />
|
|
217
|
+
</button>
|
|
275
218
|
</div>
|
|
276
|
-
|
|
277
|
-
</div>
|
|
219
|
+
</div>
|
|
278
220
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
221
|
+
{/* Center section: Status indicators */}
|
|
222
|
+
<div className="flex items-center gap-2 flex-1 min-w-0">
|
|
223
|
+
{/* Selected rows indicator */}
|
|
224
|
+
{selectedRowCount > 0 && (
|
|
225
|
+
<div className="flex items-center gap-2 px-2.5 py-1.5 bg-blue-600 text-white rounded">
|
|
226
|
+
<span className="text-xs font-medium whitespace-nowrap">
|
|
227
|
+
{selectedRowCount} row{selectedRowCount !== 1 ? 's' : ''} selected
|
|
228
|
+
</span>
|
|
229
|
+
<button
|
|
230
|
+
type={'button'}
|
|
231
|
+
onClick={onClearSelection}
|
|
232
|
+
className="p-0.5 hover:bg-blue-700 rounded"
|
|
233
|
+
title="Clear selection"
|
|
234
|
+
>
|
|
235
|
+
<HiX className="h-3 w-3" />
|
|
236
|
+
</button>
|
|
237
|
+
</div>
|
|
238
|
+
)}
|
|
292
239
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
240
|
+
{/* Show filters button */}
|
|
241
|
+
{hasActiveFilters && onToggleFiltersPanel && (
|
|
242
|
+
<button
|
|
243
|
+
type={'button'}
|
|
244
|
+
onClick={onToggleFiltersPanel}
|
|
245
|
+
className={cn(
|
|
246
|
+
'flex items-center gap-2 px-2.5 py-1.5 rounded transition-colors',
|
|
247
|
+
showFiltersPanel
|
|
248
|
+
? 'bg-amber-600 text-white hover:bg-amber-700'
|
|
249
|
+
: 'bg-amber-500 text-white hover:bg-amber-600'
|
|
250
|
+
)}
|
|
251
|
+
title={showFiltersPanel ? 'Hide active filters' : 'Show active filters'}
|
|
252
|
+
>
|
|
253
|
+
<HiFilter className="h-3.5 w-3.5" />
|
|
254
|
+
<span className="text-xs font-medium whitespace-nowrap">
|
|
255
|
+
{activeFilterCount} filter{activeFilterCount !== 1 ? 's' : ''}{' '}
|
|
256
|
+
active
|
|
257
|
+
</span>
|
|
258
|
+
{showFiltersPanel ? (
|
|
259
|
+
<HiChevronUp className="h-3 w-3" />
|
|
260
|
+
) : (
|
|
261
|
+
<HiChevronDown className="h-3 w-3" />
|
|
262
|
+
)}
|
|
263
|
+
</button>
|
|
264
|
+
)}
|
|
308
265
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
266
|
+
{/* Summary badge */}
|
|
267
|
+
{summary && (
|
|
268
|
+
<div
|
|
269
|
+
className={cn(
|
|
270
|
+
'flex items-center gap-2 px-2.5 py-1.5 rounded border text-xs',
|
|
271
|
+
getSummaryVariantClasses(summary.variant)
|
|
272
|
+
)}
|
|
273
|
+
>
|
|
274
|
+
<span className="font-semibold whitespace-nowrap">
|
|
275
|
+
{summary.label}:
|
|
276
|
+
</span>
|
|
277
|
+
<span className="font-bold whitespace-nowrap">{summary.value}</span>
|
|
278
|
+
</div>
|
|
279
|
+
)}
|
|
280
|
+
</div>
|
|
320
281
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
setShowMoreMenu(false);
|
|
330
|
-
}}
|
|
331
|
-
className="w-full px-3 py-2 text-left hover:bg-gray-50 flex items-center gap-2 text-xs transition-colors"
|
|
332
|
-
>
|
|
333
|
-
<HiCog className="h-3.5 w-3.5 text-gray-500" />
|
|
334
|
-
<span className="text-gray-700">Settings</span>
|
|
335
|
-
</button>
|
|
282
|
+
{/* Right section: Action buttons */}
|
|
283
|
+
<div className="flex items-center gap-2">
|
|
284
|
+
{/* Save status */}
|
|
285
|
+
{saveStatusDisplay && (
|
|
286
|
+
<span
|
|
287
|
+
className={cn(
|
|
288
|
+
'text-xs flex items-center gap-1',
|
|
289
|
+
saveStatusDisplay.className
|
|
336
290
|
)}
|
|
291
|
+
>
|
|
292
|
+
{saveStatusDisplay.text}
|
|
293
|
+
</span>
|
|
294
|
+
)}
|
|
337
295
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
<HiOutlineQuestionMarkCircle className="h-3.5 w-3.5 text-gray-500" />
|
|
348
|
-
<span className="text-gray-700">Keyboard Shortcuts</span>
|
|
349
|
-
</button>
|
|
296
|
+
{/* Manual save button (when auto-save is off) */}
|
|
297
|
+
{!autoSave && onSave && (
|
|
298
|
+
<button
|
|
299
|
+
type={'button'}
|
|
300
|
+
onClick={onSave}
|
|
301
|
+
disabled={!hasUnsavedChanges}
|
|
302
|
+
className={cn(
|
|
303
|
+
'px-3 py-1.5 text-xs bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors flex items-center gap-1.5',
|
|
304
|
+
'disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-blue-600'
|
|
350
305
|
)}
|
|
306
|
+
>
|
|
307
|
+
<HiCheck className="h-3.5 w-3.5" />
|
|
308
|
+
Save
|
|
309
|
+
</button>
|
|
310
|
+
)}
|
|
311
|
+
|
|
312
|
+
{/* More menu dropdown */}
|
|
313
|
+
<div className="relative" ref={menuRef}>
|
|
314
|
+
<button
|
|
315
|
+
type={'button'}
|
|
316
|
+
onClick={() => setShowMoreMenu(!showMoreMenu)}
|
|
317
|
+
className="px-2.5 py-1.5 bg-gray-100 text-gray-700 rounded hover:bg-gray-200 transition-colors flex items-center gap-1.5 text-xs"
|
|
318
|
+
title="More actions"
|
|
319
|
+
>
|
|
320
|
+
<HiDotsVertical className="h-3.5 w-3.5" />
|
|
321
|
+
<span className="hidden lg:inline">More</span>
|
|
322
|
+
</button>
|
|
351
323
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
<
|
|
324
|
+
{/* Dropdown Menu */}
|
|
325
|
+
{showMoreMenu && (
|
|
326
|
+
<div className="absolute right-0 top-full mt-1 bg-white border border-gray-200 shadow-lg rounded py-1 min-w-[180px] z-50">
|
|
327
|
+
{onSettings && (
|
|
328
|
+
<button
|
|
329
|
+
type={'button'}
|
|
330
|
+
onClick={() => {
|
|
331
|
+
onSettings();
|
|
332
|
+
setShowMoreMenu(false);
|
|
333
|
+
}}
|
|
334
|
+
className="w-full px-3 py-2 text-left hover:bg-gray-50 flex items-center gap-2 text-xs transition-colors"
|
|
335
|
+
>
|
|
336
|
+
<HiCog className="h-3.5 w-3.5 text-gray-500" />
|
|
337
|
+
<span className="text-gray-700">Settings</span>
|
|
338
|
+
</button>
|
|
357
339
|
)}
|
|
358
340
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
341
|
+
{onShowShortcuts && (
|
|
342
|
+
<button
|
|
343
|
+
type={'button'}
|
|
344
|
+
onClick={() => {
|
|
345
|
+
onShowShortcuts();
|
|
346
|
+
setShowMoreMenu(false);
|
|
347
|
+
}}
|
|
348
|
+
className="w-full px-3 py-2 text-left hover:bg-gray-50 flex items-center gap-2 text-xs transition-colors"
|
|
349
|
+
>
|
|
350
|
+
<HiOutlineQuestionMarkCircle className="h-3.5 w-3.5 text-gray-500" />
|
|
351
|
+
<span className="text-gray-700">Keyboard Shortcuts</span>
|
|
352
|
+
</button>
|
|
353
|
+
)}
|
|
354
|
+
|
|
355
|
+
{/* Custom menu items */}
|
|
356
|
+
{menuItems &&
|
|
357
|
+
menuItems.length > 0 &&
|
|
358
|
+
(onSettings || onShowShortcuts) && (
|
|
359
|
+
<div className="border-t border-gray-100 my-1" />
|
|
377
360
|
)}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
361
|
+
|
|
362
|
+
{menuItems?.map((item) => (
|
|
363
|
+
<button
|
|
364
|
+
key={item.id}
|
|
365
|
+
type={'button'}
|
|
366
|
+
disabled={item.disabled}
|
|
367
|
+
onClick={() => {
|
|
368
|
+
item.onClick();
|
|
369
|
+
setShowMoreMenu(false);
|
|
370
|
+
}}
|
|
371
|
+
className={cn(
|
|
372
|
+
'w-full px-3 py-2 text-left hover:bg-gray-50 flex items-center gap-2 text-xs transition-colors',
|
|
373
|
+
item.disabled && 'opacity-50 cursor-not-allowed'
|
|
374
|
+
)}
|
|
375
|
+
>
|
|
376
|
+
{item.icon && (
|
|
377
|
+
<span className="h-3.5 w-3.5 text-gray-500 flex items-center justify-center">
|
|
378
|
+
{item.icon}
|
|
379
|
+
</span>
|
|
380
|
+
)}
|
|
381
|
+
<span className="text-gray-700">{item.label}</span>
|
|
382
|
+
</button>
|
|
383
|
+
))}
|
|
384
|
+
</div>
|
|
385
|
+
)}
|
|
386
|
+
</div>
|
|
383
387
|
</div>
|
|
384
388
|
</div>
|
|
385
|
-
</div>
|
|
386
389
|
|
|
387
390
|
{/* Active filters panel */}
|
|
388
391
|
{showFiltersPanel && filters && columns && onClearFilter && onClearFilters && (
|
package/src/types.ts
CHANGED
|
@@ -355,6 +355,8 @@ export interface SpreadsheetProps<T = any> {
|
|
|
355
355
|
onSortChange?: (sortConfig: SpreadsheetSortConfig | null) => void;
|
|
356
356
|
/** Callback when filters change */
|
|
357
357
|
onFilterChange?: (filters: Record<string, SpreadsheetColumnFilter>) => void;
|
|
358
|
+
/** Callback with filtered data after filters are applied */
|
|
359
|
+
afterFiltered?: (filteredData: T[]) => void;
|
|
358
360
|
/** Callback for row click */
|
|
359
361
|
onRowClick?: (row: T, rowIndex: number) => void;
|
|
360
362
|
/** Callback for row double click */
|