@xcelsior/ui-spreadsheets 1.1.15 → 1.1.16
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.js +23 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +23 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/Spreadsheet.tsx +26 -3
- package/src/components/SpreadsheetToolbar.tsx +212 -209
package/package.json
CHANGED
|
@@ -286,6 +286,29 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
286
286
|
[controlledPageSize, controlledCurrentPage, onPageChange]
|
|
287
287
|
);
|
|
288
288
|
|
|
289
|
+
// Reset pagination to page 1 when filters change
|
|
290
|
+
const resetPaginationToFirstPage = useCallback(() => {
|
|
291
|
+
if (controlledCurrentPage === undefined) {
|
|
292
|
+
setInternalCurrentPage(1);
|
|
293
|
+
}
|
|
294
|
+
onPageChange?.(1, pageSize);
|
|
295
|
+
}, [controlledCurrentPage, onPageChange, pageSize]);
|
|
296
|
+
|
|
297
|
+
// Wrapper for handleFilterChange that resets pagination
|
|
298
|
+
const handleFilterChangeWithReset = useCallback(
|
|
299
|
+
(columnId: string, filter: Parameters<typeof handleFilterChange>[1]) => {
|
|
300
|
+
handleFilterChange(columnId, filter);
|
|
301
|
+
resetPaginationToFirstPage();
|
|
302
|
+
},
|
|
303
|
+
[handleFilterChange, resetPaginationToFirstPage]
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
// Wrapper for clearAllFilters that resets pagination
|
|
307
|
+
const clearAllFiltersWithReset = useCallback(() => {
|
|
308
|
+
clearAllFilters();
|
|
309
|
+
resetPaginationToFirstPage();
|
|
310
|
+
}, [clearAllFilters, resetPaginationToFirstPage]);
|
|
311
|
+
|
|
289
312
|
// Sync sortConfig to spreadsheetSettings when sorting changes
|
|
290
313
|
useEffect(() => {
|
|
291
314
|
setSpreadsheetSettings((prev) => ({
|
|
@@ -828,10 +851,10 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
828
851
|
saveStatus={saveStatus}
|
|
829
852
|
autoSave={spreadsheetSettings.autoSave}
|
|
830
853
|
hasActiveFilters={hasActiveFilters}
|
|
831
|
-
onClearFilters={
|
|
854
|
+
onClearFilters={clearAllFiltersWithReset}
|
|
832
855
|
filters={filters}
|
|
833
856
|
columns={columns}
|
|
834
|
-
onClearFilter={(columnId) =>
|
|
857
|
+
onClearFilter={(columnId) => handleFilterChangeWithReset(columnId, undefined)}
|
|
835
858
|
showFiltersPanel={showFiltersPanel}
|
|
836
859
|
onToggleFiltersPanel={() => setShowFiltersPanel(!showFiltersPanel)}
|
|
837
860
|
onZoomIn={() => setZoom((z) => Math.min(z + 10, 200))}
|
|
@@ -1013,7 +1036,7 @@ export function Spreadsheet<T extends Record<string, any>>({
|
|
|
1013
1036
|
column={column}
|
|
1014
1037
|
filter={filters[column.id]}
|
|
1015
1038
|
onFilterChange={(filter) =>
|
|
1016
|
-
|
|
1039
|
+
handleFilterChangeWithReset(column.id, filter)
|
|
1017
1040
|
}
|
|
1018
1041
|
onClose={() => setActiveFilterColumn(null)}
|
|
1019
1042
|
/>
|
|
@@ -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 && (
|