@snack-uikit/table 0.30.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/README.md +2 -0
- package/dist/cjs/components/Table/Table.d.ts +1 -1
- package/dist/cjs/components/Table/Table.js +116 -89
- 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/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 +17 -12
- 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/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 +133 -102
- package/src/components/Table/hooks/index.ts +1 -0
- package/src/components/Table/hooks/useColumnOrderByDrag.ts +100 -0
- 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 +11 -0
- package/src/helperComponents/Rows/BodyRow.tsx +36 -9
- package/src/helperComponents/Rows/HeaderRow.tsx +51 -18
- package/src/helperComponents/hooks.ts +78 -15
- package/src/types.ts +6 -0
|
@@ -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
|
+
}
|
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
|
);
|
|
@@ -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
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { horizontalListSortingStrategy, SortableContext } from '@dnd-kit/sortable';
|
|
1
2
|
import { Row as TableRow } from '@tanstack/react-table';
|
|
2
3
|
import { MouseEvent, useState } from 'react';
|
|
3
4
|
|
|
4
5
|
import { COLUMN_PIN_POSITION, TEST_IDS } from '../../constants';
|
|
6
|
+
import { GroupedColumnOrderState } from '../../types';
|
|
5
7
|
import { BodyCell } from '../Cells';
|
|
6
8
|
import { RowContext } from '../contexts';
|
|
7
9
|
import { useRowCells } from '../hooks';
|
|
@@ -21,10 +23,17 @@ export type RowClickHandler<TData> = (e: MouseEvent<HTMLDivElement>, row: RowInf
|
|
|
21
23
|
export type BodyRowProps<TData> = {
|
|
22
24
|
row: TableRow<TData>;
|
|
23
25
|
onRowClick?: RowClickHandler<TData>;
|
|
26
|
+
groupedColumnOrderState: GroupedColumnOrderState;
|
|
27
|
+
enableColumnsOrderSortByDrag?: boolean;
|
|
24
28
|
};
|
|
25
29
|
|
|
26
|
-
export function BodyRow<TData>({
|
|
27
|
-
|
|
30
|
+
export function BodyRow<TData>({
|
|
31
|
+
row,
|
|
32
|
+
onRowClick,
|
|
33
|
+
groupedColumnOrderState,
|
|
34
|
+
enableColumnsOrderSortByDrag,
|
|
35
|
+
}: BodyRowProps<TData>) {
|
|
36
|
+
const { leftPinned, rightPinned, unpinned } = useRowCells(row, groupedColumnOrderState);
|
|
28
37
|
|
|
29
38
|
const [dropListOpened, setDropListOpen] = useState(false);
|
|
30
39
|
|
|
@@ -57,22 +66,40 @@ export function BodyRow<TData>({ row, onRowClick }: BodyRowProps<TData>) {
|
|
|
57
66
|
data-row-id={row.id}
|
|
58
67
|
className={styles.bodyRow}
|
|
59
68
|
>
|
|
60
|
-
{
|
|
69
|
+
{leftPinned && (
|
|
61
70
|
<PinnedCells position={COLUMN_PIN_POSITION.Left}>
|
|
62
|
-
{
|
|
63
|
-
<
|
|
71
|
+
{leftPinned.map(cell => (
|
|
72
|
+
<SortableContext
|
|
73
|
+
key={cell.id}
|
|
74
|
+
items={groupedColumnOrderState.leftPinned}
|
|
75
|
+
strategy={horizontalListSortingStrategy}
|
|
76
|
+
>
|
|
77
|
+
<BodyCell key={cell.id} cell={cell} isDraggable={enableColumnsOrderSortByDrag} />
|
|
78
|
+
</SortableContext>
|
|
64
79
|
))}
|
|
65
80
|
</PinnedCells>
|
|
66
81
|
)}
|
|
67
82
|
|
|
68
83
|
{unpinned.map(cell => (
|
|
69
|
-
<
|
|
84
|
+
<SortableContext
|
|
85
|
+
key={cell.id}
|
|
86
|
+
items={groupedColumnOrderState.unpinned}
|
|
87
|
+
strategy={horizontalListSortingStrategy}
|
|
88
|
+
>
|
|
89
|
+
<BodyCell key={cell.id} cell={cell} isDraggable={enableColumnsOrderSortByDrag} />
|
|
90
|
+
</SortableContext>
|
|
70
91
|
))}
|
|
71
92
|
|
|
72
|
-
{
|
|
93
|
+
{rightPinned && (
|
|
73
94
|
<PinnedCells position={COLUMN_PIN_POSITION.Right}>
|
|
74
|
-
{
|
|
75
|
-
<
|
|
95
|
+
{rightPinned.map(cell => (
|
|
96
|
+
<SortableContext
|
|
97
|
+
key={cell.id}
|
|
98
|
+
items={groupedColumnOrderState.rightPinned}
|
|
99
|
+
strategy={horizontalListSortingStrategy}
|
|
100
|
+
>
|
|
101
|
+
<BodyCell key={cell.id} cell={cell} isDraggable={enableColumnsOrderSortByDrag} />
|
|
102
|
+
</SortableContext>
|
|
76
103
|
))}
|
|
77
104
|
</PinnedCells>
|
|
78
105
|
)}
|
|
@@ -1,37 +1,70 @@
|
|
|
1
|
+
import { horizontalListSortingStrategy, SortableContext } from '@dnd-kit/sortable';
|
|
2
|
+
|
|
1
3
|
import { COLUMN_PIN_POSITION, TEST_IDS } from '../../constants';
|
|
4
|
+
import { GroupedColumnOrderState } from '../../types';
|
|
2
5
|
import { HeaderCell } from '../Cells';
|
|
3
6
|
import { useHeaderGroups } from '../hooks';
|
|
4
7
|
import { PinnedCells } from './PinnedCells';
|
|
5
8
|
import { Row } from './Row';
|
|
6
9
|
import styles from './styles.module.scss';
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
type Props = {
|
|
12
|
+
groupedColumnOrderState: GroupedColumnOrderState;
|
|
13
|
+
enableColumnsOrderSortByDrag?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function HeaderRow({ groupedColumnOrderState, enableColumnsOrderSortByDrag }: Props) {
|
|
17
|
+
const { leftPinned, unpinned, rightPinned } = useHeaderGroups(groupedColumnOrderState);
|
|
10
18
|
|
|
11
19
|
return (
|
|
12
20
|
<Row className={styles.tableHeader} data-test-id={TEST_IDS.headerRow}>
|
|
13
21
|
{leftPinned && (
|
|
14
|
-
<
|
|
15
|
-
{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
<SortableContext items={groupedColumnOrderState.leftPinned} strategy={horizontalListSortingStrategy}>
|
|
23
|
+
<PinnedCells position={COLUMN_PIN_POSITION.Left}>
|
|
24
|
+
{leftPinned.map(headerGroup =>
|
|
25
|
+
headerGroup.headers.map(header =>
|
|
26
|
+
header.isPlaceholder ? null : (
|
|
27
|
+
<HeaderCell
|
|
28
|
+
key={header.id}
|
|
29
|
+
header={header}
|
|
30
|
+
isDraggable={enableColumnsOrderSortByDrag && groupedColumnOrderState.leftPinned.length > 1}
|
|
31
|
+
/>
|
|
32
|
+
),
|
|
33
|
+
),
|
|
34
|
+
)}
|
|
35
|
+
</PinnedCells>
|
|
36
|
+
</SortableContext>
|
|
21
37
|
)}
|
|
22
38
|
|
|
23
|
-
|
|
39
|
+
<SortableContext items={groupedColumnOrderState.unpinned} strategy={horizontalListSortingStrategy}>
|
|
40
|
+
{unpinned.map(headerGroup =>
|
|
41
|
+
headerGroup.headers.map(header => (
|
|
42
|
+
<HeaderCell
|
|
43
|
+
key={header.id}
|
|
44
|
+
header={header}
|
|
45
|
+
isDraggable={enableColumnsOrderSortByDrag && groupedColumnOrderState.unpinned.length > 1}
|
|
46
|
+
/>
|
|
47
|
+
)),
|
|
48
|
+
)}
|
|
49
|
+
</SortableContext>
|
|
24
50
|
|
|
25
51
|
{rightPinned && (
|
|
26
|
-
<
|
|
27
|
-
{
|
|
28
|
-
|
|
29
|
-
header
|
|
30
|
-
|
|
52
|
+
<SortableContext items={groupedColumnOrderState.rightPinned} strategy={horizontalListSortingStrategy}>
|
|
53
|
+
<PinnedCells position={COLUMN_PIN_POSITION.Right}>
|
|
54
|
+
{rightPinned.map(headerGroup =>
|
|
55
|
+
headerGroup.headers.map(header =>
|
|
56
|
+
header.isPlaceholder ? null : (
|
|
57
|
+
<HeaderCell
|
|
58
|
+
key={header.id}
|
|
59
|
+
header={header}
|
|
60
|
+
pinPosition={COLUMN_PIN_POSITION.Right}
|
|
61
|
+
isDraggable={enableColumnsOrderSortByDrag && groupedColumnOrderState.rightPinned.length > 1}
|
|
62
|
+
/>
|
|
63
|
+
),
|
|
31
64
|
),
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
</
|
|
65
|
+
)}
|
|
66
|
+
</PinnedCells>
|
|
67
|
+
</SortableContext>
|
|
35
68
|
)}
|
|
36
69
|
</Row>
|
|
37
70
|
);
|
|
@@ -1,17 +1,36 @@
|
|
|
1
|
+
import { useSortable } from '@dnd-kit/sortable';
|
|
2
|
+
import { CSS } from '@dnd-kit/utilities';
|
|
1
3
|
import { Cell, Header, HeaderGroup, Row } from '@tanstack/react-table';
|
|
2
|
-
import { useMemo } from 'react';
|
|
4
|
+
import { CSSProperties, useMemo } from 'react';
|
|
3
5
|
|
|
6
|
+
import { GroupedColumnOrderState } from '../types';
|
|
4
7
|
import { useTableContext } from './contexts';
|
|
5
8
|
|
|
6
9
|
function hasHeaders<TData>(groups: HeaderGroup<TData>[]) {
|
|
7
10
|
return groups.some(group => group.headers.length);
|
|
8
11
|
}
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
const sortHeaderGroup = (groups: HeaderGroup<unknown>[], groupOrder: string[]) =>
|
|
14
|
+
groups.map(group => ({
|
|
15
|
+
...group,
|
|
16
|
+
headers: group.headers.sort((a, b) => {
|
|
17
|
+
const indexA = groupOrder.findIndex(columnId => columnId === a.id);
|
|
18
|
+
const indexB = groupOrder.findIndex(columnId => columnId === b.id);
|
|
19
|
+
|
|
20
|
+
if (indexA > -1 && indexB > -1) {
|
|
21
|
+
return indexA - indexB;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return 0;
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
export function useHeaderGroups(groupedColumnOrderState: GroupedColumnOrderState) {
|
|
11
29
|
const { table } = useTableContext();
|
|
12
30
|
|
|
13
31
|
const columnDefs = table._getColumnDefs();
|
|
14
32
|
const pinEnabled = table.getIsSomeColumnsPinned();
|
|
33
|
+
const { columnOrder } = table.getState();
|
|
15
34
|
|
|
16
35
|
return useMemo(() => {
|
|
17
36
|
if (!pinEnabled) {
|
|
@@ -24,20 +43,34 @@ export function useHeaderGroups() {
|
|
|
24
43
|
const right = table.getRightHeaderGroups();
|
|
25
44
|
|
|
26
45
|
return {
|
|
27
|
-
leftPinned: hasHeaders(left) ? left : undefined,
|
|
28
|
-
rightPinned: hasHeaders(right) ? right : undefined,
|
|
46
|
+
leftPinned: hasHeaders(left) ? sortHeaderGroup(left, groupedColumnOrderState.leftPinned) : undefined,
|
|
47
|
+
rightPinned: hasHeaders(right) ? sortHeaderGroup(right, groupedColumnOrderState.rightPinned) : undefined,
|
|
29
48
|
unpinned: table.getCenterHeaderGroups(),
|
|
30
49
|
};
|
|
31
50
|
// need to rebuild if columnDefinitions has changed
|
|
32
51
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
33
|
-
}, [table, pinEnabled, columnDefs]);
|
|
52
|
+
}, [table, pinEnabled, columnDefs, columnOrder, groupedColumnOrderState]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function sortPinnedColumnsByOrderState<TData>(groupOrder: string[]) {
|
|
56
|
+
return (a: Cell<TData, unknown>, b: Cell<TData, unknown>) => {
|
|
57
|
+
const indexA = groupOrder.findIndex(columnId => columnId === a.column.id);
|
|
58
|
+
const indexB = groupOrder.findIndex(columnId => columnId === b.column.id);
|
|
59
|
+
|
|
60
|
+
if (indexA > -1 && indexB > -1) {
|
|
61
|
+
return indexA - indexB;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return 0;
|
|
65
|
+
};
|
|
34
66
|
}
|
|
35
67
|
|
|
36
|
-
export function useRowCells<TData>(row: Row<TData
|
|
68
|
+
export function useRowCells<TData>(row: Row<TData>, groupedColumnOrderState: GroupedColumnOrderState) {
|
|
37
69
|
const { table } = useTableContext();
|
|
38
70
|
|
|
39
71
|
const pinEnabled = table.getIsSomeColumnsPinned();
|
|
40
72
|
const columnDefs = table._getColumnDefs();
|
|
73
|
+
const { columnOrder } = table.getState();
|
|
41
74
|
|
|
42
75
|
return useMemo(() => {
|
|
43
76
|
if (!pinEnabled) {
|
|
@@ -50,30 +83,60 @@ export function useRowCells<TData>(row: Row<TData>) {
|
|
|
50
83
|
const right = row.getRightVisibleCells();
|
|
51
84
|
|
|
52
85
|
return {
|
|
53
|
-
|
|
54
|
-
|
|
86
|
+
leftPinned: left.length
|
|
87
|
+
? left.slice().sort(sortPinnedColumnsByOrderState(groupedColumnOrderState.leftPinned))
|
|
88
|
+
: undefined,
|
|
89
|
+
rightPinned: right.length
|
|
90
|
+
? right.slice().sort(sortPinnedColumnsByOrderState(groupedColumnOrderState.rightPinned))
|
|
91
|
+
: undefined,
|
|
55
92
|
unpinned: row.getCenterVisibleCells(),
|
|
56
93
|
};
|
|
57
94
|
// need to rebuild if columnDefinitions has changed
|
|
58
95
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
59
|
-
}, [row, pinEnabled, columnDefs]);
|
|
96
|
+
}, [row, pinEnabled, columnDefs, columnOrder, groupedColumnOrderState]);
|
|
60
97
|
}
|
|
61
98
|
|
|
62
|
-
|
|
99
|
+
type CellSizesOptions = {
|
|
100
|
+
isDraggable?: boolean;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export function useCellSizes<TData>(
|
|
104
|
+
element: Cell<TData, unknown> | Header<TData, unknown>,
|
|
105
|
+
options?: CellSizesOptions,
|
|
106
|
+
) {
|
|
63
107
|
const column = element.column;
|
|
64
108
|
|
|
109
|
+
const { isDragging, transform } = useSortable({
|
|
110
|
+
id: column.id,
|
|
111
|
+
});
|
|
112
|
+
|
|
65
113
|
const minWidth = column.columnDef.minSize;
|
|
66
114
|
const maxWidth = column.columnDef.maxSize;
|
|
67
115
|
const width = `var(--table-column-${column.id}-size)`;
|
|
68
116
|
const flexShrink = `var(--table-column-${column.id}-flex)`;
|
|
69
117
|
|
|
70
|
-
|
|
71
|
-
|
|
118
|
+
const isHeaderCell = 'headerGroup' in element;
|
|
119
|
+
|
|
120
|
+
return useMemo(() => {
|
|
121
|
+
const styles: CSSProperties = {
|
|
72
122
|
minWidth,
|
|
73
123
|
width,
|
|
74
124
|
maxWidth,
|
|
75
125
|
flexShrink,
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
if (options?.isDraggable) {
|
|
129
|
+
styles.opacity = isDragging ? 0.8 : 1;
|
|
130
|
+
styles.position = 'relative';
|
|
131
|
+
styles.transform = CSS.Translate.toString(transform);
|
|
132
|
+
styles.transition = 'width transform 0.2s ease-in-out';
|
|
133
|
+
styles.zIndex = isDragging ? 1 : 0;
|
|
134
|
+
|
|
135
|
+
if (isHeaderCell) {
|
|
136
|
+
styles.whiteSpace = 'nowrap';
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return styles;
|
|
141
|
+
}, [options?.isDraggable, flexShrink, isDragging, isHeaderCell, maxWidth, minWidth, transform, width]);
|
|
79
142
|
}
|
package/src/types.ts
CHANGED
|
@@ -66,6 +66,12 @@ type PinnedColumnDefinition<TData> = BaseColumnDefinition<TData> & {
|
|
|
66
66
|
|
|
67
67
|
export type ColumnDefinition<TData> = NormalColumnDefinition<TData> | PinnedColumnDefinition<TData>;
|
|
68
68
|
|
|
69
|
+
export type GroupedColumnOrderState = {
|
|
70
|
+
leftPinned: string[];
|
|
71
|
+
rightPinned: string[];
|
|
72
|
+
unpinned: string[];
|
|
73
|
+
};
|
|
74
|
+
|
|
69
75
|
export type {
|
|
70
76
|
RowActionsColumnDefProps,
|
|
71
77
|
StatusColumnDefinitionProps,
|