@snack-uikit/table 0.33.4 → 0.34.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 (80) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +5 -3
  3. package/dist/cjs/components/Table/Table.d.ts +1 -1
  4. package/dist/cjs/components/Table/Table.js +107 -48
  5. package/dist/cjs/components/Table/hooks/index.d.ts +1 -0
  6. package/dist/cjs/components/Table/hooks/index.js +2 -1
  7. package/dist/cjs/components/Table/hooks/useColumnOrderByDrag.d.ts +8 -0
  8. package/dist/cjs/components/Table/hooks/useColumnOrderByDrag.js +49 -0
  9. package/dist/cjs/components/Table/utils.d.ts +13 -0
  10. package/dist/cjs/components/Table/utils.js +71 -0
  11. package/dist/cjs/components/types.d.ts +12 -3
  12. package/dist/cjs/constants.d.ts +1 -0
  13. package/dist/cjs/constants.js +3 -2
  14. package/dist/cjs/helperComponents/Cells/BodyCell/BodyCell.d.ts +2 -1
  15. package/dist/cjs/helperComponents/Cells/BodyCell/BodyCell.js +13 -3
  16. package/dist/cjs/helperComponents/Cells/HeaderCell/HeaderCell.d.ts +2 -1
  17. package/dist/cjs/helperComponents/Cells/HeaderCell/HeaderCell.js +41 -17
  18. package/dist/cjs/helperComponents/Cells/HeaderCell/styles.module.css +10 -0
  19. package/dist/cjs/helperComponents/ColumnsSettings/ColumnsSettings.d.ts +8 -0
  20. package/dist/cjs/helperComponents/ColumnsSettings/ColumnsSettings.js +38 -0
  21. package/dist/cjs/helperComponents/ColumnsSettings/index.d.ts +1 -0
  22. package/dist/cjs/helperComponents/ColumnsSettings/index.js +25 -0
  23. package/dist/cjs/helperComponents/ColumnsSettings/styles.module.css +3 -0
  24. package/dist/cjs/helperComponents/Rows/BodyRow.d.ts +4 -1
  25. package/dist/cjs/helperComponents/Rows/BodyRow.js +20 -11
  26. package/dist/cjs/helperComponents/Rows/HeaderRow.d.ts +7 -1
  27. package/dist/cjs/helperComponents/Rows/HeaderRow.js +13 -5
  28. package/dist/cjs/helperComponents/hooks.d.ts +9 -9
  29. package/dist/cjs/helperComponents/hooks.js +39 -11
  30. package/dist/cjs/helperComponents/index.d.ts +1 -0
  31. package/dist/cjs/helperComponents/index.js +2 -1
  32. package/dist/cjs/types.d.ts +11 -3
  33. package/dist/esm/components/Table/Table.d.ts +1 -1
  34. package/dist/esm/components/Table/Table.js +40 -12
  35. package/dist/esm/components/Table/hooks/index.d.ts +1 -0
  36. package/dist/esm/components/Table/hooks/index.js +1 -0
  37. package/dist/esm/components/Table/hooks/useColumnOrderByDrag.d.ts +8 -0
  38. package/dist/esm/components/Table/hooks/useColumnOrderByDrag.js +39 -0
  39. package/dist/esm/components/Table/utils.d.ts +13 -0
  40. package/dist/esm/components/Table/utils.js +70 -0
  41. package/dist/esm/components/types.d.ts +12 -3
  42. package/dist/esm/constants.d.ts +1 -0
  43. package/dist/esm/constants.js +1 -0
  44. package/dist/esm/helperComponents/Cells/BodyCell/BodyCell.d.ts +2 -1
  45. package/dist/esm/helperComponents/Cells/BodyCell/BodyCell.js +7 -3
  46. package/dist/esm/helperComponents/Cells/HeaderCell/HeaderCell.d.ts +2 -1
  47. package/dist/esm/helperComponents/Cells/HeaderCell/HeaderCell.js +14 -4
  48. package/dist/esm/helperComponents/Cells/HeaderCell/styles.module.css +10 -0
  49. package/dist/esm/helperComponents/ColumnsSettings/ColumnsSettings.d.ts +8 -0
  50. package/dist/esm/helperComponents/ColumnsSettings/ColumnsSettings.js +12 -0
  51. package/dist/esm/helperComponents/ColumnsSettings/index.d.ts +1 -0
  52. package/dist/esm/helperComponents/ColumnsSettings/index.js +1 -0
  53. package/dist/esm/helperComponents/ColumnsSettings/styles.module.css +3 -0
  54. package/dist/esm/helperComponents/Rows/BodyRow.d.ts +4 -1
  55. package/dist/esm/helperComponents/Rows/BodyRow.js +4 -3
  56. package/dist/esm/helperComponents/Rows/HeaderRow.d.ts +7 -1
  57. package/dist/esm/helperComponents/Rows/HeaderRow.js +3 -2
  58. package/dist/esm/helperComponents/hooks.d.ts +9 -9
  59. package/dist/esm/helperComponents/hooks.js +32 -11
  60. package/dist/esm/helperComponents/index.d.ts +1 -0
  61. package/dist/esm/helperComponents/index.js +1 -0
  62. package/dist/esm/types.d.ts +11 -3
  63. package/package.json +9 -5
  64. package/src/components/Table/Table.tsx +147 -61
  65. package/src/components/Table/hooks/index.ts +1 -0
  66. package/src/components/Table/hooks/useColumnOrderByDrag.ts +67 -0
  67. package/src/components/Table/utils.ts +118 -0
  68. package/src/components/types.ts +13 -3
  69. package/src/constants.tsx +1 -0
  70. package/src/helperComponents/Cells/BodyCell/BodyCell.tsx +9 -2
  71. package/src/helperComponents/Cells/HeaderCell/HeaderCell.tsx +50 -23
  72. package/src/helperComponents/Cells/HeaderCell/styles.module.scss +15 -0
  73. package/src/helperComponents/ColumnsSettings/ColumnsSettings.tsx +28 -0
  74. package/src/helperComponents/ColumnsSettings/index.ts +1 -0
  75. package/src/helperComponents/ColumnsSettings/styles.module.scss +5 -0
  76. package/src/helperComponents/Rows/BodyRow.tsx +30 -8
  77. package/src/helperComponents/Rows/HeaderRow.tsx +21 -4
  78. package/src/helperComponents/hooks.ts +41 -11
  79. package/src/helperComponents/index.ts +1 -0
  80. package/src/types.ts +21 -3
