@snack-uikit/table 0.29.0 → 0.30.1-preview-9a03a1c9.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.
- package/CHANGELOG.md +11 -0
- package/README.md +2 -0
- package/dist/cjs/components/Table/Table.d.ts +1 -1
- package/dist/cjs/components/Table/Table.js +141 -104
- package/dist/cjs/components/Table/hooks/index.d.ts +1 -0
- package/dist/cjs/components/Table/hooks/index.js +2 -1
- package/dist/cjs/components/Table/hooks/useColumnOrderByDrag.d.ts +9 -0
- package/dist/cjs/components/Table/hooks/useColumnOrderByDrag.js +76 -0
- package/dist/cjs/components/Table/utils.js +6 -3
- package/dist/cjs/components/types.d.ts +2 -0
- package/dist/cjs/constants.d.ts +1 -0
- package/dist/cjs/constants.js +3 -2
- package/dist/cjs/helperComponents/Cells/BodyCell/BodyCell.d.ts +2 -1
- package/dist/cjs/helperComponents/Cells/BodyCell/BodyCell.js +13 -3
- package/dist/cjs/helperComponents/Cells/HeaderCell/DragHandle.d.ts +6 -0
- package/dist/cjs/helperComponents/Cells/HeaderCell/DragHandle.js +27 -0
- package/dist/cjs/helperComponents/Cells/HeaderCell/HeaderCell.d.ts +2 -1
- package/dist/cjs/helperComponents/Cells/HeaderCell/HeaderCell.js +22 -2
- package/dist/cjs/helperComponents/Cells/HeaderCell/styles.module.css +11 -0
- package/dist/cjs/helperComponents/Rows/BodyRow.d.ts +4 -1
- package/dist/cjs/helperComponents/Rows/BodyRow.js +30 -12
- package/dist/cjs/helperComponents/Rows/HeaderRow.d.ts +7 -1
- package/dist/cjs/helperComponents/Rows/HeaderRow.js +33 -13
- package/dist/cjs/helperComponents/hooks.d.ts +22 -13
- package/dist/cjs/helperComponents/hooks.js +63 -15
- package/dist/cjs/types.d.ts +5 -0
- package/dist/esm/components/Table/Table.d.ts +1 -1
- package/dist/esm/components/Table/Table.js +34 -25
- package/dist/esm/components/Table/hooks/index.d.ts +1 -0
- package/dist/esm/components/Table/hooks/index.js +1 -0
- package/dist/esm/components/Table/hooks/useColumnOrderByDrag.d.ts +9 -0
- package/dist/esm/components/Table/hooks/useColumnOrderByDrag.js +66 -0
- package/dist/esm/components/Table/utils.js +6 -3
- package/dist/esm/components/types.d.ts +2 -0
- package/dist/esm/constants.d.ts +1 -0
- package/dist/esm/constants.js +1 -0
- package/dist/esm/helperComponents/Cells/BodyCell/BodyCell.d.ts +2 -1
- package/dist/esm/helperComponents/Cells/BodyCell/BodyCell.js +7 -3
- package/dist/esm/helperComponents/Cells/HeaderCell/DragHandle.d.ts +6 -0
- package/dist/esm/helperComponents/Cells/HeaderCell/DragHandle.js +6 -0
- package/dist/esm/helperComponents/Cells/HeaderCell/HeaderCell.d.ts +2 -1
- package/dist/esm/helperComponents/Cells/HeaderCell/HeaderCell.js +13 -4
- package/dist/esm/helperComponents/Cells/HeaderCell/styles.module.css +11 -0
- package/dist/esm/helperComponents/Rows/BodyRow.d.ts +4 -1
- package/dist/esm/helperComponents/Rows/BodyRow.js +4 -3
- package/dist/esm/helperComponents/Rows/HeaderRow.d.ts +7 -1
- package/dist/esm/helperComponents/Rows/HeaderRow.js +4 -3
- package/dist/esm/helperComponents/hooks.d.ts +22 -13
- package/dist/esm/helperComponents/hooks.js +58 -15
- package/dist/esm/types.d.ts +5 -0
- package/package.json +6 -2
- package/src/components/Table/Table.tsx +151 -117
- package/src/components/Table/hooks/index.ts +1 -0
- package/src/components/Table/hooks/useColumnOrderByDrag.ts +100 -0
- package/src/components/Table/utils.ts +8 -3
- package/src/components/types.ts +3 -0
- package/src/constants.tsx +2 -0
- package/src/helperComponents/Cells/BodyCell/BodyCell.tsx +9 -2
- package/src/helperComponents/Cells/HeaderCell/DragHandle.tsx +18 -0
- package/src/helperComponents/Cells/HeaderCell/HeaderCell.tsx +19 -4
- package/src/helperComponents/Cells/HeaderCell/styles.module.scss +15 -4
- package/src/helperComponents/Rows/BodyRow.tsx +36 -9
- package/src/helperComponents/Rows/HeaderRow.tsx +51 -18
- package/src/helperComponents/Rows/styles.module.scss +2 -2
- package/src/helperComponents/hooks.ts +78 -15
- package/src/types.ts +6 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { closestCenter, DndContext } from '@dnd-kit/core';
|
|
2
|
+
import { restrictToHorizontalAxis } from '@dnd-kit/modifiers';
|
|
1
3
|
import {
|
|
2
4
|
CellContext,
|
|
3
5
|
ColumnPinningState,
|
|
@@ -43,7 +45,7 @@ import { getTreeColumnDef } from '../../helperComponents/Cells/TreeCell';
|
|
|
43
45
|
import { ColumnDefinition } from '../../types';
|
|
44
46
|
import { fuzzyFilter } from '../../utils';
|
|
45
47
|
import { TableProps } from '../types';
|
|
46
|
-
import { useLoadingTable, useStateControl } from './hooks';
|
|
48
|
+
import { useColumnOrderByDrag, useLoadingTable, useStateControl } from './hooks';
|
|
47
49
|
import { usePageReset } from './hooks/usePageReset';
|
|
48
50
|
import styles from './styles.module.scss';
|
|
49
51
|
import {
|
|
@@ -97,6 +99,7 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
|
|
|
97
99
|
savedState,
|
|
98
100
|
expanding,
|
|
99
101
|
bulkActions: bulkActionsProp,
|
|
102
|
+
enableColumnsOrderSortByDrag,
|
|
100
103
|
...rest
|
|
101
104
|
}: TableProps<TData, TFilters>) {
|
|
102
105
|
const { state: globalFilter, onStateChange: onGlobalFilterChange } = useStateControl<string>(search, '');
|
|
@@ -130,6 +133,9 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
|
|
|
130
133
|
return cols;
|
|
131
134
|
}, [columnDefinitions, enableSelection, enableSelectPinned, expanding]);
|
|
132
135
|
|
|
136
|
+
const { columnOrder, setColumnOrder, groupedColumnOrderState, sensors, handleDragEnd } =
|
|
137
|
+
useColumnOrderByDrag(tableColumns);
|
|
138
|
+
|
|
133
139
|
const columnPinning = useMemo(() => {
|
|
134
140
|
const pinningState: Required<ColumnPinningState> = { left: [], right: [] };
|
|
135
141
|
for (const col of tableColumns) {
|
|
@@ -160,6 +166,7 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
|
|
|
160
166
|
columns: tableColumns,
|
|
161
167
|
state: {
|
|
162
168
|
columnPinning,
|
|
169
|
+
columnOrder,
|
|
163
170
|
globalFilter,
|
|
164
171
|
rowSelection,
|
|
165
172
|
sorting,
|
|
@@ -173,6 +180,7 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
|
|
|
173
180
|
minSize: 40,
|
|
174
181
|
cell: (cell: CellContext<TData, unknown>) => <TruncateString text={String(cell.getValue())} maxLines={1} />,
|
|
175
182
|
},
|
|
183
|
+
onColumnOrderChange: setColumnOrder,
|
|
176
184
|
|
|
177
185
|
manualSorting,
|
|
178
186
|
manualPagination,
|
|
@@ -256,9 +264,11 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
|
|
|
256
264
|
const columnSizeVarsRef = useRef<Record<string, string>>();
|
|
257
265
|
const headers = table.getFlatHeaders();
|
|
258
266
|
|
|
259
|
-
const
|
|
267
|
+
const columnSizes = useMemo(() => {
|
|
260
268
|
const originalColumnDefs = table._getColumnDefs();
|
|
261
|
-
const
|
|
269
|
+
const vars: Record<string, string> = {};
|
|
270
|
+
const realSizes: Record<string, number> = {};
|
|
271
|
+
const resizedColumnIndex = headers.findIndex(({ column }) => column.getIsResizing());
|
|
262
272
|
|
|
263
273
|
for (let i = 0; i < headers.length; i++) {
|
|
264
274
|
const header = headers[i];
|
|
@@ -267,8 +277,8 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
|
|
|
267
277
|
const originalColDef = originalColumnDefs.find(col => getColumnId(header) === col.id);
|
|
268
278
|
|
|
269
279
|
if (header.id === 'snack_predefined_statusColumn' && !originalColDef?.header && !originalColDef?.enableSorting) {
|
|
270
|
-
|
|
271
|
-
|
|
280
|
+
vars[sizeKey] = 'var(--size-table-cell-status-indicator-horizontal)';
|
|
281
|
+
vars[flexKey] = '100%';
|
|
272
282
|
} else {
|
|
273
283
|
const originalColumnDefSize = originalColDef?.size;
|
|
274
284
|
let initSize = originalColumnDefSize ? `${originalColumnDefSize}px` : '100%';
|
|
@@ -289,12 +299,9 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
|
|
|
289
299
|
const currentSize = header.getSize();
|
|
290
300
|
const colDefSize = header.column.columnDef.size;
|
|
291
301
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const realSize = getCurrentlyConfiguredHeaderWidth(header.id);
|
|
296
|
-
table.setColumnSizing(old => ({ ...old, [header.id]: realSize }));
|
|
297
|
-
|
|
302
|
+
if (currentSize !== colDefSize || (i < resizedColumnIndex && prevSize === '100%')) {
|
|
303
|
+
const realSize = prevSize === '100%' ? getCurrentlyConfiguredHeaderWidth(header.id) : currentSize;
|
|
304
|
+
realSizes[header.id] = realSize;
|
|
298
305
|
size = `${realSize}px`;
|
|
299
306
|
}
|
|
300
307
|
}
|
|
@@ -303,12 +310,12 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
|
|
|
303
310
|
saveStateToLocalStorage({ id: savedState.id, columnId: header.id, size });
|
|
304
311
|
}
|
|
305
312
|
|
|
306
|
-
|
|
307
|
-
|
|
313
|
+
vars[sizeKey] = size;
|
|
314
|
+
vars[flexKey] = size === '100%' ? 'unset' : '0';
|
|
308
315
|
}
|
|
309
316
|
}
|
|
310
317
|
|
|
311
|
-
return
|
|
318
|
+
return { vars, realSizes };
|
|
312
319
|
/*
|
|
313
320
|
effect must be called only on columnSizingInfo.isResizingColumn changes
|
|
314
321
|
to reduce unnecessary recalculations
|
|
@@ -321,8 +328,12 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
|
|
|
321
328
|
}, [table.getState().columnSizingInfo.isResizingColumn, headers, table.getTotalSize()]);
|
|
322
329
|
|
|
323
330
|
useEffect(() => {
|
|
324
|
-
|
|
325
|
-
|
|
331
|
+
if (Object.keys(columnSizes.realSizes).length) {
|
|
332
|
+
table.setColumnSizing(old => ({ ...old, ...columnSizes.realSizes }));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
columnSizeVarsRef.current = columnSizes.vars;
|
|
336
|
+
}, [columnSizes, table]);
|
|
326
337
|
|
|
327
338
|
const tableRows = table.getRowModel().rows;
|
|
328
339
|
const tableCenterRows = table.getCenterRows();
|
|
@@ -359,110 +370,133 @@ export function Table<TData extends object, TFilters extends FiltersState = Reco
|
|
|
359
370
|
const showToolbar = !suppressToolbar;
|
|
360
371
|
|
|
361
372
|
return (
|
|
362
|
-
<
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
373
|
+
<DndContext
|
|
374
|
+
collisionDetection={closestCenter}
|
|
375
|
+
modifiers={[restrictToHorizontalAxis]}
|
|
376
|
+
onDragEnd={handleDragEnd}
|
|
377
|
+
sensors={sensors}
|
|
378
|
+
>
|
|
379
|
+
<CellAutoResizeContext.Provider value={{ updateCellMap }}>
|
|
380
|
+
<div
|
|
381
|
+
style={{
|
|
382
|
+
'--page-size': cssPageSize,
|
|
383
|
+
}}
|
|
384
|
+
className={cn(styles.wrapper, className)}
|
|
385
|
+
{...extractSupportProps(rest)}
|
|
386
|
+
>
|
|
387
|
+
{showToolbar && (
|
|
388
|
+
<div className={styles.header}>
|
|
389
|
+
<Toolbar
|
|
390
|
+
search={
|
|
391
|
+
suppressSearch
|
|
392
|
+
? undefined
|
|
393
|
+
: {
|
|
394
|
+
value: globalFilter,
|
|
395
|
+
onChange: onGlobalFilterChange,
|
|
396
|
+
loading: search?.loading,
|
|
397
|
+
placeholder: search?.placeholder || t('searchPlaceholder'),
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
className={styles.toolbar}
|
|
401
|
+
onRefresh={onRefresh ? handleOnRefresh : undefined}
|
|
402
|
+
bulkActions={bulkActions}
|
|
403
|
+
selectionMode={rowSelectionProp?.multiRow ? 'multiple' : 'single'}
|
|
404
|
+
checked={table.getIsAllPageRowsSelected()}
|
|
405
|
+
indeterminate={table.getIsSomePageRowsSelected()}
|
|
406
|
+
onCheck={enableSelection ? handleOnToolbarCheck : undefined}
|
|
407
|
+
outline={outline}
|
|
408
|
+
after={
|
|
409
|
+
toolbarAfter || exportSettings ? (
|
|
410
|
+
<>
|
|
411
|
+
{toolbarAfter}
|
|
412
|
+
{exportSettings && (
|
|
413
|
+
<ExportButton
|
|
414
|
+
settings={exportSettings}
|
|
415
|
+
columnDefinitions={columnDefinitions}
|
|
416
|
+
data={data}
|
|
417
|
+
topRows={filteredTopRows}
|
|
418
|
+
centerRows={centerRows}
|
|
419
|
+
/>
|
|
420
|
+
)}
|
|
421
|
+
</>
|
|
422
|
+
) : undefined
|
|
423
|
+
}
|
|
424
|
+
moreActions={moreActions}
|
|
425
|
+
filterRow={columnFilters}
|
|
426
|
+
data-test-id={TEST_IDS.toolbar}
|
|
427
|
+
/>
|
|
428
|
+
</div>
|
|
429
|
+
)}
|
|
430
|
+
|
|
431
|
+
<div className={styles.scrollWrapper} data-outline={outline || undefined}>
|
|
432
|
+
<Scroll size='s' className={styles.table} ref={scrollContainerRef}>
|
|
433
|
+
<div className={styles.tableContent} style={columnSizes.vars}>
|
|
434
|
+
<TableContext.Provider value={{ table }}>
|
|
435
|
+
{loading ? (
|
|
436
|
+
<SkeletonContextProvider loading>
|
|
437
|
+
<HeaderRow groupedColumnOrderState={groupedColumnOrderState} />
|
|
438
|
+
{loadingTableRows.map(row => (
|
|
439
|
+
<BodyRow key={row.id} row={row} groupedColumnOrderState={groupedColumnOrderState} />
|
|
440
|
+
))}
|
|
441
|
+
</SkeletonContextProvider>
|
|
442
|
+
) : (
|
|
443
|
+
<>
|
|
444
|
+
{centerRows.length || filteredTopRows.length ? (
|
|
445
|
+
<HeaderRow
|
|
446
|
+
groupedColumnOrderState={groupedColumnOrderState}
|
|
447
|
+
enableColumnsOrderSortByDrag={enableColumnsOrderSortByDrag}
|
|
448
|
+
/>
|
|
449
|
+
) : null}
|
|
450
|
+
|
|
451
|
+
{filteredTopRows.length ? (
|
|
452
|
+
<div className={styles.topRowWrapper}>
|
|
453
|
+
{filteredTopRows.map(row => (
|
|
454
|
+
<BodyRow
|
|
455
|
+
key={row.id}
|
|
456
|
+
row={row}
|
|
457
|
+
onRowClick={onRowClick}
|
|
458
|
+
groupedColumnOrderState={groupedColumnOrderState}
|
|
459
|
+
/>
|
|
460
|
+
))}
|
|
461
|
+
</div>
|
|
462
|
+
) : null}
|
|
463
|
+
|
|
464
|
+
{centerRows.map(row => (
|
|
465
|
+
<BodyRow
|
|
466
|
+
key={row.id}
|
|
467
|
+
row={row}
|
|
468
|
+
onRowClick={onRowClick}
|
|
469
|
+
groupedColumnOrderState={groupedColumnOrderState}
|
|
470
|
+
enableColumnsOrderSortByDrag={enableColumnsOrderSortByDrag}
|
|
471
|
+
/>
|
|
472
|
+
))}
|
|
473
|
+
|
|
474
|
+
<TableEmptyState
|
|
475
|
+
emptyStates={emptyStates}
|
|
476
|
+
dataError={dataError}
|
|
477
|
+
dataFiltered={dataFiltered || Boolean(table.getState().globalFilter)}
|
|
478
|
+
tableRowsLength={tableRows.length + filteredTopRows.length}
|
|
402
479
|
/>
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
data-test-id={TEST_IDS.toolbar}
|
|
410
|
-
/>
|
|
480
|
+
</>
|
|
481
|
+
)}
|
|
482
|
+
</TableContext.Provider>
|
|
483
|
+
</div>
|
|
484
|
+
<div className={styles.scrollStub} ref={scrollRef as RefObject<HTMLDivElement>} />
|
|
485
|
+
</Scroll>
|
|
411
486
|
</div>
|
|
412
|
-
)}
|
|
413
|
-
|
|
414
|
-
<div className={styles.scrollWrapper} data-outline={outline || undefined}>
|
|
415
|
-
<Scroll size='s' className={styles.table} ref={scrollContainerRef}>
|
|
416
|
-
<div className={styles.tableContent} style={columnSizeVars}>
|
|
417
|
-
<TableContext.Provider value={{ table }}>
|
|
418
|
-
{loading ? (
|
|
419
|
-
<SkeletonContextProvider loading>
|
|
420
|
-
<HeaderRow />
|
|
421
|
-
{loadingTableRows.map(row => (
|
|
422
|
-
<BodyRow key={row.id} row={row} />
|
|
423
|
-
))}
|
|
424
|
-
</SkeletonContextProvider>
|
|
425
|
-
) : (
|
|
426
|
-
<>
|
|
427
|
-
{centerRows.length || filteredTopRows.length ? <HeaderRow /> : null}
|
|
428
|
-
|
|
429
|
-
{filteredTopRows.length ? (
|
|
430
|
-
<div className={styles.topRowWrapper}>
|
|
431
|
-
{filteredTopRows.map(row => (
|
|
432
|
-
<BodyRow key={row.id} row={row} onRowClick={onRowClick} />
|
|
433
|
-
))}
|
|
434
|
-
</div>
|
|
435
|
-
) : null}
|
|
436
|
-
|
|
437
|
-
{centerRows.map(row => (
|
|
438
|
-
<BodyRow key={row.id} row={row} onRowClick={onRowClick} />
|
|
439
|
-
))}
|
|
440
|
-
|
|
441
|
-
<TableEmptyState
|
|
442
|
-
emptyStates={emptyStates}
|
|
443
|
-
dataError={dataError}
|
|
444
|
-
dataFiltered={dataFiltered || Boolean(table.getState().globalFilter)}
|
|
445
|
-
tableRowsLength={tableRows.length + filteredTopRows.length}
|
|
446
|
-
/>
|
|
447
|
-
</>
|
|
448
|
-
)}
|
|
449
|
-
</TableContext.Provider>
|
|
450
|
-
</div>
|
|
451
|
-
<div className={styles.scrollStub} ref={scrollRef as RefObject<HTMLDivElement>} />
|
|
452
|
-
</Scroll>
|
|
453
|
-
</div>
|
|
454
487
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
488
|
+
{!suppressPagination && (
|
|
489
|
+
<TablePagination
|
|
490
|
+
table={table}
|
|
491
|
+
options={paginationProp?.options}
|
|
492
|
+
optionsLabel={paginationProp?.optionsLabel}
|
|
493
|
+
pageCount={pageCount}
|
|
494
|
+
optionsRender={paginationProp?.optionsRender}
|
|
495
|
+
/>
|
|
496
|
+
)}
|
|
497
|
+
</div>
|
|
498
|
+
</CellAutoResizeContext.Provider>
|
|
499
|
+
</DndContext>
|
|
466
500
|
);
|
|
467
501
|
}
|
|
468
502
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { DragEndEvent, KeyboardSensor, MouseSensor, TouchSensor, useSensor, useSensors } from '@dnd-kit/core';
|
|
2
|
+
import { arrayMove } from '@dnd-kit/sortable';
|
|
3
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { UNDRAGGABLE_COLUMNS } from '../../../constants';
|
|
6
|
+
import { ColumnDefinition, GroupedColumnOrderState } from '../../../types';
|
|
7
|
+
|
|
8
|
+
type PinnedColumnsMap = Record<string, keyof GroupedColumnOrderState>;
|
|
9
|
+
|
|
10
|
+
function preparePinnedColumnsMap<TData extends object>(tableColumns: ColumnDefinition<TData>[]): PinnedColumnsMap {
|
|
11
|
+
return tableColumns.reduce((accMap, columnDefinition) => {
|
|
12
|
+
switch (columnDefinition.pinned) {
|
|
13
|
+
case 'left':
|
|
14
|
+
accMap[columnDefinition.id] = 'leftPinned';
|
|
15
|
+
break;
|
|
16
|
+
|
|
17
|
+
case 'right':
|
|
18
|
+
accMap[columnDefinition.id] = 'rightPinned';
|
|
19
|
+
break;
|
|
20
|
+
|
|
21
|
+
default:
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return accMap;
|
|
25
|
+
}, {} as PinnedColumnsMap);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function prepareInitialState<TData extends object>(tableColumns: ColumnDefinition<TData>[]) {
|
|
29
|
+
return tableColumns.map(c => c.id as string).filter(id => !UNDRAGGABLE_COLUMNS.includes(id));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function prepareGroupedColumnOrderState(
|
|
33
|
+
columnOrder: string[],
|
|
34
|
+
pinnedColumnsMap: PinnedColumnsMap,
|
|
35
|
+
): GroupedColumnOrderState {
|
|
36
|
+
return columnOrder.reduce(
|
|
37
|
+
(accState: GroupedColumnOrderState, columnId) => {
|
|
38
|
+
if (UNDRAGGABLE_COLUMNS.includes(columnId)) {
|
|
39
|
+
return accState;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const groupName = pinnedColumnsMap[columnId] ?? 'unpinned';
|
|
43
|
+
|
|
44
|
+
accState[groupName].push(columnId);
|
|
45
|
+
|
|
46
|
+
return accState;
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
leftPinned: [],
|
|
50
|
+
unpinned: [],
|
|
51
|
+
rightPinned: [],
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function useColumnOrderByDrag<TData extends object>(tableColumns: ColumnDefinition<TData>[]) {
|
|
57
|
+
const [columnOrder, setColumnOrder] = useState<string[]>(() => prepareInitialState(tableColumns));
|
|
58
|
+
|
|
59
|
+
const pinnedColumnsMap = useMemo(() => preparePinnedColumnsMap(tableColumns), [tableColumns]);
|
|
60
|
+
|
|
61
|
+
const groupedColumnOrderState = useMemo(
|
|
62
|
+
() => prepareGroupedColumnOrderState(columnOrder, pinnedColumnsMap),
|
|
63
|
+
[columnOrder, pinnedColumnsMap],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const handleDragEnd = useCallback(
|
|
67
|
+
({ active, over }: DragEndEvent) => {
|
|
68
|
+
if (active && over && active.id !== over.id) {
|
|
69
|
+
if (UNDRAGGABLE_COLUMNS.includes(over.id.toString())) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const activeGroup = pinnedColumnsMap[active.id.toString()];
|
|
74
|
+
const overGroup = pinnedColumnsMap[over.id.toString()];
|
|
75
|
+
if (activeGroup !== overGroup) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
setColumnOrder(columnOrder => {
|
|
80
|
+
const oldIndex = columnOrder.indexOf(active.id.toString());
|
|
81
|
+
const newIndex = columnOrder.indexOf(over.id.toString());
|
|
82
|
+
return arrayMove(columnOrder, oldIndex, newIndex);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
[pinnedColumnsMap],
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const sensors = useSensors(useSensor(MouseSensor, {}), useSensor(TouchSensor, {}), useSensor(KeyboardSensor, {}));
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
columnOrder,
|
|
93
|
+
setColumnOrder,
|
|
94
|
+
|
|
95
|
+
groupedColumnOrderState,
|
|
96
|
+
|
|
97
|
+
handleDragEnd,
|
|
98
|
+
sensors,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -7,10 +7,15 @@ export function getCurrentlyConfiguredHeaderWidth(id: string): number {
|
|
|
7
7
|
'[data-test-id="table__header-cell-resize-handle-moving-part"]',
|
|
8
8
|
);
|
|
9
9
|
|
|
10
|
-
if (cell
|
|
10
|
+
if (cell) {
|
|
11
11
|
const { width } = cell.getBoundingClientRect();
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
|
|
13
|
+
if (resizeHandler) {
|
|
14
|
+
const offset = parseInt(resizeHandler.style.getPropertyValue('--offset'));
|
|
15
|
+
return width + offset;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return width;
|
|
14
19
|
}
|
|
15
20
|
}
|
|
16
21
|
|
package/src/components/types.ts
CHANGED
|
@@ -45,6 +45,9 @@ export type TableProps<
|
|
|
45
45
|
onChange?(state: SortingState): void;
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
/** Включение сортировки порядка столбцов вручную перетаскиванием */
|
|
49
|
+
enableColumnsOrderSortByDrag?: boolean;
|
|
50
|
+
|
|
48
51
|
/** Параметр отвечает за общие настройки раскрывающихся строк*/
|
|
49
52
|
expanding?: {
|
|
50
53
|
/** Метод отвечает за получение дочерних строк*/
|
package/src/constants.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useSortable } from '@dnd-kit/sortable';
|
|
1
2
|
import { Cell as TableCell, flexRender } from '@tanstack/react-table';
|
|
2
3
|
import cn from 'classnames';
|
|
3
4
|
|
|
@@ -9,16 +10,22 @@ import styles from './styles.module.scss';
|
|
|
9
10
|
|
|
10
11
|
type BodyCellProps<TData> = Omit<CellProps, 'style' | 'children'> & {
|
|
11
12
|
cell: TableCell<TData, unknown>;
|
|
13
|
+
isDraggable?: boolean;
|
|
12
14
|
};
|
|
13
15
|
|
|
14
|
-
export function BodyCell<TData>({ cell, className, ...props }: BodyCellProps<TData>) {
|
|
16
|
+
export function BodyCell<TData>({ cell, className, isDraggable, ...props }: BodyCellProps<TData>) {
|
|
15
17
|
const columnDef: ColumnDefinition<TData> = cell.column.columnDef;
|
|
16
18
|
|
|
17
|
-
const style = useCellSizes(cell);
|
|
19
|
+
const style = useCellSizes(cell, { isDraggable });
|
|
20
|
+
|
|
21
|
+
const { setNodeRef } = useSortable({
|
|
22
|
+
id: cell.column.id,
|
|
23
|
+
});
|
|
18
24
|
|
|
19
25
|
return (
|
|
20
26
|
<Cell
|
|
21
27
|
{...props}
|
|
28
|
+
ref={setNodeRef}
|
|
22
29
|
style={style}
|
|
23
30
|
className={cn(styles.tableBodyCell, className, columnDef.cellClassName)}
|
|
24
31
|
data-align={columnDef.align}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useSortable } from '@dnd-kit/sortable';
|
|
2
|
+
import { CSSProperties } from 'react';
|
|
3
|
+
|
|
4
|
+
import { KebabSVG } from '@snack-uikit/icons';
|
|
5
|
+
|
|
6
|
+
import styles from './styles.module.scss';
|
|
7
|
+
|
|
8
|
+
export type DragHandleProps = Pick<ReturnType<typeof useSortable>, 'attributes' | 'listeners'> & {
|
|
9
|
+
style?: CSSProperties;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function DragHandle({ attributes, listeners }: DragHandleProps) {
|
|
13
|
+
return (
|
|
14
|
+
<div className={styles.dragHandle} {...attributes} {...listeners}>
|
|
15
|
+
<KebabSVG className={styles.dragIcon} />
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import { useSortable } from '@dnd-kit/sortable';
|
|
1
2
|
import { flexRender, Header } from '@tanstack/react-table';
|
|
2
3
|
import cn from 'classnames';
|
|
3
4
|
import { MouseEvent, useRef } from 'react';
|
|
4
5
|
|
|
5
6
|
import { TruncateString } from '@snack-uikit/truncate-string';
|
|
6
7
|
|
|
7
|
-
import { TEST_IDS } from '../../../constants';
|
|
8
|
+
import { TEST_IDS, UNDRAGGABLE_COLUMNS } from '../../../constants';
|
|
8
9
|
import { ColumnDefinition, ColumnPinPosition } from '../../../types';
|
|
9
10
|
import { useCellSizes } from '../../hooks';
|
|
10
11
|
import { Cell, CellProps } from '../Cell';
|
|
12
|
+
import { DragHandle } from './DragHandle';
|
|
11
13
|
import { getSortingIcon } from './helpers';
|
|
12
14
|
import { ResizeHandle } from './ResizeHandle';
|
|
13
15
|
import styles from './styles.module.scss';
|
|
@@ -15,9 +17,10 @@ import styles from './styles.module.scss';
|
|
|
15
17
|
type HeaderCellProps<TData> = Omit<CellProps, 'align' | 'children' | 'onClick' | 'style'> & {
|
|
16
18
|
header: Header<TData, unknown>;
|
|
17
19
|
pinPosition?: ColumnPinPosition;
|
|
20
|
+
isDraggable?: boolean;
|
|
18
21
|
};
|
|
19
22
|
|
|
20
|
-
export function HeaderCell<TData>({ header, pinPosition, className }: HeaderCellProps<TData>) {
|
|
23
|
+
export function HeaderCell<TData>({ header, pinPosition, isDraggable, className }: HeaderCellProps<TData>) {
|
|
21
24
|
const cellRef = useRef<HTMLDivElement>(null);
|
|
22
25
|
const isSortable = header.column.getCanSort();
|
|
23
26
|
const isResizable = header.column.getCanResize();
|
|
@@ -31,7 +34,11 @@ export function HeaderCell<TData>({ header, pinPosition, className }: HeaderCell
|
|
|
31
34
|
|
|
32
35
|
const columnDef: ColumnDefinition<TData> = header.column.columnDef;
|
|
33
36
|
|
|
34
|
-
const style = useCellSizes(header);
|
|
37
|
+
const style = useCellSizes(header, { isDraggable });
|
|
38
|
+
|
|
39
|
+
const { attributes, listeners, setNodeRef } = useSortable({
|
|
40
|
+
id: header.column.id,
|
|
41
|
+
});
|
|
35
42
|
|
|
36
43
|
const sortingHandler = (e: MouseEvent<HTMLDivElement>) => {
|
|
37
44
|
if (isSomeColumnResizing) return;
|
|
@@ -39,11 +46,14 @@ export function HeaderCell<TData>({ header, pinPosition, className }: HeaderCell
|
|
|
39
46
|
return header.column.getToggleSortingHandler()?.(e);
|
|
40
47
|
};
|
|
41
48
|
|
|
49
|
+
const isAvailableForDrag = !UNDRAGGABLE_COLUMNS.includes(header.column.id);
|
|
50
|
+
|
|
42
51
|
return (
|
|
43
52
|
<Cell
|
|
44
53
|
style={style}
|
|
45
54
|
onClick={sortingHandler}
|
|
46
55
|
data-sortable={isSortable || undefined}
|
|
56
|
+
data-draggable={isDraggable || undefined}
|
|
47
57
|
data-no-padding={columnDef.noHeaderCellPadding || undefined}
|
|
48
58
|
data-no-offset={columnDef.noHeaderCellBorderOffset || undefined}
|
|
49
59
|
data-test-id={TEST_IDS.headerCell}
|
|
@@ -53,7 +63,10 @@ export function HeaderCell<TData>({ header, pinPosition, className }: HeaderCell
|
|
|
53
63
|
data-pin-position={pinPosition || undefined}
|
|
54
64
|
role='columnheader'
|
|
55
65
|
className={cn(styles.tableHeaderCell, className, columnDef.headerClassName)}
|
|
56
|
-
ref={
|
|
66
|
+
ref={element => {
|
|
67
|
+
setNodeRef(element);
|
|
68
|
+
return cellRef;
|
|
69
|
+
}}
|
|
57
70
|
>
|
|
58
71
|
<div className={styles.tableHeaderCellMain}>
|
|
59
72
|
{columnDef.header && (
|
|
@@ -73,6 +86,8 @@ export function HeaderCell<TData>({ header, pinPosition, className }: HeaderCell
|
|
|
73
86
|
)}
|
|
74
87
|
</div>
|
|
75
88
|
|
|
89
|
+
{isAvailableForDrag && Boolean(isDraggable) && <DragHandle attributes={attributes} listeners={listeners} />}
|
|
90
|
+
|
|
76
91
|
{Boolean(isResizable) && <ResizeHandle header={header} cellRef={cellRef} />}
|
|
77
92
|
</Cell>
|
|
78
93
|
);
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
left: 0;
|
|
59
59
|
transform: translateX(-50%);
|
|
60
60
|
|
|
61
|
-
width: calc(100% + styles-tokens-table.$dimension-4m);
|
|
61
|
+
width: calc(100% + #{styles-tokens-table.$dimension-4m});
|
|
62
62
|
height: 100%;
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
left: 50%;
|
|
87
87
|
transform: translateX(-50%);
|
|
88
88
|
|
|
89
|
-
width: calc(100% - styles-tokens-table.$space-table-head-separator-padding * 2);
|
|
89
|
+
width: calc(100% - #{styles-tokens-table.$space-table-head-separator-padding} * 2);
|
|
90
90
|
height: styles-tokens-table.$border-width-table;
|
|
91
91
|
|
|
92
92
|
background-color: styles-tokens-table.$sys-neutral-decor-default;
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
&::after {
|
|
107
107
|
left: 0;
|
|
108
108
|
transform: none;
|
|
109
|
-
width: calc(100% - styles-tokens-table.$space-table-head-separator-padding);
|
|
109
|
+
width: calc(100% - #{styles-tokens-table.$space-table-head-separator-padding});
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -136,7 +136,7 @@
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
.tableHeaderResizeIndicator {
|
|
139
|
-
right: calc(styles-tokens-table.$dimension-1m / 2);
|
|
139
|
+
right: calc(#{styles-tokens-table.$dimension-1m} / 2);
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
}
|
|
@@ -173,3 +173,14 @@
|
|
|
173
173
|
height: styles-tokens-table.simple-var(styles-tokens-element.$icon-xs) !important; /* stylelint-disable-line declaration-no-important */
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
|
+
|
|
177
|
+
.dragHandle {
|
|
178
|
+
cursor: pointer;
|
|
179
|
+
display: flex;
|
|
180
|
+
flex-direction: row;
|
|
181
|
+
gap: 2px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.dragIcon {
|
|
185
|
+
color: styles-tokens-table.simple-var(styles-tokens-table.$sys-neutral-text-light);
|
|
186
|
+
}
|