@snack-uikit/table 0.8.9 → 0.9.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 +1 -4
- package/dist/components/Table/Table.js +27 -1
- package/dist/helperComponents/Cells/Cell.d.ts +10 -1
- package/dist/helperComponents/Cells/Cell.js +5 -4
- package/dist/helperComponents/Cells/HeaderCell/HeaderCell.js +14 -1
- package/dist/helperComponents/Cells/HeaderCell/ResizeHandle.d.ts +8 -0
- package/dist/helperComponents/Cells/HeaderCell/ResizeHandle.js +29 -0
- package/dist/helperComponents/Cells/HeaderCell/styles.module.css +65 -3
- package/dist/helperComponents/Cells/RowActionsCell/RowActionsCell.js +1 -0
- package/dist/helperComponents/Cells/SelectionCell/SelectionCell.js +1 -0
- package/dist/helperComponents/Cells/StatusCell/StatusCell.d.ts +4 -1
- package/dist/helperComponents/Cells/StatusCell/StatusCell.js +2 -1
- package/dist/helperComponents/Rows/HeaderRow.js +1 -1
- package/dist/helperComponents/Rows/Row.d.ts +1 -2
- package/dist/helperComponents/Rows/Row.js +4 -2
- package/dist/helperComponents/hooks.d.ts +2 -5
- package/dist/helperComponents/hooks.js +11 -18
- package/dist/types.d.ts +1 -1
- package/package.json +3 -3
- package/src/components/Table/Table.tsx +35 -1
- package/src/helperComponents/Cells/Cell.tsx +16 -6
- package/src/helperComponents/Cells/HeaderCell/HeaderCell.tsx +38 -16
- package/src/helperComponents/Cells/HeaderCell/ResizeHandle.tsx +63 -0
- package/src/helperComponents/Cells/HeaderCell/styles.module.scss +81 -2
- package/src/helperComponents/Cells/RowActionsCell/RowActionsCell.tsx +1 -0
- package/src/helperComponents/Cells/SelectionCell/SelectionCell.tsx +1 -0
- package/src/helperComponents/Cells/StatusCell/StatusCell.tsx +5 -0
- package/src/helperComponents/Rows/HeaderRow.tsx +1 -1
- package/src/helperComponents/Rows/Row.tsx +3 -3
- package/src/helperComponents/hooks.ts +16 -21
- package/src/types.ts +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 0.9.0 (2024-01-16)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **FF-4093:** table columns resizing ([8cae511](https://github.com/cloud-ru-tech/snack-uikit/commit/8cae5116f73f079ce3087099253390f2e79034fe))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
## 0.8.9 (2023-12-29)
|
|
7
18
|
|
|
8
19
|
|
package/README.md
CHANGED
|
@@ -6,11 +6,7 @@
|
|
|
6
6
|
[Changelog](./CHANGELOG.md)
|
|
7
7
|
|
|
8
8
|
## TODO:
|
|
9
|
-
- resize columns
|
|
10
|
-
- export data
|
|
11
|
-
- loading state
|
|
12
9
|
- multiple row selection with Shift key pressed
|
|
13
|
-
- try to make the table independent of height
|
|
14
10
|
|
|
15
11
|
|
|
16
12
|
## Примечание
|
|
@@ -139,6 +135,7 @@ const columnDefinitions: ColumnDefinition<TableData>[] = [
|
|
|
139
135
|
| renderDescription | `(cellValue: string) => string` | - | Функция для отрисовки текста, если не передана, то будет отрисован только индикатор статуса |
|
|
140
136
|
| size | `number` | - | Размер ячейки |
|
|
141
137
|
| header | `ColumnDefTemplate<HeaderContext<TData, unknown>> & (string \| ((ctx: HeaderContext<TData, unknown>) => string))` | - | Заголовок колонки |
|
|
138
|
+
| enableResizing | `boolean` | - | Включение/выключение ресайза колонки |
|
|
142
139
|
## Table.getRowActionsColumnDef
|
|
143
140
|
Вспомогательная функция для создания ячейки с дополнительными действиями у строки
|
|
144
141
|
### Props
|
|
@@ -70,6 +70,8 @@ export function Table(_a) {
|
|
|
70
70
|
pageCount,
|
|
71
71
|
defaultColumn: {
|
|
72
72
|
enableSorting: false,
|
|
73
|
+
enableResizing: false,
|
|
74
|
+
minSize: 40,
|
|
73
75
|
},
|
|
74
76
|
globalFilterFn: fuzzyFilter,
|
|
75
77
|
onGlobalFilterChange,
|
|
@@ -78,6 +80,7 @@ export function Table(_a) {
|
|
|
78
80
|
enableMultiRowSelection: rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow,
|
|
79
81
|
enableFilters: true,
|
|
80
82
|
getFilteredRowModel: getFilteredRowModel(),
|
|
83
|
+
enableColumnResizing: true,
|
|
81
84
|
enableSorting: true,
|
|
82
85
|
manualSorting: false,
|
|
83
86
|
enableMultiSort: false,
|
|
@@ -111,10 +114,33 @@ export function Table(_a) {
|
|
|
111
114
|
return;
|
|
112
115
|
}
|
|
113
116
|
}, [loading, rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow, table]);
|
|
117
|
+
const columnSizeVars = useMemo(() => {
|
|
118
|
+
const originalColumnDefs = table._getColumnDefs();
|
|
119
|
+
const headers = table.getFlatHeaders();
|
|
120
|
+
const colSizes = {};
|
|
121
|
+
for (let i = 0; i < headers.length; i++) {
|
|
122
|
+
const header = headers[i];
|
|
123
|
+
const originalColDef = originalColumnDefs.find(col => getColumnId(header) === col.id);
|
|
124
|
+
const originalColumnDefSize = originalColDef === null || originalColDef === void 0 ? void 0 : originalColDef.size;
|
|
125
|
+
const initSize = originalColumnDefSize ? `${originalColumnDefSize}px` : '100%';
|
|
126
|
+
let size = initSize;
|
|
127
|
+
if (header.column.getCanResize()) {
|
|
128
|
+
const currentSize = header.getSize();
|
|
129
|
+
const colDefSize = header.column.columnDef.size;
|
|
130
|
+
size = currentSize === colDefSize ? initSize : `${currentSize}px`;
|
|
131
|
+
}
|
|
132
|
+
colSizes[`--table-column-${header.id}-size`] = size;
|
|
133
|
+
colSizes[`--table-column-${header.id}-flex`] = size === '100%' ? 'unset' : '0';
|
|
134
|
+
}
|
|
135
|
+
return colSizes;
|
|
136
|
+
/* effect must be called only on columnSizingInfo.isResizingColumn changes
|
|
137
|
+
to reduce unnecessary recalculations */
|
|
138
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
139
|
+
}, [table.getState().columnSizingInfo.isResizingColumn]);
|
|
114
140
|
const tableRows = table.getRowModel().rows;
|
|
115
141
|
const loadingTableRows = loadingTable.getRowModel().rows;
|
|
116
142
|
const tablePagination = table.getState().pagination;
|
|
117
|
-
return (_jsx(_Fragment, { children: _jsxs("div", Object.assign({ style: { '--page-size': !suppressPagination ? tablePagination === null || tablePagination === void 0 ? void 0 : tablePagination.pageSize : pageSize }, className: cn(styles.wrapper, className) }, extractSupportProps(rest), { children: [!suppressToolbar && (_jsxs("div", { className: styles.header, children: [_jsx(Toolbar, { value: globalFilter, onChange: onGlobalFilterChange, checked: table.getIsAllPageRowsSelected(), indeterminate: table.getIsSomePageRowsSelected(), className: styles.toolbar, onRefresh: onRefresh ? handleOnRefresh : undefined, onDelete: enableSelection && onDelete ? handleOnDelete : undefined, onCheck: enableSelection ? handleOnCheck : undefined, outline: outline, loading: search === null || search === void 0 ? void 0 : search.loading, placeholder: (search === null || search === void 0 ? void 0 : search.placeholder) || 'Search', selectionMode: (rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow) ? 'multiple' : 'single', actions: exportFileName ? (_jsx(ExportButton, { fileName: exportFileName, columnDefinitions: columnDefinitions, data: data })) : undefined, moreActions: moreActions }), columnFiltersProp && _jsxs("div", { className: styles.filtersWrapper, children: [" ", columnFiltersProp, " "] })] })), _jsx("div", { className: styles.scrollWrapper, "data-outline": outline || undefined, children: _jsx(Scroll, { size: 's', className: styles.table, children: _jsx("div", { className: styles.tableContent, children: _jsx(TableContext.Provider, { value: { table }, children: loading ? (_jsxs(SkeletonContextProvider, { loading: true, children: [_jsx(HeaderRow, {}), loadingTableRows.map(row => (_jsx(BodyRow, { row: row }, row.id)))] })) : (_jsxs(_Fragment, { children: [tableRows.length ? _jsx(HeaderRow, {}) : null, tableRows.map(row => (_jsx(BodyRow, { row: row, onRowClick: onRowClick }, row.id))), !tableRows.length && globalFilter && _jsx(TableEmptyState, Object.assign({}, noResultsState)), !tableRows.length && !globalFilter && _jsx(TableEmptyState, Object.assign({}, noDataState))] })) }) }) }) }), !suppressPagination && (_jsx(TablePagination, { table: table, options: paginationProp === null || paginationProp === void 0 ? void 0 : paginationProp.options, optionsLabel: paginationProp === null || paginationProp === void 0 ? void 0 : paginationProp.optionsLabel, pageCount: pageCount }))] })) }));
|
|
143
|
+
return (_jsx(_Fragment, { children: _jsxs("div", Object.assign({ style: { '--page-size': !suppressPagination ? tablePagination === null || tablePagination === void 0 ? void 0 : tablePagination.pageSize : pageSize }, className: cn(styles.wrapper, className) }, extractSupportProps(rest), { children: [!suppressToolbar && (_jsxs("div", { className: styles.header, children: [_jsx(Toolbar, { value: globalFilter, onChange: onGlobalFilterChange, checked: table.getIsAllPageRowsSelected(), indeterminate: table.getIsSomePageRowsSelected(), className: styles.toolbar, onRefresh: onRefresh ? handleOnRefresh : undefined, onDelete: enableSelection && onDelete ? handleOnDelete : undefined, onCheck: enableSelection ? handleOnCheck : undefined, outline: outline, loading: search === null || search === void 0 ? void 0 : search.loading, placeholder: (search === null || search === void 0 ? void 0 : search.placeholder) || 'Search', selectionMode: (rowSelectionProp === null || rowSelectionProp === void 0 ? void 0 : rowSelectionProp.multiRow) ? 'multiple' : 'single', actions: exportFileName ? (_jsx(ExportButton, { fileName: exportFileName, columnDefinitions: columnDefinitions, data: data })) : undefined, moreActions: moreActions }), columnFiltersProp && _jsxs("div", { className: styles.filtersWrapper, children: [" ", columnFiltersProp, " "] })] })), _jsx("div", { className: styles.scrollWrapper, "data-outline": outline || undefined, children: _jsx(Scroll, { size: 's', className: styles.table, children: _jsx("div", { className: styles.tableContent, style: columnSizeVars, children: _jsx(TableContext.Provider, { value: { table }, children: loading ? (_jsxs(SkeletonContextProvider, { loading: true, children: [_jsx(HeaderRow, {}), loadingTableRows.map(row => (_jsx(BodyRow, { row: row }, row.id)))] })) : (_jsxs(_Fragment, { children: [tableRows.length ? _jsx(HeaderRow, {}) : null, tableRows.map(row => (_jsx(BodyRow, { row: row, onRowClick: onRowClick }, row.id))), !tableRows.length && globalFilter && _jsx(TableEmptyState, Object.assign({}, noResultsState)), !tableRows.length && !globalFilter && _jsx(TableEmptyState, Object.assign({}, noDataState))] })) }) }) }) }), !suppressPagination && (_jsx(TablePagination, { table: table, options: paginationProp === null || paginationProp === void 0 ? void 0 : paginationProp.options, optionsLabel: paginationProp === null || paginationProp === void 0 ? void 0 : paginationProp.optionsLabel, pageCount: pageCount }))] })) }));
|
|
118
144
|
}
|
|
119
145
|
Table.getStatusColumnDef = getStatusColumnDef;
|
|
120
146
|
Table.statusAppearances = STATUS_APPEARANCE;
|
|
@@ -3,7 +3,16 @@ import { DataAttributes } from '../types';
|
|
|
3
3
|
export type CellProps = {
|
|
4
4
|
children: ReactNode;
|
|
5
5
|
onClick?: MouseEventHandler;
|
|
6
|
+
onMouseUp?: MouseEventHandler;
|
|
6
7
|
className?: string;
|
|
7
8
|
style?: CSSProperties;
|
|
9
|
+
role?: 'cell' | 'columnheader';
|
|
8
10
|
} & DataAttributes;
|
|
9
|
-
export declare
|
|
11
|
+
export declare const Cell: import("react").ForwardRefExoticComponent<{
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
onClick?: MouseEventHandler | undefined;
|
|
14
|
+
onMouseUp?: MouseEventHandler | undefined;
|
|
15
|
+
className?: string | undefined;
|
|
16
|
+
style?: CSSProperties | undefined;
|
|
17
|
+
role?: "cell" | "columnheader" | undefined;
|
|
18
|
+
} & DataAttributes & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -11,10 +11,11 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
13
|
import cn from 'classnames';
|
|
14
|
+
import { forwardRef } from 'react';
|
|
14
15
|
import styles from './styles.module.css';
|
|
15
|
-
export
|
|
16
|
-
var { onClick, className, style, children } = _a, attributes = __rest(_a, ["onClick", "className", "style", "children"]);
|
|
16
|
+
export const Cell = forwardRef((_a, ref) => {
|
|
17
|
+
var { onClick, onMouseUp, className, style, children, role = 'cell' } = _a, attributes = __rest(_a, ["onClick", "onMouseUp", "className", "style", "children", "role"]);
|
|
17
18
|
return (
|
|
18
19
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
|
19
|
-
_jsx("div", Object.assign({ role:
|
|
20
|
-
}
|
|
20
|
+
_jsx("div", Object.assign({ role: role, onClick: onClick, onMouseUp: onMouseUp, className: cn(styles.tableCell, className), style: style, ref: ref }, attributes, { children: children })));
|
|
21
|
+
});
|
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { flexRender } from '@tanstack/react-table';
|
|
3
3
|
import cn from 'classnames';
|
|
4
|
+
import { useRef } from 'react';
|
|
4
5
|
import { TruncateString } from '@snack-uikit/truncate-string';
|
|
5
6
|
import { TEST_IDS } from '../../../constants';
|
|
6
7
|
import { useCellSizes } from '../../hooks';
|
|
7
8
|
import { Cell } from '../Cell';
|
|
8
9
|
import { getSortingIcon } from './helpers';
|
|
10
|
+
import { ResizeHandle } from './ResizeHandle';
|
|
9
11
|
import styles from './styles.module.css';
|
|
10
12
|
export function HeaderCell({ header, className }) {
|
|
13
|
+
const cellRef = useRef(null);
|
|
11
14
|
const isSortable = header.column.getCanSort();
|
|
15
|
+
const isResizable = header.column.getCanResize();
|
|
16
|
+
const isResizing = isResizable && header.column.getIsResizing();
|
|
12
17
|
const sortDirection = isSortable && (header.column.getIsSorted() || undefined);
|
|
13
18
|
const sortIcon = getSortingIcon(sortDirection);
|
|
19
|
+
const columnSizingInfo = header.getContext().table.getState().columnSizingInfo;
|
|
20
|
+
const isSomeColumnResizing = columnSizingInfo.isResizingColumn;
|
|
14
21
|
const columnDef = header.column.columnDef;
|
|
15
22
|
const style = useCellSizes(header);
|
|
16
|
-
|
|
23
|
+
const sortingHandler = (e) => {
|
|
24
|
+
var _a;
|
|
25
|
+
if (isSomeColumnResizing)
|
|
26
|
+
return;
|
|
27
|
+
return (_a = header.column.getToggleSortingHandler()) === null || _a === void 0 ? void 0 : _a(e);
|
|
28
|
+
};
|
|
29
|
+
return (_jsxs(Cell, { style: style, onMouseUp: sortingHandler, "data-sortable": isSortable || undefined, "data-no-padding": columnDef.noHeaderCellPadding || undefined, "data-no-offset": columnDef.noHeaderCellBorderOffset || undefined, "data-test-id": TEST_IDS.headerCell, "data-resizing": isResizing || undefined, role: 'columnheader', className: cn(styles.tableHeaderCell, className, columnDef.headerClassName), ref: cellRef, children: [_jsxs("div", { className: styles.tableHeaderCellMain, children: [columnDef.header && (_jsx("div", { className: styles.tableHeaderCellName, children: _jsx(TruncateString, { text: flexRender(columnDef.header, header.getContext()) }) })), Boolean(sortIcon) && (_jsx("div", { className: styles.tableHeaderIcon, "data-sort-direction": sortDirection, "data-test-id": TEST_IDS.headerSortIndicator, children: sortIcon }))] }), Boolean(isResizable) && _jsx(ResizeHandle, { header: header, cellRef: cellRef })] }));
|
|
17
30
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Header } from '@tanstack/react-table';
|
|
2
|
+
import { RefObject } from 'react';
|
|
3
|
+
type ResizeHandleProps<TData> = {
|
|
4
|
+
header: Header<TData, unknown>;
|
|
5
|
+
cellRef: RefObject<HTMLDivElement>;
|
|
6
|
+
};
|
|
7
|
+
export declare function ResizeHandle<TData>({ header, cellRef }: ResizeHandleProps<TData>): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import cn from 'classnames';
|
|
3
|
+
import styles from './styles.module.css';
|
|
4
|
+
function getResizeIndicatorOffset({ header, cellRef }) {
|
|
5
|
+
var _a;
|
|
6
|
+
const columnSizingInfo = header.getContext().table.getState().columnSizingInfo;
|
|
7
|
+
const { minSize, maxSize } = header.column.columnDef;
|
|
8
|
+
const { startSize, deltaOffset } = columnSizingInfo;
|
|
9
|
+
let offset = 0;
|
|
10
|
+
if (startSize !== null && deltaOffset !== null) {
|
|
11
|
+
const divElementSize = ((_a = cellRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) || 0;
|
|
12
|
+
const initSize = Math.max(startSize, divElementSize);
|
|
13
|
+
const limit = deltaOffset < 0 ? minSize : maxSize;
|
|
14
|
+
let deltaLimit = 0;
|
|
15
|
+
if (limit !== undefined && deltaOffset !== 0) {
|
|
16
|
+
deltaLimit = deltaOffset < 0 ? -(initSize - limit) : limit - initSize;
|
|
17
|
+
offset = deltaOffset < 0 ? Math.max(deltaOffset, deltaLimit) : Math.min(Math.abs(deltaOffset), deltaLimit);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return offset;
|
|
21
|
+
}
|
|
22
|
+
export function ResizeHandle({ header, cellRef }) {
|
|
23
|
+
const isResizing = header.column.getIsResizing();
|
|
24
|
+
const resizeHandler = header.getResizeHandler();
|
|
25
|
+
const offset = isResizing ? getResizeIndicatorOffset({ header, cellRef }) : 0;
|
|
26
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: cn(styles.tableHeaderIcon, styles.tableHeaderResizeHandle), "data-resizing": isResizing || undefined, onMouseDown: resizeHandler, onTouchStart: resizeHandler }), isResizing && (_jsx("div", { className: styles.tableHeaderResizeIndicator, style: {
|
|
27
|
+
transform: `translateX(${offset}px)`,
|
|
28
|
+
} }))] }));
|
|
29
|
+
}
|
|
@@ -1,8 +1,34 @@
|
|
|
1
|
+
.tableHeaderResizeHandle{
|
|
2
|
+
cursor:ew-resize;
|
|
3
|
+
position:absolute;
|
|
4
|
+
z-index:1;
|
|
5
|
+
top:0;
|
|
6
|
+
right:0;
|
|
7
|
+
transform:translateX(50%);
|
|
8
|
+
width:var(--dimension-1m, 8px);
|
|
9
|
+
height:100%;
|
|
10
|
+
opacity:0;
|
|
11
|
+
}
|
|
12
|
+
.tableHeaderResizeHandle::after{
|
|
13
|
+
content:"";
|
|
14
|
+
position:absolute;
|
|
15
|
+
top:0;
|
|
16
|
+
left:50%;
|
|
17
|
+
transform:translateX(-50%);
|
|
18
|
+
width:var(--border-width-table, 1px);
|
|
19
|
+
height:100%;
|
|
20
|
+
background-color:var(--sys-neutral-decor-hovered, #d2d2d2);
|
|
21
|
+
}
|
|
22
|
+
.tableHeaderResizeHandle[data-resizing]{
|
|
23
|
+
opacity:0;
|
|
24
|
+
}
|
|
25
|
+
|
|
1
26
|
.tableHeaderCell{
|
|
2
27
|
padding:var(--space-table-cell-padding, 8px);
|
|
3
28
|
position:relative;
|
|
4
29
|
display:flex;
|
|
5
30
|
align-items:center;
|
|
31
|
+
justify-content:space-between;
|
|
6
32
|
box-sizing:border-box;
|
|
7
33
|
width:100%;
|
|
8
34
|
background-color:inherit;
|
|
@@ -15,9 +41,12 @@
|
|
|
15
41
|
left:50%;
|
|
16
42
|
transform:translateX(-50%);
|
|
17
43
|
width:calc(100% - var(--space-table-head-separator-padding, 8px) * 2);
|
|
18
|
-
height:1px;
|
|
44
|
+
height:var(--border-width-table, 1px);
|
|
19
45
|
background-color:var(--sys-neutral-decor-default, #dedede);
|
|
20
46
|
}
|
|
47
|
+
.tableHeaderCell:hover .tableHeaderResizeHandle:not([data-resizing]){
|
|
48
|
+
opacity:1;
|
|
49
|
+
}
|
|
21
50
|
.tableHeaderCell[data-sortable]{
|
|
22
51
|
cursor:pointer;
|
|
23
52
|
}
|
|
@@ -32,6 +61,19 @@
|
|
|
32
61
|
.tableHeaderCell[data-no-padding]::after{
|
|
33
62
|
width:100%;
|
|
34
63
|
}
|
|
64
|
+
.tableHeaderCell[data-resizing]{
|
|
65
|
+
-webkit-user-select:none;
|
|
66
|
+
-moz-user-select:none;
|
|
67
|
+
user-select:none;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.tableHeaderCellMain{
|
|
71
|
+
overflow:auto;
|
|
72
|
+
display:flex;
|
|
73
|
+
align-items:center;
|
|
74
|
+
box-sizing:border-box;
|
|
75
|
+
min-width:0;
|
|
76
|
+
}
|
|
35
77
|
|
|
36
78
|
.tableHeaderCellName{
|
|
37
79
|
height:var(--size-table-head-name-layout, 24px);
|
|
@@ -42,12 +84,32 @@
|
|
|
42
84
|
color:var(--sys-neutral-text-light, #898989);
|
|
43
85
|
}
|
|
44
86
|
|
|
45
|
-
.
|
|
87
|
+
.tableHeaderIcon{
|
|
46
88
|
display:flex;
|
|
47
89
|
box-sizing:border-box;
|
|
48
90
|
color:var(--sys-neutral-text-light, #898989);
|
|
49
91
|
}
|
|
50
|
-
.
|
|
92
|
+
.tableHeaderIcon svg{
|
|
51
93
|
width:var(--dimension-2m, 16px) !important;
|
|
52
94
|
height:var(--dimension-2m, 16px) !important;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.tableHeaderResizeIndicator{
|
|
98
|
+
cursor:col-resize;
|
|
99
|
+
position:absolute;
|
|
100
|
+
z-index:2;
|
|
101
|
+
top:0;
|
|
102
|
+
right:0;
|
|
103
|
+
width:1px;
|
|
104
|
+
height:100%;
|
|
105
|
+
background-color:var(--sys-neutral-decor-activated, #b8b8b8);
|
|
106
|
+
}
|
|
107
|
+
.tableHeaderResizeIndicator::after{
|
|
108
|
+
content:"";
|
|
109
|
+
position:absolute;
|
|
110
|
+
top:0;
|
|
111
|
+
left:0;
|
|
112
|
+
transform:translateX(-50%);
|
|
113
|
+
width:calc(100% + var(--dimension-4m, 32px));
|
|
114
|
+
height:100%;
|
|
53
115
|
}
|
|
@@ -33,6 +33,7 @@ export function getRowActionsColumnDef({ actionsGenerator, pinned, }) {
|
|
|
33
33
|
},
|
|
34
34
|
noBodyCellPadding: true,
|
|
35
35
|
cellClassName: styles.rowActionsCell,
|
|
36
|
+
enableResizing: false,
|
|
36
37
|
cell: cell => _jsx(RowActionsCell, { row: cell.row, actions: actionsGenerator(cell) }),
|
|
37
38
|
};
|
|
38
39
|
}
|
|
@@ -15,6 +15,7 @@ type StatusColumnDef = BaseStatusColumnDef & {
|
|
|
15
15
|
renderDescription?: never;
|
|
16
16
|
size?: never;
|
|
17
17
|
header?: never;
|
|
18
|
+
enableResizing?: never;
|
|
18
19
|
};
|
|
19
20
|
type StatusColumnDefWithDescription<TData> = BaseStatusColumnDef & {
|
|
20
21
|
/** Функция для отрисовки текста, если не передана, то будет отрисован только индикатор статуса */
|
|
@@ -23,7 +24,9 @@ type StatusColumnDefWithDescription<TData> = BaseStatusColumnDef & {
|
|
|
23
24
|
size: number;
|
|
24
25
|
/** Заголовок колонки */
|
|
25
26
|
header?: ColumnDefinition<TData>['header'];
|
|
27
|
+
/** Включение/выключение ресайза колонки */
|
|
28
|
+
enableResizing?: boolean;
|
|
26
29
|
};
|
|
27
30
|
export type StatusColumnDefinitionProps<TData> = StatusColumnDef | StatusColumnDefWithDescription<TData>;
|
|
28
31
|
/** Вспомогательная функция для создания ячейки со статусом */
|
|
29
|
-
export declare function getStatusColumnDef<TData>({ header, accessorKey, mapStatusToAppearance, renderDescription, size, enableSorting, }: StatusColumnDefinitionProps<TData>): ColumnDefinition<TData>;
|
|
32
|
+
export declare function getStatusColumnDef<TData>({ header, accessorKey, mapStatusToAppearance, renderDescription, size, enableSorting, enableResizing, }: StatusColumnDefinitionProps<TData>): ColumnDefinition<TData>;
|
|
@@ -10,7 +10,7 @@ function StatusCell({ appearance, label }) {
|
|
|
10
10
|
return (_jsxs("div", { className: styles.statusCell, "data-no-label": !label || undefined, children: [_jsx("div", { "data-appearance": isLoading ? undefined : appearance, className: styles.statusCellIndicator, "data-loading": isLoading || undefined, "data-test-id": TEST_IDS.statusIndicator }), label && (_jsx("div", { className: styles.statusCellLabel, "data-test-id": TEST_IDS.statusLabel, children: _jsx(Typography.LightLabelS, { children: _jsx(TruncateString, { text: label }) }) }))] }));
|
|
11
11
|
}
|
|
12
12
|
/** Вспомогательная функция для создания ячейки со статусом */
|
|
13
|
-
export function getStatusColumnDef({ header, accessorKey, mapStatusToAppearance, renderDescription, size, enableSorting, }) {
|
|
13
|
+
export function getStatusColumnDef({ header, accessorKey, mapStatusToAppearance, renderDescription, size, enableSorting, enableResizing, }) {
|
|
14
14
|
const hasDescription = Boolean(renderDescription);
|
|
15
15
|
return {
|
|
16
16
|
id: 'snack_predefined_statusColumn',
|
|
@@ -26,6 +26,7 @@ export function getStatusColumnDef({ header, accessorKey, mapStatusToAppearance,
|
|
|
26
26
|
accessorKey,
|
|
27
27
|
enableSorting,
|
|
28
28
|
header: hasDescription ? header : undefined,
|
|
29
|
+
enableResizing: enableResizing !== null && enableResizing !== void 0 ? enableResizing : hasDescription,
|
|
29
30
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
31
|
accessorFn: (row) => renderDescription && Object.hasOwn(row, accessorKey)
|
|
31
32
|
? renderDescription(row[accessorKey])
|
|
@@ -7,5 +7,5 @@ import { Row } from './Row';
|
|
|
7
7
|
import styles from './styles.module.css';
|
|
8
8
|
export function HeaderRow() {
|
|
9
9
|
const { leftPinned, unpinned, rightPinned } = useHeaderGroups();
|
|
10
|
-
return (_jsxs(Row, { className: styles.tableHeader, "data-test-id": TEST_IDS.headerRow,
|
|
10
|
+
return (_jsxs(Row, { className: styles.tableHeader, "data-test-id": TEST_IDS.headerRow, children: [leftPinned && (_jsx(PinnedCells, { position: COLUMN_PIN_POSITION.Left, children: leftPinned.map(headerGroup => headerGroup.headers.map(header => header.isPlaceholder ? null : _jsx(HeaderCell, { header: header }, header.id))) })), unpinned.map(headerGroup => headerGroup.headers.map(header => _jsx(HeaderCell, { header: header }, header.id))), rightPinned && (_jsx(PinnedCells, { position: COLUMN_PIN_POSITION.Right, children: rightPinned.map(headerGroup => headerGroup.headers.map(header => header.isPlaceholder ? null : _jsx(HeaderCell, { header: header }, header.id))) }))] }));
|
|
11
11
|
}
|
|
@@ -4,7 +4,6 @@ type RowProps = {
|
|
|
4
4
|
children: ReactNode;
|
|
5
5
|
onClick?(e: MouseEvent<HTMLDivElement>): void;
|
|
6
6
|
className?: string;
|
|
7
|
-
role?: 'rowheader' | 'row';
|
|
8
7
|
} & DataAttributes;
|
|
9
|
-
export declare function Row({ onClick,
|
|
8
|
+
export declare function Row({ onClick, children, className, ...attributes }: RowProps): import("react/jsx-runtime").JSX.Element;
|
|
10
9
|
export {};
|
|
@@ -13,6 +13,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
13
13
|
import cn from 'classnames';
|
|
14
14
|
import styles from './styles.module.css';
|
|
15
15
|
export function Row(_a) {
|
|
16
|
-
var { onClick,
|
|
17
|
-
return (
|
|
16
|
+
var { onClick, children, className } = _a, attributes = __rest(_a, ["onClick", "children", "className"]);
|
|
17
|
+
return (
|
|
18
|
+
// eslint-disable-next-line jsx-a11y/interactive-supports-focus
|
|
19
|
+
_jsx("div", Object.assign({ onClick: onClick, className: cn(styles.tableRow, className), role: 'row' }, attributes, { children: children })));
|
|
18
20
|
}
|
|
@@ -18,11 +18,8 @@ export declare function useRowCells<TData>(row: Row<TData>): {
|
|
|
18
18
|
unpinned: Cell<TData, unknown>[];
|
|
19
19
|
};
|
|
20
20
|
export declare function useCellSizes<TData>(element: Cell<TData, unknown> | Header<TData, unknown>): {
|
|
21
|
-
width: number;
|
|
22
|
-
minWidth?: undefined;
|
|
23
|
-
maxWidth?: undefined;
|
|
24
|
-
} | {
|
|
25
21
|
minWidth: number | undefined;
|
|
26
|
-
width:
|
|
22
|
+
width: string;
|
|
27
23
|
maxWidth: number | undefined;
|
|
24
|
+
flexShrink: string;
|
|
28
25
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
import { useTableContext } from './contexts';
|
|
3
|
-
import { getColumnId } from './helpers';
|
|
4
3
|
function hasHeaders(groups) {
|
|
5
4
|
return groups.some(group => group.headers.length);
|
|
6
5
|
}
|
|
@@ -47,21 +46,15 @@ export function useRowCells(row) {
|
|
|
47
46
|
}, [row, pinEnabled, columnDefs]);
|
|
48
47
|
}
|
|
49
48
|
export function useCellSizes(element) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
minWidth: (originalColumnDef === null || originalColumnDef === void 0 ? void 0 : originalColumnDef.size) || (originalColumnDef === null || originalColumnDef === void 0 ? void 0 : originalColumnDef.minSize) || column.columnDef.minSize,
|
|
63
|
-
width: originalColumnDef === null || originalColumnDef === void 0 ? void 0 : originalColumnDef.size,
|
|
64
|
-
maxWidth: (originalColumnDef === null || originalColumnDef === void 0 ? void 0 : originalColumnDef.maxSize) || column.columnDef.maxSize,
|
|
65
|
-
};
|
|
66
|
-
}, [element]);
|
|
49
|
+
const column = element.column;
|
|
50
|
+
const minWidth = column.columnDef.minSize;
|
|
51
|
+
const maxWidth = column.columnDef.maxSize;
|
|
52
|
+
const width = `var(--table-column-${column.id}-size)`;
|
|
53
|
+
const flexShrink = `var(--table-column-${column.id}-flex)`;
|
|
54
|
+
return useMemo(() => ({
|
|
55
|
+
minWidth,
|
|
56
|
+
width,
|
|
57
|
+
maxWidth,
|
|
58
|
+
flexShrink,
|
|
59
|
+
}), [flexShrink, maxWidth, minWidth, width]);
|
|
67
60
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { COLUMN_ALIGN, COLUMN_PIN_POSITION } from './constants';
|
|
|
6
6
|
import { Except } from './helperComponents';
|
|
7
7
|
type ColumnAlign = ValueOf<typeof COLUMN_ALIGN>;
|
|
8
8
|
type ColumnPinPosition = ValueOf<typeof COLUMN_PIN_POSITION>;
|
|
9
|
-
type BaseColumnDefinition<TData> = Except<ColumnDef<TData>, 'footer' | 'enablePinning' | 'enableGrouping' | '
|
|
9
|
+
type BaseColumnDefinition<TData> = Except<ColumnDef<TData>, 'footer' | 'enablePinning' | 'enableGrouping' | 'enableColumnFilter' | 'filterFn' | 'enableGlobalFilter' | 'enableMultiSort' | 'enableHiding'> & {
|
|
10
10
|
/** Заголовок колонки */
|
|
11
11
|
header?: string | ((ctx: HeaderContext<TData, unknown>) => string);
|
|
12
12
|
/** Позиционирование контента ячейки в теле таблицы */
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
6
|
"title": "Table",
|
|
7
|
-
"version": "0.
|
|
7
|
+
"version": "0.9.0",
|
|
8
8
|
"sideEffects": [
|
|
9
9
|
"*.css",
|
|
10
10
|
"*.woff",
|
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
"@snack-uikit/typography": "0.6.1",
|
|
47
47
|
"@snack-uikit/utils": "3.2.0",
|
|
48
48
|
"@tanstack/match-sorter-utils": "8.8.4",
|
|
49
|
-
"@tanstack/react-table": "8.
|
|
49
|
+
"@tanstack/react-table": "8.11.6",
|
|
50
50
|
"classnames": "2.3.2",
|
|
51
51
|
"uncontrollable": "8.0.0",
|
|
52
52
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.1/xlsx-0.20.1.tgz"
|
|
53
53
|
},
|
|
54
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "b8ffb981d1933d3798b28e05095f25538768ffd4"
|
|
55
55
|
}
|
|
@@ -243,6 +243,8 @@ export function Table<TData extends object>({
|
|
|
243
243
|
|
|
244
244
|
defaultColumn: {
|
|
245
245
|
enableSorting: false,
|
|
246
|
+
enableResizing: false,
|
|
247
|
+
minSize: 40,
|
|
246
248
|
},
|
|
247
249
|
|
|
248
250
|
globalFilterFn: fuzzyFilter,
|
|
@@ -255,6 +257,8 @@ export function Table<TData extends object>({
|
|
|
255
257
|
enableFilters: true,
|
|
256
258
|
getFilteredRowModel: getFilteredRowModel(),
|
|
257
259
|
|
|
260
|
+
enableColumnResizing: true,
|
|
261
|
+
|
|
258
262
|
enableSorting: true,
|
|
259
263
|
manualSorting: false,
|
|
260
264
|
enableMultiSort: false,
|
|
@@ -296,6 +300,36 @@ export function Table<TData extends object>({
|
|
|
296
300
|
}
|
|
297
301
|
}, [loading, rowSelectionProp?.multiRow, table]);
|
|
298
302
|
|
|
303
|
+
const columnSizeVars = useMemo(() => {
|
|
304
|
+
const originalColumnDefs = table._getColumnDefs();
|
|
305
|
+
const headers = table.getFlatHeaders();
|
|
306
|
+
const colSizes: { [key: string]: string } = {};
|
|
307
|
+
|
|
308
|
+
for (let i = 0; i < headers.length; i++) {
|
|
309
|
+
const header = headers[i];
|
|
310
|
+
const originalColDef = originalColumnDefs.find(col => getColumnId(header) === col.id);
|
|
311
|
+
const originalColumnDefSize = originalColDef?.size;
|
|
312
|
+
const initSize = originalColumnDefSize ? `${originalColumnDefSize}px` : '100%';
|
|
313
|
+
|
|
314
|
+
let size = initSize;
|
|
315
|
+
|
|
316
|
+
if (header.column.getCanResize()) {
|
|
317
|
+
const currentSize = header.getSize();
|
|
318
|
+
const colDefSize = header.column.columnDef.size;
|
|
319
|
+
|
|
320
|
+
size = currentSize === colDefSize ? initSize : `${currentSize}px`;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
colSizes[`--table-column-${header.id}-size`] = size;
|
|
324
|
+
colSizes[`--table-column-${header.id}-flex`] = size === '100%' ? 'unset' : '0';
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return colSizes;
|
|
328
|
+
/* effect must be called only on columnSizingInfo.isResizingColumn changes
|
|
329
|
+
to reduce unnecessary recalculations */
|
|
330
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
331
|
+
}, [table.getState().columnSizingInfo.isResizingColumn]);
|
|
332
|
+
|
|
299
333
|
const tableRows = table.getRowModel().rows;
|
|
300
334
|
const loadingTableRows = loadingTable.getRowModel().rows;
|
|
301
335
|
const tablePagination = table.getState().pagination;
|
|
@@ -336,7 +370,7 @@ export function Table<TData extends object>({
|
|
|
336
370
|
|
|
337
371
|
<div className={styles.scrollWrapper} data-outline={outline || undefined}>
|
|
338
372
|
<Scroll size='s' className={styles.table}>
|
|
339
|
-
<div className={styles.tableContent}>
|
|
373
|
+
<div className={styles.tableContent} style={columnSizeVars}>
|
|
340
374
|
<TableContext.Provider value={{ table }}>
|
|
341
375
|
{loading ? (
|
|
342
376
|
<SkeletonContextProvider loading>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import cn from 'classnames';
|
|
2
|
-
import { CSSProperties, MouseEventHandler, ReactNode } from 'react';
|
|
2
|
+
import { CSSProperties, forwardRef, MouseEventHandler, ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
import { DataAttributes } from '../types';
|
|
5
5
|
import styles from './styles.module.scss';
|
|
@@ -7,15 +7,25 @@ import styles from './styles.module.scss';
|
|
|
7
7
|
export type CellProps = {
|
|
8
8
|
children: ReactNode;
|
|
9
9
|
onClick?: MouseEventHandler;
|
|
10
|
+
onMouseUp?: MouseEventHandler;
|
|
10
11
|
className?: string;
|
|
11
12
|
style?: CSSProperties;
|
|
13
|
+
role?: 'cell' | 'columnheader';
|
|
12
14
|
} & DataAttributes;
|
|
13
15
|
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
+
export const Cell = forwardRef<HTMLDivElement, CellProps>(
|
|
17
|
+
({ onClick, onMouseUp, className, style, children, role = 'cell', ...attributes }, ref) => (
|
|
16
18
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
|
17
|
-
<div
|
|
19
|
+
<div
|
|
20
|
+
role={role}
|
|
21
|
+
onClick={onClick}
|
|
22
|
+
onMouseUp={onMouseUp}
|
|
23
|
+
className={cn(styles.tableCell, className)}
|
|
24
|
+
style={style}
|
|
25
|
+
ref={ref}
|
|
26
|
+
{...attributes}
|
|
27
|
+
>
|
|
18
28
|
{children}
|
|
19
29
|
</div>
|
|
20
|
-
)
|
|
21
|
-
|
|
30
|
+
),
|
|
31
|
+
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { flexRender, Header } from '@tanstack/react-table';
|
|
2
2
|
import cn from 'classnames';
|
|
3
|
+
import { MouseEvent, useRef } from 'react';
|
|
3
4
|
|
|
4
5
|
import { TruncateString } from '@snack-uikit/truncate-string';
|
|
5
6
|
|
|
@@ -8,6 +9,7 @@ import { ColumnDefinition } from '../../../types';
|
|
|
8
9
|
import { useCellSizes } from '../../hooks';
|
|
9
10
|
import { Cell, CellProps } from '../Cell';
|
|
10
11
|
import { getSortingIcon } from './helpers';
|
|
12
|
+
import { ResizeHandle } from './ResizeHandle';
|
|
11
13
|
import styles from './styles.module.scss';
|
|
12
14
|
|
|
13
15
|
type HeaderCellProps<TData> = Omit<CellProps, 'align' | 'children' | 'onClick' | 'style'> & {
|
|
@@ -15,39 +17,59 @@ type HeaderCellProps<TData> = Omit<CellProps, 'align' | 'children' | 'onClick' |
|
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
export function HeaderCell<TData>({ header, className }: HeaderCellProps<TData>) {
|
|
20
|
+
const cellRef = useRef<HTMLDivElement>(null);
|
|
18
21
|
const isSortable = header.column.getCanSort();
|
|
22
|
+
const isResizable = header.column.getCanResize();
|
|
23
|
+
const isResizing = isResizable && header.column.getIsResizing();
|
|
24
|
+
|
|
19
25
|
const sortDirection = isSortable && (header.column.getIsSorted() || undefined);
|
|
20
26
|
const sortIcon = getSortingIcon(sortDirection);
|
|
21
27
|
|
|
28
|
+
const columnSizingInfo = header.getContext().table.getState().columnSizingInfo;
|
|
29
|
+
const isSomeColumnResizing = columnSizingInfo.isResizingColumn;
|
|
30
|
+
|
|
22
31
|
const columnDef: ColumnDefinition<TData> = header.column.columnDef;
|
|
23
32
|
|
|
24
33
|
const style = useCellSizes(header);
|
|
25
34
|
|
|
35
|
+
const sortingHandler = (e: MouseEvent<HTMLDivElement>) => {
|
|
36
|
+
if (isSomeColumnResizing) return;
|
|
37
|
+
|
|
38
|
+
return header.column.getToggleSortingHandler()?.(e);
|
|
39
|
+
};
|
|
40
|
+
|
|
26
41
|
return (
|
|
27
42
|
<Cell
|
|
28
43
|
style={style}
|
|
29
|
-
|
|
44
|
+
onMouseUp={sortingHandler}
|
|
30
45
|
data-sortable={isSortable || undefined}
|
|
31
46
|
data-no-padding={columnDef.noHeaderCellPadding || undefined}
|
|
32
47
|
data-no-offset={columnDef.noHeaderCellBorderOffset || undefined}
|
|
33
48
|
data-test-id={TEST_IDS.headerCell}
|
|
49
|
+
data-resizing={isResizing || undefined}
|
|
50
|
+
role='columnheader'
|
|
34
51
|
className={cn(styles.tableHeaderCell, className, columnDef.headerClassName)}
|
|
52
|
+
ref={cellRef}
|
|
35
53
|
>
|
|
36
|
-
{
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
<div className={styles.tableHeaderCellMain}>
|
|
55
|
+
{columnDef.header && (
|
|
56
|
+
<div className={styles.tableHeaderCellName}>
|
|
57
|
+
<TruncateString text={flexRender(columnDef.header, header.getContext()) as string} />
|
|
58
|
+
</div>
|
|
59
|
+
)}
|
|
60
|
+
|
|
61
|
+
{Boolean(sortIcon) && (
|
|
62
|
+
<div
|
|
63
|
+
className={styles.tableHeaderIcon}
|
|
64
|
+
data-sort-direction={sortDirection}
|
|
65
|
+
data-test-id={TEST_IDS.headerSortIndicator}
|
|
66
|
+
>
|
|
67
|
+
{sortIcon}
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
{Boolean(isResizable) && <ResizeHandle header={header} cellRef={cellRef} />}
|
|
51
73
|
</Cell>
|
|
52
74
|
);
|
|
53
75
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Header } from '@tanstack/react-table';
|
|
2
|
+
import cn from 'classnames';
|
|
3
|
+
import { RefObject } from 'react';
|
|
4
|
+
|
|
5
|
+
import styles from './styles.module.scss';
|
|
6
|
+
|
|
7
|
+
type ResizeHandleProps<TData> = {
|
|
8
|
+
header: Header<TData, unknown>;
|
|
9
|
+
cellRef: RefObject<HTMLDivElement>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function getResizeIndicatorOffset<TData>({ header, cellRef }: ResizeHandleProps<TData>) {
|
|
13
|
+
const columnSizingInfo = header.getContext().table.getState().columnSizingInfo;
|
|
14
|
+
|
|
15
|
+
const { minSize, maxSize } = header.column.columnDef;
|
|
16
|
+
const { startSize, deltaOffset } = columnSizingInfo;
|
|
17
|
+
|
|
18
|
+
let offset = 0;
|
|
19
|
+
|
|
20
|
+
if (startSize !== null && deltaOffset !== null) {
|
|
21
|
+
const divElementSize = cellRef.current?.offsetWidth || 0;
|
|
22
|
+
const initSize = Math.max(startSize, divElementSize);
|
|
23
|
+
|
|
24
|
+
const limit = deltaOffset < 0 ? minSize : maxSize;
|
|
25
|
+
let deltaLimit = 0;
|
|
26
|
+
|
|
27
|
+
if (limit !== undefined && deltaOffset !== 0) {
|
|
28
|
+
deltaLimit = deltaOffset < 0 ? -(initSize - limit) : limit - initSize;
|
|
29
|
+
|
|
30
|
+
offset = deltaOffset < 0 ? Math.max(deltaOffset, deltaLimit) : Math.min(Math.abs(deltaOffset), deltaLimit);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return offset;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function ResizeHandle<TData>({ header, cellRef }: ResizeHandleProps<TData>) {
|
|
38
|
+
const isResizing = header.column.getIsResizing();
|
|
39
|
+
const resizeHandler = header.getResizeHandler();
|
|
40
|
+
|
|
41
|
+
const offset = isResizing ? getResizeIndicatorOffset({ header, cellRef }) : 0;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<>
|
|
45
|
+
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
|
46
|
+
<div
|
|
47
|
+
className={cn(styles.tableHeaderIcon, styles.tableHeaderResizeHandle)}
|
|
48
|
+
data-resizing={isResizing || undefined}
|
|
49
|
+
onMouseDown={resizeHandler}
|
|
50
|
+
onTouchStart={resizeHandler}
|
|
51
|
+
/>
|
|
52
|
+
|
|
53
|
+
{isResizing && (
|
|
54
|
+
<div
|
|
55
|
+
className={styles.tableHeaderResizeIndicator}
|
|
56
|
+
style={{
|
|
57
|
+
transform: `translateX(${offset}px)`,
|
|
58
|
+
}}
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
61
|
+
</>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-table';
|
|
2
2
|
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
|
|
3
3
|
|
|
4
|
+
.tableHeaderResizeHandle {
|
|
5
|
+
cursor: ew-resize;
|
|
6
|
+
|
|
7
|
+
position: absolute;
|
|
8
|
+
z-index: 1;
|
|
9
|
+
top: 0;
|
|
10
|
+
right: 0;
|
|
11
|
+
transform: translateX(50%);
|
|
12
|
+
|
|
13
|
+
width: $dimension-1m;
|
|
14
|
+
height: 100%;
|
|
15
|
+
|
|
16
|
+
opacity: 0;
|
|
17
|
+
|
|
18
|
+
&::after {
|
|
19
|
+
content: '';
|
|
20
|
+
|
|
21
|
+
position: absolute;
|
|
22
|
+
top: 0;
|
|
23
|
+
left: 50%;
|
|
24
|
+
transform: translateX(-50%);
|
|
25
|
+
|
|
26
|
+
width: $border-width-table;
|
|
27
|
+
height: 100%;
|
|
28
|
+
|
|
29
|
+
background-color: $sys-neutral-decor-hovered;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&[data-resizing] {
|
|
33
|
+
opacity: 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
4
37
|
.tableHeaderCell {
|
|
5
38
|
@include composite-var($table-head-column);
|
|
6
39
|
|
|
@@ -8,6 +41,7 @@
|
|
|
8
41
|
|
|
9
42
|
display: flex;
|
|
10
43
|
align-items: center;
|
|
44
|
+
justify-content: space-between;
|
|
11
45
|
|
|
12
46
|
box-sizing: border-box;
|
|
13
47
|
width: 100%;
|
|
@@ -24,11 +58,17 @@
|
|
|
24
58
|
transform: translateX(-50%);
|
|
25
59
|
|
|
26
60
|
width: calc(100% - $space-table-head-separator-padding * 2);
|
|
27
|
-
height:
|
|
61
|
+
height: $border-width-table;
|
|
28
62
|
|
|
29
63
|
background-color: $sys-neutral-decor-default;
|
|
30
64
|
}
|
|
31
65
|
|
|
66
|
+
&:hover {
|
|
67
|
+
.tableHeaderResizeHandle:not([data-resizing]) {
|
|
68
|
+
opacity: 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
32
72
|
&[data-sortable] {
|
|
33
73
|
cursor: pointer;
|
|
34
74
|
}
|
|
@@ -48,6 +88,19 @@
|
|
|
48
88
|
width: 100%;
|
|
49
89
|
}
|
|
50
90
|
}
|
|
91
|
+
|
|
92
|
+
&[data-resizing] {
|
|
93
|
+
user-select: none;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.tableHeaderCellMain {
|
|
98
|
+
overflow: auto;
|
|
99
|
+
display: flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
|
|
102
|
+
box-sizing: border-box;
|
|
103
|
+
min-width: 0;
|
|
51
104
|
}
|
|
52
105
|
|
|
53
106
|
.tableHeaderCellName {
|
|
@@ -62,7 +115,7 @@
|
|
|
62
115
|
color: simple-var($sys-neutral-text-light);
|
|
63
116
|
}
|
|
64
117
|
|
|
65
|
-
.
|
|
118
|
+
.tableHeaderIcon {
|
|
66
119
|
display: flex;
|
|
67
120
|
box-sizing: border-box;
|
|
68
121
|
color: simple-var($sys-neutral-text-light);
|
|
@@ -72,3 +125,29 @@
|
|
|
72
125
|
height: simple-var($icon-xs) !important; /* stylelint-disable-line declaration-no-important */
|
|
73
126
|
}
|
|
74
127
|
}
|
|
128
|
+
|
|
129
|
+
.tableHeaderResizeIndicator {
|
|
130
|
+
cursor: col-resize;
|
|
131
|
+
|
|
132
|
+
position: absolute;
|
|
133
|
+
z-index: 2;
|
|
134
|
+
top: 0;
|
|
135
|
+
right: 0;
|
|
136
|
+
|
|
137
|
+
width: 1px;
|
|
138
|
+
height: 100%;
|
|
139
|
+
|
|
140
|
+
background-color: $sys-neutral-decor-activated;
|
|
141
|
+
|
|
142
|
+
&::after {
|
|
143
|
+
content: '';
|
|
144
|
+
|
|
145
|
+
position: absolute;
|
|
146
|
+
top: 0;
|
|
147
|
+
left: 0;
|
|
148
|
+
transform: translateX(-50%);
|
|
149
|
+
|
|
150
|
+
width: calc(100% + $dimension-4m);
|
|
151
|
+
height: 100%;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -34,6 +34,7 @@ export function getSelectionCellColumnDef<TData>(): ColumnDefinition<TData> {
|
|
|
34
34
|
noBodyCellPadding: true,
|
|
35
35
|
size: 40,
|
|
36
36
|
headerClassName: styles.selectionCellHeader,
|
|
37
|
+
enableResizing: false,
|
|
37
38
|
cell: ({ row, table }) => {
|
|
38
39
|
const disabled = !row.getCanSelect();
|
|
39
40
|
|
|
@@ -29,6 +29,7 @@ type StatusColumnDef = BaseStatusColumnDef & {
|
|
|
29
29
|
renderDescription?: never;
|
|
30
30
|
size?: never;
|
|
31
31
|
header?: never;
|
|
32
|
+
enableResizing?: never;
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
type StatusColumnDefWithDescription<TData> = BaseStatusColumnDef & {
|
|
@@ -38,6 +39,8 @@ type StatusColumnDefWithDescription<TData> = BaseStatusColumnDef & {
|
|
|
38
39
|
size: number;
|
|
39
40
|
/** Заголовок колонки */
|
|
40
41
|
header?: ColumnDefinition<TData>['header'];
|
|
42
|
+
/** Включение/выключение ресайза колонки */
|
|
43
|
+
enableResizing?: boolean;
|
|
41
44
|
};
|
|
42
45
|
|
|
43
46
|
export type StatusColumnDefinitionProps<TData> = StatusColumnDef | StatusColumnDefWithDescription<TData>;
|
|
@@ -73,6 +76,7 @@ export function getStatusColumnDef<TData>({
|
|
|
73
76
|
renderDescription,
|
|
74
77
|
size,
|
|
75
78
|
enableSorting,
|
|
79
|
+
enableResizing,
|
|
76
80
|
}: StatusColumnDefinitionProps<TData>): ColumnDefinition<TData> {
|
|
77
81
|
const hasDescription = Boolean(renderDescription);
|
|
78
82
|
|
|
@@ -90,6 +94,7 @@ export function getStatusColumnDef<TData>({
|
|
|
90
94
|
accessorKey,
|
|
91
95
|
enableSorting,
|
|
92
96
|
header: hasDescription ? header : undefined,
|
|
97
|
+
enableResizing: enableResizing ?? hasDescription,
|
|
93
98
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
94
99
|
accessorFn: (row: any) =>
|
|
95
100
|
renderDescription && Object.hasOwn(row as object, accessorKey)
|
|
@@ -9,7 +9,7 @@ export function HeaderRow() {
|
|
|
9
9
|
const { leftPinned, unpinned, rightPinned } = useHeaderGroups();
|
|
10
10
|
|
|
11
11
|
return (
|
|
12
|
-
<Row className={styles.tableHeader} data-test-id={TEST_IDS.headerRow}
|
|
12
|
+
<Row className={styles.tableHeader} data-test-id={TEST_IDS.headerRow}>
|
|
13
13
|
{leftPinned && (
|
|
14
14
|
<PinnedCells position={COLUMN_PIN_POSITION.Left}>
|
|
15
15
|
{leftPinned.map(headerGroup =>
|
|
@@ -8,12 +8,12 @@ type RowProps = {
|
|
|
8
8
|
children: ReactNode;
|
|
9
9
|
onClick?(e: MouseEvent<HTMLDivElement>): void;
|
|
10
10
|
className?: string;
|
|
11
|
-
role?: 'rowheader' | 'row';
|
|
12
11
|
} & DataAttributes;
|
|
13
12
|
|
|
14
|
-
export function Row({ onClick,
|
|
13
|
+
export function Row({ onClick, children, className, ...attributes }: RowProps) {
|
|
15
14
|
return (
|
|
16
|
-
|
|
15
|
+
// eslint-disable-next-line jsx-a11y/interactive-supports-focus
|
|
16
|
+
<div onClick={onClick} className={cn(styles.tableRow, className)} role='row' {...attributes}>
|
|
17
17
|
{children}
|
|
18
18
|
</div>
|
|
19
19
|
);
|
|
@@ -2,7 +2,6 @@ import { Cell, Header, HeaderGroup, Row } from '@tanstack/react-table';
|
|
|
2
2
|
import { useMemo } from 'react';
|
|
3
3
|
|
|
4
4
|
import { useTableContext } from './contexts';
|
|
5
|
-
import { getColumnId } from './helpers';
|
|
6
5
|
|
|
7
6
|
function hasHeaders<TData>(groups: HeaderGroup<TData>[]) {
|
|
8
7
|
return groups.some(group => group.headers.length);
|
|
@@ -61,24 +60,20 @@ export function useRowCells<TData>(row: Row<TData>) {
|
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
export function useCellSizes<TData>(element: Cell<TData, unknown> | Header<TData, unknown>) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
width: originalColumnDef?.size,
|
|
81
|
-
maxWidth: originalColumnDef?.maxSize || column.columnDef.maxSize,
|
|
82
|
-
};
|
|
83
|
-
}, [element]);
|
|
63
|
+
const column = element.column;
|
|
64
|
+
|
|
65
|
+
const minWidth = column.columnDef.minSize;
|
|
66
|
+
const maxWidth = column.columnDef.maxSize;
|
|
67
|
+
const width = `var(--table-column-${column.id}-size)`;
|
|
68
|
+
const flexShrink = `var(--table-column-${column.id}-flex)`;
|
|
69
|
+
|
|
70
|
+
return useMemo(
|
|
71
|
+
() => ({
|
|
72
|
+
minWidth,
|
|
73
|
+
width,
|
|
74
|
+
maxWidth,
|
|
75
|
+
flexShrink,
|
|
76
|
+
}),
|
|
77
|
+
[flexShrink, maxWidth, minWidth, width],
|
|
78
|
+
);
|
|
84
79
|
}
|