@@ -1,5 +1,9 @@
1
+ import { BaseItemProps, GroupSelectItemProps } from '@snack-uikit/list';
2
+ import { useLocale } from '@snack-uikit/locale';
1
3
  import { isBrowser } from '@snack-uikit/utils';
2
4
 
5
+ import { ColumnDefinition, FilterableColumnDefinition } from '../../types';
6
+
3
7
  export function getCurrentlyConfiguredHeaderWidth(id: string): number {
4
8
  if (isBrowser()) {
5
9
  const cell = document.querySelector<HTMLDivElement>(`[data-header-id="${id}"]`);
@@ -63,3 +67,117 @@ export function saveStateToLocalStorage({ id, columnId, size }: SaveStateToLocal
63
67
 
64
68
  localStorage.setItem(id, JSON.stringify({ ...(savedStateFromStorage || {}), resizeState: newResizeState }));
65
69
  }
70
+
71
+ export function isFilterableColumn<TData extends object>(
72
+ colDef: ColumnDefinition<TData>,
73
+ ): colDef is FilterableColumnDefinition<TData> {
74
+ return 'id' in colDef && 'headerConfigLabel' in colDef;
75
+ }
76
+
77
+ export function prepareColumnsSettingsMap<TData extends object>(
78
+ columnDefinitions: ColumnDefinition<TData>[],
79
+ ): string[] {
80
+ return columnDefinitions.filter(isFilterableColumn).map(colDef => colDef.id);
81
+ }
82
+
83
+ const sortColumnDefinitions = (columnOrder: string[]) =>
84
+ function sortColDefs<TData extends object>(
85
+ colDefA: FilterableColumnDefinition<TData>,
86
+ colDefB: FilterableColumnDefinition<TData>,
87
+ ) {
88
+ const indexItemA = columnOrder.findIndex(columnIndex => columnIndex === colDefA.id);
89
+ const indexItemB = columnOrder.findIndex(columnIndex => columnIndex === colDefB.id);
90
+
91
+ return indexItemA - indexItemB;
92
+ };
93
+
94
+ function createColumnsSettingsOption<TData extends object>(
95
+ columnDefinition: FilterableColumnDefinition<TData>,
96
+ ): BaseItemProps {
97
+ return {
98
+ id: columnDefinition.id,
99
+ content: {
100
+ option: columnDefinition.headerConfigLabel as string,
101
+ },
102
+ switch: true,
103
+ showSwitchIcon: true,
104
+ };
105
+ }
106
+
107
+ type ColumnGroups = {
108
+ pinTop: BaseItemProps[];
109
+ unpinned: BaseItemProps[];
110
+ pinBottom: BaseItemProps[];
111
+ };
112
+
113
+ type PrepareColumnsSettingsProps<TData extends object> = {
114
+ columnDefinitions: ColumnDefinition<TData>[];
115
+ columnOrder: string[];
116
+ areAllColumnsEnabled: boolean;
117
+ columnsSettingsHeader?: string;
118
+ t: ReturnType<typeof useLocale<'Table'>>['t'];
119
+ };
120
+
121
+ export function prepareColumnsSettings<TData extends object>({
122
+ columnDefinitions,
123
+ columnOrder,
124
+ columnsSettingsHeader,
125
+ areAllColumnsEnabled,
126
+ t,
127
+ }: PrepareColumnsSettingsProps<TData>): [GroupSelectItemProps] {
128
+ const groupedItems = columnDefinitions
129
+ .filter(isFilterableColumn)
130
+ .sort(sortColumnDefinitions(columnOrder))
131
+ .reduce(
132
+ (accSettings: ColumnGroups, colDef: FilterableColumnDefinition<TData>) => {
133
+ const item = createColumnsSettingsOption(colDef);
134
+
135
+ switch (colDef.pinned) {
136
+ case 'left':
137
+ accSettings.pinTop.push(item);
138
+ break;
139
+
140
+ case 'right':
141
+ accSettings.pinBottom.push(item);
142
+ break;
143
+
144
+ default:
145
+ accSettings.unpinned.push(item);
146
+ }
147
+
148
+ return accSettings;
149
+ },
150
+ {
151
+ pinTop: [],
152
+ unpinned: [],
153
+ pinBottom: [],
154
+ },
155
+ );
156
+
157
+ return [
158
+ {
159
+ divider: false,
160
+ items: [
161
+ {
162
+ divider: false,
163
+ items: groupedItems.pinTop,
164
+ type: 'group',
165
+ },
166
+ {
167
+ divider: true,
168
+ items: groupedItems.unpinned,
169
+ type: 'group',
170
+ },
171
+ {
172
+ divider: true,
173
+ items: groupedItems.pinBottom,
174
+ type: 'group',
175
+ },
176
+ ],
177
+ selectButtonLabel: areAllColumnsEnabled ? t('groupSelectButton.hide') : t('groupSelectButton.show'),
178
+ label: columnsSettingsHeader || 'Display settings',
179
+ mode: 'primary',
180
+ type: 'group-select',
181
+ },
182
+ ];
183
+ }
@@ -8,8 +8,8 @@ import {
8
8
  } from '@tanstack/react-table';
9
9
  import { ReactNode, RefObject } from 'react';
10
10
 
11
- import { ChipChoiceRowProps, FiltersState } from '@snack-uikit/chips';
12
- import { ToolbarProps } from '@snack-uikit/toolbar';
11
+ import { FiltersState } from '@snack-uikit/chips';
12
+ import { FilterRow, ToolbarProps } from '@snack-uikit/toolbar';
13
13
  import { WithSupportProps } from '@snack-uikit/utils';
14
14
 
15
15
  import { EmptyStateProps, ExportButtonProps, RowClickHandler } from '../helperComponents';
@@ -42,6 +42,16 @@ type BaseTableProps<TData extends object, TFilters extends FiltersState = Record
42
42
  onChange?(state: SortingState): void;
43
43
  };
44
44
 
45
+ /** Параметры отвечают за настройки колонок <br>
46
+ * <strong>enableDrag</strong>: Включение сортировки порядка столбцов вручную перетаскиванием <br>
47
+ * <strong>headerLabel</strong>: Название меню настроек колонок. Наличие включает показ настроек <br>
48
+ * <strong>selectAllButtonLabels</strong>: Значения кнопки включения/отключения всех айтемов ([вкл, выкл]) <br>
49
+ * */
50
+ columnsSettings?: {
51
+ enableDrag?: boolean;
52
+ headerLabel?: string;
53
+ };
54
+
45
55
  /** Параметр отвечает за общие настройки раскрывающихся строк*/
46
56
  expanding?: {
47
57
  /** Метод отвечает за получение дочерних строк*/
@@ -101,7 +111,7 @@ type BaseTableProps<TData extends object, TFilters extends FiltersState = Record
101
111
  outline?: boolean;
102
112
 
103
113
  /** Фильтры */
104
- columnFilters?: ChipChoiceRowProps<TFilters>;
114
+ columnFilters?: FilterRow<TFilters>;
105
115
 
106
116
  /** Флаг, показывающий что данные были отфильтрованы при пустых данных */
107
117
  dataFiltered?: boolean;
package/src/constants.tsx CHANGED
@@ -42,3 +42,4 @@ export const DEFAULT_PAGE_SIZE = 10;
42
42
  export const DEFAULT_SORTING = [];
43
43
  export const DEFAULT_FILTER_VISIBILITY = [];
44
44
  export const DEFAULT_ROW_SELECTION = {};
45
+ export const DEFAULT_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
 
@@ -10,16 +11,22 @@ import styles from './styles.module.scss';
10
11
  type BodyCellProps<TData> = Omit<CellProps, 'style' | 'children'> & {
11
12
  cell: TableCell<TData, unknown>;
12
13
  rowAutoHeight?: boolean;
14
+ isDraggable?: boolean;
13
15
  };
14
16
 
15
- export function BodyCell<TData>({ cell, className, rowAutoHeight, ...props }: BodyCellProps<TData>) {
17
+ export function BodyCell<TData>({ cell, className, rowAutoHeight, isDraggable, ...props }: BodyCellProps<TData>) {
16
18
  const columnDef: ColumnDefinition<TData> = cell.column.columnDef;
17
19
 
18
- const style = useCellSizes(cell);
20
+ const style = useCellSizes(cell, { isDraggable });
21
+
22
+ const { setNodeRef } = useSortable({
23
+ id: cell.column.id,
24
+ });
19
25
 
20
26
  return (
21
27
  <Cell
22
28
  {...props}
29
+ ref={setNodeRef}
23
30
  style={style}
24
31
  className={cn(styles.tableBodyCell, className, columnDef.cellClassName)}
25
32
  data-row-auto-height={rowAutoHeight || undefined}
@@ -1,10 +1,11 @@
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 { DEFAULT_COLUMNS, TEST_IDS } from '../../../constants';
8
9
  import { ColumnDefinition, ColumnPinPosition } from '../../../types';
9
10
  import { useCellSizes } from '../../hooks';
10
11
  import { Cell, CellProps } from '../Cell';
@@ -16,9 +17,16 @@ type HeaderCellProps<TData> = Omit<CellProps, 'align' | 'children' | 'onClick' |
16
17
  header: Header<TData, unknown>;
17
18
  pinPosition?: ColumnPinPosition;
18
19
  rowAutoHeight?: boolean;
20
+ isDraggable?: boolean;
19
21
  };
20
22
 
21
- export function HeaderCell<TData>({ header, pinPosition, className, rowAutoHeight }: HeaderCellProps<TData>) {
23
+ export function HeaderCell<TData>({
24
+ header,
25
+ pinPosition,
26
+ className,
27
+ rowAutoHeight,
28
+ isDraggable,
29
+ }: HeaderCellProps<TData>) {
22
30
  const cellRef = useRef<HTMLDivElement>(null);
23
31
  const isSortable = header.column.getCanSort();
24
32
  const isResizable = header.column.getCanResize();
@@ -32,7 +40,11 @@ export function HeaderCell<TData>({ header, pinPosition, className, rowAutoHeigh
32
40
 
33
41
  const columnDef: ColumnDefinition<TData> = header.column.columnDef;
34
42
 
35
- const style = useCellSizes(header);
43
+ const style = useCellSizes(header, { isDraggable });
44
+
45
+ const { attributes, listeners, setNodeRef, isDragging } = useSortable({
46
+ id: header.column.id,
47
+ });
36
48
 
37
49
  const sortingHandler = (e: MouseEvent<HTMLDivElement>) => {
38
50
  if (isSomeColumnResizing) return;
@@ -40,11 +52,16 @@ export function HeaderCell<TData>({ header, pinPosition, className, rowAutoHeigh
40
52
  return header.column.getToggleSortingHandler()?.(e);
41
53
  };
42
54
 
55
+ const isAvailableForDrag = !DEFAULT_COLUMNS.includes(header.column.id);
56
+ const dragAttributes = isAvailableForDrag ? attributes : {};
57
+ const listenersAttributes = isAvailableForDrag ? listeners : {};
58
+
43
59
  return (
44
60
  <Cell
45
61
  style={style}
46
62
  onClick={sortingHandler}
47
63
  data-sortable={isSortable || undefined}
64
+ data-draggable={isDraggable || undefined}
48
65
  data-no-padding={columnDef.noHeaderCellPadding || undefined}
49
66
  data-no-offset={columnDef.noHeaderCellBorderOffset || undefined}
50
67
  data-test-id={TEST_IDS.headerCell}
@@ -55,28 +72,38 @@ export function HeaderCell<TData>({ header, pinPosition, className, rowAutoHeigh
55
72
  data-row-auto-height={rowAutoHeight || undefined}
56
73
  role='columnheader'
57
74
  className={cn(styles.tableHeaderCell, className, columnDef.headerClassName)}
58
- ref={cellRef}
75
+ ref={element => {
76
+ setNodeRef(element);
77
+ return cellRef;
78
+ }}
59
79
  >
60
- <div className={styles.tableHeaderCellMain}>
61
- {columnDef.header && (
62
- <div className={styles.tableHeaderCellName}>
63
- {rowAutoHeight ? (
64
- flexRender(columnDef.header, header.getContext())
65
- ) : (
66
- <TruncateString text={flexRender(columnDef.header, header.getContext()) as string} />
67
- )}
68
- </div>
69
- )}
80
+ <div
81
+ className={styles.tableHeaderCellDragWrapper}
82
+ data-dragging={isDragging || undefined}
83
+ {...dragAttributes}
84
+ {...listenersAttributes}
85
+ >
86
+ <div className={styles.tableHeaderCellMain}>
87
+ {columnDef.header && (
88
+ <div className={styles.tableHeaderCellName}>
89
+ {rowAutoHeight ? (
90
+ flexRender(columnDef.header, header.getContext())
91
+ ) : (
92
+ <TruncateString text={flexRender(columnDef.header, header.getContext()) as string} />
93
+ )}
94
+ </div>
95
+ )}
70
96
 
71
- {Boolean(sortIcon) && (
72
- <div
73
- className={styles.tableHeaderIcon}
74
- data-sort-direction={sortDirection}
75
- data-test-id={TEST_IDS.headerSortIndicator}
76
- >
77
- {sortIcon}
78
- </div>
79
- )}
97
+ {Boolean(sortIcon) && (
98
+ <div
99
+ className={styles.tableHeaderIcon}
100
+ data-sort-direction={sortDirection}
101
+ data-test-id={TEST_IDS.headerSortIndicator}
102
+ >
103
+ {sortIcon}
104
+ </div>
105
+ )}
106
+ </div>
80
107
  </div>
81
108
 
82
109
  {Boolean(isResizable) && <ResizeHandle header={header} cellRef={cellRef} />}
@@ -98,6 +98,10 @@
98
98
  }
99
99
  }
100
100
 
101
+ &[data-draggable] {
102
+ cursor: grab;
103
+ }
104
+
101
105
  &[data-sortable] {
102
106
  cursor: pointer;
103
107
  }
@@ -151,6 +155,16 @@
151
155
  }
152
156
  }
153
157
 
158
+ .tableHeaderCellDragWrapper {
159
+ width: 100%;
160
+
161
+ &[data-dragging] {
162
+ &:active {
163
+ cursor: grabbing;
164
+ }
165
+ }
166
+ }
167
+
154
168
  .tableHeaderCellMain {
155
169
  overflow: auto;
156
170
  display: flex;
@@ -182,3 +196,4 @@
182
196
  height: styles-tokens-table.simple-var(styles-tokens-element.$icon-xs) !important; /* stylelint-disable-line declaration-no-important */
183
197
  }
184
198
  }
199
+
@@ -0,0 +1,28 @@
1
+ import { ButtonFunction } from '@snack-uikit/button';
2
+ import { FunctionSettingsSVG } from '@snack-uikit/icons';
3
+ import { Droplist, GroupSelectItemProps } from '@snack-uikit/list';
4
+
5
+ import styles from './styles.module.scss';
6
+
7
+ type ColumnsSettingsProps = {
8
+ enabledColumns: string[];
9
+ setEnabledColumns(enabledColumns: string[]): void;
10
+ columnsSettings: [GroupSelectItemProps];
11
+ };
12
+
13
+ export function ColumnsSettings({ columnsSettings, enabledColumns, setEnabledColumns }: ColumnsSettingsProps) {
14
+ return (
15
+ <Droplist
16
+ className={styles.columnsSettings}
17
+ items={columnsSettings}
18
+ selection={{
19
+ value: enabledColumns,
20
+ onChange: setEnabledColumns,
21
+ mode: 'multiple',
22
+ }}
23
+ placement='bottom-end'
24
+ >
25
+ <ButtonFunction size='m' data-test-id='table__column-settings' icon={<FunctionSettingsSVG />} />
26
+ </Droplist>
27
+ );
28
+ }
@@ -0,0 +1 @@
1
+ export * from './ColumnsSettings';
@@ -0,0 +1,5 @@
1
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-table' as table;
2
+
3
+ .columnsSettings {
4
+ min-width: 256px;
5
+ }
@@ -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 { ColumnOrder } from '../../types';
5
7
  import { BodyCell } from '../Cells';
6
8
  import { RowContext } from '../contexts';
7
9
  import { useRowCells } from '../hooks';
@@ -21,10 +23,18 @@ export type RowClickHandler<TData> = (e: MouseEvent<HTMLDivElement>, row: RowInf
21
23
  export type BodyRowProps<TData> = Pick<RowProps, 'rowAutoHeight'> & {
22
24
  row: TableRow<TData>;
23
25
  onRowClick?: RowClickHandler<TData>;
26
+ columnOrder: ColumnOrder;
27
+ enableColumnsOrderSortByDrag?: boolean;
24
28
  };
25
29
 
26
- export function BodyRow<TData>({ row, onRowClick, rowAutoHeight }: BodyRowProps<TData>) {
27
- const { pinnedLeft, pinnedRight, unpinned } = useRowCells(row);
30
+ export function BodyRow<TData>({
31
+ row,
32
+ onRowClick,
33
+ rowAutoHeight,
34
+ columnOrder,
35
+ enableColumnsOrderSortByDrag,
36
+ }: BodyRowProps<TData>) {
37
+ const { leftPinned, rightPinned, unpinned } = useRowCells(row);
28
38
 
29
39
  const [dropListOpened, setDropListOpen] = useState(false);
30
40
 
@@ -58,22 +68,34 @@ export function BodyRow<TData>({ row, onRowClick, rowAutoHeight }: BodyRowProps<
58
68
  className={styles.bodyRow}
59
69
  rowAutoHeight={rowAutoHeight}
60
70
  >
61
- {pinnedLeft && (
71
+ {leftPinned && (
62
72
  <PinnedCells position={COLUMN_PIN_POSITION.Left}>
63
- {pinnedLeft.map(cell => (
73
+ {leftPinned.map(cell => (
64
74
  <BodyCell key={cell.id} cell={cell} rowAutoHeight={rowAutoHeight} />
65
75
  ))}
66
76
  </PinnedCells>
67
77
  )}
68
78
 
69
79
  {unpinned.map(cell => (
70
- <BodyCell key={cell.id} cell={cell} rowAutoHeight={rowAutoHeight} />
80
+ <SortableContext key={cell.id} items={columnOrder} strategy={horizontalListSortingStrategy}>
81
+ <BodyCell
82
+ key={cell.id}
83
+ cell={cell}
84
+ rowAutoHeight={rowAutoHeight}
85
+ isDraggable={enableColumnsOrderSortByDrag}
86
+ />
87
+ </SortableContext>
71
88
  ))}
72
89
 
73
- {pinnedRight && (
90
+ {rightPinned && (
74
91
  <PinnedCells position={COLUMN_PIN_POSITION.Right}>
75
- {pinnedRight.map(cell => (
76
- <BodyCell key={cell.id} cell={cell} rowAutoHeight={rowAutoHeight} />
92
+ {rightPinned.map(cell => (
93
+ <BodyCell
94
+ key={cell.id}
95
+ cell={cell}
96
+ rowAutoHeight={rowAutoHeight}
97
+ isDraggable={enableColumnsOrderSortByDrag}
98
+ />
77
99
  ))}
78
100
  </PinnedCells>
79
101
  )}
@@ -1,11 +1,19 @@
1
+ import { horizontalListSortingStrategy, SortableContext } from '@dnd-kit/sortable';
2
+
1
3
  import { COLUMN_PIN_POSITION, TEST_IDS } from '../../constants';
4
+ import { ColumnOrder } from '../../types';
2
5
  import { HeaderCell } from '../Cells';
3
6
  import { useHeaderGroups } from '../hooks';
4
7
  import { PinnedCells } from './PinnedCells';
5
8
  import { Row, RowProps } from './Row';
6
9
  import styles from './styles.module.scss';
7
10
 
8
- export function HeaderRow({ rowAutoHeight }: Pick<RowProps, 'rowAutoHeight'>) {
11
+ type Props = Pick<RowProps, 'rowAutoHeight'> & {
12
+ columnOrder: ColumnOrder;
13
+ enableColumnsOrderSortByDrag?: boolean;
14
+ };
15
+
16
+ export function HeaderRow({ rowAutoHeight, columnOrder, enableColumnsOrderSortByDrag }: Props) {
9
17
  const { leftPinned, unpinned, rightPinned } = useHeaderGroups();
10
18
 
11
19
  return (
@@ -22,9 +30,18 @@ export function HeaderRow({ rowAutoHeight }: Pick<RowProps, 'rowAutoHeight'>) {
22
30
  </PinnedCells>
23
31
  )}
24
32
 
25
- {unpinned.map(headerGroup =>
26
- headerGroup.headers.map(header => <HeaderCell key={header.id} header={header} rowAutoHeight={rowAutoHeight} />),
27
- )}
33
+ <SortableContext items={columnOrder} strategy={horizontalListSortingStrategy}>
34
+ {unpinned.map(headerGroup =>
35
+ headerGroup.headers.map(header => (
36
+ <HeaderCell
37
+ key={header.id}
38
+ header={header}
39
+ rowAutoHeight={rowAutoHeight}
40
+ isDraggable={enableColumnsOrderSortByDrag && columnOrder.length > 1}
41
+ />
42
+ )),
43
+ )}
44
+ </SortableContext>
28
45
 
29
46
  {rightPinned && (
30
47
  <PinnedCells position={COLUMN_PIN_POSITION.Right}>
@@ -1,5 +1,7 @@
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
 
4
6
  import { useTableContext } from './contexts';
5
7
 
@@ -12,6 +14,7 @@ export function useHeaderGroups() {
12
14
 
13
15
  const columnDefs = table._getColumnDefs();
14
16
  const pinEnabled = table.getIsSomeColumnsPinned();
17
+ const { columnOrder } = table.getState();
15
18
 
16
19
  return useMemo(() => {
17
20
  if (!pinEnabled) {
@@ -30,7 +33,7 @@ export function useHeaderGroups() {
30
33
  };
31
34
  // need to rebuild if columnDefinitions has changed
32
35
  // eslint-disable-next-line react-hooks/exhaustive-deps
33
- }, [table, pinEnabled, columnDefs]);
36
+ }, [table, pinEnabled, columnDefs, columnOrder]);
34
37
  }
35
38
 
36
39
  export function useRowCells<TData>(row: Row<TData>) {
@@ -38,6 +41,7 @@ export function useRowCells<TData>(row: Row<TData>) {
38
41
 
39
42
  const pinEnabled = table.getIsSomeColumnsPinned();
40
43
  const columnDefs = table._getColumnDefs();
44
+ const { columnOrder } = table.getState();
41
45
 
42
46
  return useMemo(() => {
43
47
  if (!pinEnabled) {
@@ -50,30 +54,56 @@ export function useRowCells<TData>(row: Row<TData>) {
50
54
  const right = row.getRightVisibleCells();
51
55
 
52
56
  return {
53
- pinnedLeft: left.length ? left : undefined,
54
- pinnedRight: right.length ? right : undefined,
57
+ leftPinned: left.length ? left : undefined,
58
+ rightPinned: right.length ? right : undefined,
55
59
  unpinned: row.getCenterVisibleCells(),
56
60
  };
57
61
  // need to rebuild if columnDefinitions has changed
58
62
  // eslint-disable-next-line react-hooks/exhaustive-deps
59
- }, [row, pinEnabled, columnDefs]);
63
+ }, [row, pinEnabled, columnDefs, columnOrder]);
60
64
  }
61
65
 
62
- export function useCellSizes<TData>(element: Cell<TData, unknown> | Header<TData, unknown>) {
66
+ type CellSizesOptions = {
67
+ isDraggable?: boolean;
68
+ };
69
+
70
+ export function useCellSizes<TData>(
71
+ element: Cell<TData, unknown> | Header<TData, unknown>,
72
+ options?: CellSizesOptions,
73
+ ) {
63
74
  const column = element.column;
64
75
 
76
+ const { isDragging, transform } = useSortable({
77
+ id: column.id,
78
+ });
79
+
65
80
  const minWidth = column.columnDef.minSize;
66
81
  const maxWidth = column.columnDef.maxSize;
67
82
  const width = `var(--table-column-${column.id}-size)`;
68
83
  const flexShrink = `var(--table-column-${column.id}-flex)`;
69
84
 
70
- return useMemo(
71
- () => ({
85
+ const isHeaderCell = 'headerGroup' in element;
86
+
87
+ return useMemo(() => {
88
+ const styles: CSSProperties = {
72
89
  minWidth,
73
90
  width,
74
91
  maxWidth,
75
92
  flexShrink,
76
- }),
77
- [flexShrink, maxWidth, minWidth, width],
78
- );
93
+ };
94
+
95
+ if (options?.isDraggable) {
96
+ styles.opacity = isDragging ? 0.8 : 1;
97
+ styles.position = 'relative';
98
+ styles.transform = CSS.Translate.toString(transform);
99
+ styles.transition = 'width transform 0.2s ease-in-out';
100
+ styles.zIndex = isDragging ? 1 : undefined;
101
+
102
+ if (isHeaderCell) {
103
+ styles.whiteSpace = 'nowrap';
104
+ }
105
+ }
106
+
107
+ return styles;
108
+ }, [options?.isDraggable, flexShrink, isDragging, isHeaderCell, maxWidth, minWidth, transform, width]);
79
109
  }
@@ -7,3 +7,4 @@ export * from './hooks';
7
7
  export * from './types';
8
8
  export * from './TableEmptyState';
9
9
  export * from './TablePagination';
10
+ export * from './ColumnsSettings';
package/src/types.ts CHANGED
@@ -14,8 +14,7 @@ import { ToolbarProps } from '@snack-uikit/toolbar';
14
14
  import { ValueOf } from '@snack-uikit/utils';
15
15
 
16
16
  import { COLUMN_ALIGN, COLUMN_PIN_POSITION } from './constants';
17
- import { Except } from './helperComponents';
18
- import { EmptyStateProps } from './helperComponents/TableEmptyState';
17
+ import { EmptyStateProps, Except } from './helperComponents';
19
18
 
20
19
  type ColumnAlign = ValueOf<typeof COLUMN_ALIGN>;
21
20
 
@@ -64,7 +63,26 @@ type PinnedColumnDefinition<TData> = BaseColumnDefinition<TData> & {
64
63
  size: number;
65
64
  };
66
65
 
67
- export type ColumnDefinition<TData> = NormalColumnDefinition<TData> | PinnedColumnDefinition<TData>;
66
+ type FilterableProps = {
67
+ id: string;
68
+ /** Название колонки в настройках таблицы */
69
+ headerConfigLabel?: string;
70
+ };
71
+
72
+ type FilterableNormalColumnDefinition<TData> = NormalColumnDefinition<TData> & FilterableProps;
73
+
74
+ type FilterablePinnedColumnDefinition<TData> = PinnedColumnDefinition<TData> & FilterableProps;
75
+
76
+ export type FilterableColumnDefinition<TData> =
77
+ | FilterableNormalColumnDefinition<TData>
78
+ | FilterablePinnedColumnDefinition<TData>;
79
+
80
+ export type ColumnDefinition<TData> =
81
+ | NormalColumnDefinition<TData>
82
+ | PinnedColumnDefinition<TData>
83
+ | FilterableColumnDefinition<TData>;
84
+
85
+ export type ColumnOrder = string[];
68
86
 
69
87
  export type {
70
88
  RowActionsColumnDefProps,