@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.
Files changed (61) hide show
  1. package/README.md +2 -0
  2. package/dist/cjs/components/Table/Table.d.ts +1 -1
  3. package/dist/cjs/components/Table/Table.js +116 -89
  4. package/dist/cjs/components/Table/hooks/index.d.ts +1 -0
  5. package/dist/cjs/components/Table/hooks/index.js +2 -1
  6. package/dist/cjs/components/Table/hooks/useColumnOrderByDrag.d.ts +9 -0
  7. package/dist/cjs/components/Table/hooks/useColumnOrderByDrag.js +76 -0
  8. package/dist/cjs/components/types.d.ts +2 -0
  9. package/dist/cjs/constants.d.ts +1 -0
  10. package/dist/cjs/constants.js +3 -2
  11. package/dist/cjs/helperComponents/Cells/BodyCell/BodyCell.d.ts +2 -1
  12. package/dist/cjs/helperComponents/Cells/BodyCell/BodyCell.js +13 -3
  13. package/dist/cjs/helperComponents/Cells/HeaderCell/DragHandle.d.ts +6 -0
  14. package/dist/cjs/helperComponents/Cells/HeaderCell/DragHandle.js +27 -0
  15. package/dist/cjs/helperComponents/Cells/HeaderCell/HeaderCell.d.ts +2 -1
  16. package/dist/cjs/helperComponents/Cells/HeaderCell/HeaderCell.js +22 -2
  17. package/dist/cjs/helperComponents/Cells/HeaderCell/styles.module.css +11 -0
  18. package/dist/cjs/helperComponents/Rows/BodyRow.d.ts +4 -1
  19. package/dist/cjs/helperComponents/Rows/BodyRow.js +30 -12
  20. package/dist/cjs/helperComponents/Rows/HeaderRow.d.ts +7 -1
  21. package/dist/cjs/helperComponents/Rows/HeaderRow.js +33 -13
  22. package/dist/cjs/helperComponents/hooks.d.ts +22 -13
  23. package/dist/cjs/helperComponents/hooks.js +63 -15
  24. package/dist/cjs/types.d.ts +5 -0
  25. package/dist/esm/components/Table/Table.d.ts +1 -1
  26. package/dist/esm/components/Table/Table.js +17 -12
  27. package/dist/esm/components/Table/hooks/index.d.ts +1 -0
  28. package/dist/esm/components/Table/hooks/index.js +1 -0
  29. package/dist/esm/components/Table/hooks/useColumnOrderByDrag.d.ts +9 -0
  30. package/dist/esm/components/Table/hooks/useColumnOrderByDrag.js +66 -0
  31. package/dist/esm/components/types.d.ts +2 -0
  32. package/dist/esm/constants.d.ts +1 -0
  33. package/dist/esm/constants.js +1 -0
  34. package/dist/esm/helperComponents/Cells/BodyCell/BodyCell.d.ts +2 -1
  35. package/dist/esm/helperComponents/Cells/BodyCell/BodyCell.js +7 -3
  36. package/dist/esm/helperComponents/Cells/HeaderCell/DragHandle.d.ts +6 -0
  37. package/dist/esm/helperComponents/Cells/HeaderCell/DragHandle.js +6 -0
  38. package/dist/esm/helperComponents/Cells/HeaderCell/HeaderCell.d.ts +2 -1
  39. package/dist/esm/helperComponents/Cells/HeaderCell/HeaderCell.js +13 -4
  40. package/dist/esm/helperComponents/Cells/HeaderCell/styles.module.css +11 -0
  41. package/dist/esm/helperComponents/Rows/BodyRow.d.ts +4 -1
  42. package/dist/esm/helperComponents/Rows/BodyRow.js +4 -3
  43. package/dist/esm/helperComponents/Rows/HeaderRow.d.ts +7 -1
  44. package/dist/esm/helperComponents/Rows/HeaderRow.js +4 -3
  45. package/dist/esm/helperComponents/hooks.d.ts +22 -13
  46. package/dist/esm/helperComponents/hooks.js +58 -15
  47. package/dist/esm/types.d.ts +5 -0
  48. package/package.json +6 -2
  49. package/src/components/Table/Table.tsx +133 -102
  50. package/src/components/Table/hooks/index.ts +1 -0
  51. package/src/components/Table/hooks/useColumnOrderByDrag.ts +100 -0
  52. package/src/components/types.ts +3 -0
  53. package/src/constants.tsx +2 -0
  54. package/src/helperComponents/Cells/BodyCell/BodyCell.tsx +9 -2
  55. package/src/helperComponents/Cells/HeaderCell/DragHandle.tsx +18 -0
  56. package/src/helperComponents/Cells/HeaderCell/HeaderCell.tsx +19 -4
  57. package/src/helperComponents/Cells/HeaderCell/styles.module.scss +11 -0
  58. package/src/helperComponents/Rows/BodyRow.tsx +36 -9
  59. package/src/helperComponents/Rows/HeaderRow.tsx +51 -18
  60. package/src/helperComponents/hooks.ts +78 -15
  61. 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
+ }
@@ -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
@@ -39,3 +39,5 @@ export const SORT_FN = {
39
39
  } as const;
40
40
 
41
41
  export const DEFAULT_PAGE_SIZE = 10;
42
+
43
+ export const UNDRAGGABLE_COLUMNS = ['snack_predefined_statusColumn', 'selectionCell', 'rowActions'];
@@ -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={cellRef}
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>({ row, onRowClick }: BodyRowProps<TData>) {
27
- const { pinnedLeft, pinnedRight, unpinned } = useRowCells(row);
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
- {pinnedLeft && (
69
+ {leftPinned && (
61
70
  <PinnedCells position={COLUMN_PIN_POSITION.Left}>
62
- {pinnedLeft.map(cell => (
63
- <BodyCell key={cell.id} cell={cell} />
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
- <BodyCell key={cell.id} cell={cell} />
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
- {pinnedRight && (
93
+ {rightPinned && (
73
94
  <PinnedCells position={COLUMN_PIN_POSITION.Right}>
74
- {pinnedRight.map(cell => (
75
- <BodyCell key={cell.id} cell={cell} />
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
- export function HeaderRow() {
9
- const { leftPinned, unpinned, rightPinned } = useHeaderGroups();
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
- <PinnedCells position={COLUMN_PIN_POSITION.Left}>
15
- {leftPinned.map(headerGroup =>
16
- headerGroup.headers.map(header =>
17
- header.isPlaceholder ? null : <HeaderCell key={header.id} header={header} />,
18
- ),
19
- )}
20
- </PinnedCells>
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
- {unpinned.map(headerGroup => headerGroup.headers.map(header => <HeaderCell key={header.id} header={header} />))}
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
- <PinnedCells position={COLUMN_PIN_POSITION.Right}>
27
- {rightPinned.map(headerGroup =>
28
- headerGroup.headers.map(header =>
29
- header.isPlaceholder ? null : (
30
- <HeaderCell key={header.id} header={header} pinPosition={COLUMN_PIN_POSITION.Right} />
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
- </PinnedCells>
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
- export function useHeaderGroups() {
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
- pinnedLeft: left.length ? left : undefined,
54
- pinnedRight: right.length ? right : undefined,
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
- export function useCellSizes<TData>(element: Cell<TData, unknown> | Header<TData, unknown>) {
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
- return useMemo(
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
- [flexShrink, maxWidth, minWidth, width],
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,