Skip to content

Commit ff27390

Browse files
feat: make tables resizeable (#823)
1 parent 2dbf0f0 commit ff27390

File tree

81 files changed

+665
-520
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+665
-520
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"@gravity-ui/icons": "^2.9.1",
2020
"@gravity-ui/navigation": "^2.7.0",
2121
"@gravity-ui/paranoid": "^1.4.1",
22-
"@gravity-ui/react-data-table": "^2.0.1",
22+
"@gravity-ui/react-data-table": "^2.1.1",
2323
"@gravity-ui/uikit": "^6.10.2",
2424
"@gravity-ui/websql-autocomplete": "^8.1.0",
2525
"@reduxjs/toolkit": "^2.2.3",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
.date-range {
22
&__input {
33
min-width: 190px;
4+
height: 28px;
45
padding: 5px 8px;
56

67
color: var(--g-color-text-primary);
78
border: 1px solid var(--g-color-line-generic);
89
border-radius: var(--g-border-radius-m);
10+
outline: none;
911
background: transparent;
1012
}
13+
14+
&__input:focus,
15+
&__input:focus-visible {
16+
border: 1px solid var(--g-color-line-generic-hover);
17+
}
1118
}

src/components/NodeHostWrapper/NodeHostWrapper.scss

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
11
.ydb-node-host-wrapper {
2-
&__host-wrapper {
3-
display: flex;
4-
5-
width: 330px;
6-
}
7-
82
&__host {
93
overflow: hidden;
104
}

src/components/NodeHostWrapper/NodeHostWrapper.tsx

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,18 @@ export const NodeHostWrapper = ({node, getNodeRef}: NodeHostWrapperProps) => {
3939
placement={['top', 'bottom']}
4040
behavior={PopoverBehavior.Immediate}
4141
>
42-
<div className={b('host-wrapper')}>
43-
<EntityStatus
44-
name={node.Host}
45-
status={node.SystemState}
46-
path={nodePath}
47-
hasClipboardButton
48-
className={b('host')}
49-
/>
50-
{nodeRef && (
51-
<Button
52-
size="s"
53-
href={nodeRef}
54-
className={b('external-button')}
55-
target="_blank"
56-
>
57-
<Icon name="external" />
58-
</Button>
59-
)}
60-
</div>
42+
<EntityStatus
43+
name={node.Host}
44+
status={node.SystemState}
45+
path={nodePath}
46+
hasClipboardButton
47+
className={b('host')}
48+
/>
49+
{nodeRef && (
50+
<Button size="s" href={nodeRef} className={b('external-button')} target="_blank">
51+
<Icon name="external" />
52+
</Button>
53+
)}
6154
</CellWithPopover>
6255
);
6356
};

src/components/QueryResultTable/QueryResultTable.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
.ydb-query-result-table {
44
&__cell {
5+
width: 100%;
56
@include cell-container;
67
}
78

src/components/QueryResultTable/QueryResultTable.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import React from 'react';
22

33
import DataTable from '@gravity-ui/react-data-table';
4-
import type {Column, DataTableProps, Settings} from '@gravity-ui/react-data-table';
4+
import type {Column, Settings} from '@gravity-ui/react-data-table';
55

66
import type {ColumnType, KeyValueRow} from '../../types/api/query';
77
import {cn} from '../../utils/cn';
88
import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants';
99
import {getColumnType, prepareQueryResponse} from '../../utils/query';
1010
import {isNumeric} from '../../utils/utils';
11+
import type {ResizeableDataTableProps} from '../ResizeableDataTable/ResizeableDataTable';
12+
import {ResizeableDataTable} from '../ResizeableDataTable/ResizeableDataTable';
1113

1214
import {Cell} from './Cell';
1315
import i18n from './i18n';
@@ -70,7 +72,7 @@ const prepareGenericColumns = (data: KeyValueRow[]) => {
7072
const getRowIndex = (_: unknown, index: number) => index;
7173

7274
interface QueryResultTableProps
73-
extends Omit<DataTableProps<KeyValueRow>, 'data' | 'columns' | 'theme'> {
75+
extends Omit<ResizeableDataTableProps<KeyValueRow>, 'data' | 'columns'> {
7476
data?: KeyValueRow[];
7577
columns?: ColumnType[];
7678
}
@@ -101,8 +103,7 @@ export const QueryResultTable = (props: QueryResultTableProps) => {
101103
}
102104

103105
return (
104-
<DataTable
105-
theme="yandex-cloud"
106+
<ResizeableDataTable
106107
data={data}
107108
columns={columns}
108109
settings={settings}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.ydb-resizeable-data-table {
2+
display: flex;
3+
4+
width: max-content;
5+
6+
// padding for easier resize of the last column
7+
padding-right: 20px;
8+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type {DataTableProps, Settings} from '@gravity-ui/react-data-table';
2+
import DataTable, {updateColumnsWidth} from '@gravity-ui/react-data-table';
3+
4+
import {cn} from '../../utils/cn';
5+
import {useTableResize} from '../../utils/hooks/useTableResize';
6+
7+
import './ResizeableDataTable.scss';
8+
9+
const b = cn('ydb-resizeable-data-table');
10+
11+
export interface ResizeableDataTableProps<T> extends Omit<DataTableProps<T>, 'theme' | 'onResize'> {
12+
columnsWidthLSKey?: string;
13+
wrapperClassName?: string;
14+
}
15+
16+
export function ResizeableDataTable<T>({
17+
columnsWidthLSKey,
18+
columns,
19+
settings,
20+
wrapperClassName,
21+
...props
22+
}: ResizeableDataTableProps<T>) {
23+
const [tableColumnsWidth, setTableColumnsWidth] = useTableResize(columnsWidthLSKey);
24+
25+
const updatedColumns = updateColumnsWidth(columns, tableColumnsWidth);
26+
27+
const newSettings: Settings = {
28+
...settings,
29+
defaultResizeable: true,
30+
};
31+
32+
return (
33+
<div className={b(null, wrapperClassName)}>
34+
<DataTable
35+
theme="yandex-cloud"
36+
columns={updatedColumns}
37+
onResize={setTableColumnsWidth}
38+
settings={newSettings}
39+
{...props}
40+
/>
41+
</div>
42+
);
43+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import React from 'react';
2+
3+
import {b} from './shared';
4+
import {calculateColumnWidth, rafThrottle} from './utils';
5+
6+
interface ResizeHandlerProps {
7+
maxWidth?: number;
8+
minWidth?: number;
9+
getCurrentColumnWidth: () => number | undefined;
10+
onResize?: (width: number) => void;
11+
}
12+
13+
export function ResizeHandler({
14+
minWidth,
15+
maxWidth,
16+
getCurrentColumnWidth,
17+
onResize,
18+
}: ResizeHandlerProps) {
19+
const elementRef = React.useRef<HTMLElement>(null);
20+
21+
const [resizing, setResizing] = React.useState(false);
22+
23+
React.useEffect(() => {
24+
const element = elementRef.current;
25+
26+
if (!element) {
27+
return undefined;
28+
}
29+
30+
let mouseXPosition: number | undefined;
31+
let initialColumnWidth: number | undefined;
32+
let currentColumnWidth: number | undefined;
33+
34+
const onMouseMove = rafThrottle((e: MouseEvent) => {
35+
restrictMouseEvent(e);
36+
37+
if (typeof mouseXPosition !== 'number' || typeof initialColumnWidth !== 'number') {
38+
return;
39+
}
40+
41+
const xDiff = e.clientX - mouseXPosition;
42+
43+
const newWidth = calculateColumnWidth(initialColumnWidth + xDiff, minWidth, maxWidth);
44+
45+
if (newWidth === currentColumnWidth) {
46+
return;
47+
}
48+
49+
currentColumnWidth = newWidth;
50+
51+
onResize?.(currentColumnWidth);
52+
});
53+
54+
const onMouseUp = (e: MouseEvent) => {
55+
restrictMouseEvent(e);
56+
57+
if (currentColumnWidth !== undefined) {
58+
onResize?.(currentColumnWidth);
59+
}
60+
61+
setResizing(false);
62+
mouseXPosition = undefined;
63+
64+
document.removeEventListener('mousemove', onMouseMove);
65+
document.removeEventListener('mouseup', onMouseUp);
66+
};
67+
68+
const onMouseDown = (e: MouseEvent) => {
69+
initialColumnWidth = getCurrentColumnWidth();
70+
71+
restrictMouseEvent(e);
72+
73+
mouseXPosition = e.clientX;
74+
75+
setResizing(true);
76+
77+
document.addEventListener('mousemove', onMouseMove);
78+
document.addEventListener('mouseup', onMouseUp);
79+
};
80+
81+
element.addEventListener('mousedown', onMouseDown);
82+
83+
return () => {
84+
element.removeEventListener('mousedown', onMouseDown);
85+
document.removeEventListener('mousemove', onMouseMove);
86+
document.removeEventListener('mouseup', onMouseUp);
87+
};
88+
}, [onResize, minWidth, maxWidth, getCurrentColumnWidth]);
89+
90+
return (
91+
<span
92+
ref={elementRef}
93+
className={b('resize-handler', {resizing})}
94+
// Prevent sort trigger on resize
95+
onClick={(e) => restrictMouseEvent(e)}
96+
/>
97+
);
98+
}
99+
100+
// Prevent sort trigger and text selection on resize
101+
function restrictMouseEvent<
102+
T extends {preventDefault: VoidFunction; stopPropagation: VoidFunction},
103+
>(e: T) {
104+
e.preventDefault();
105+
e.stopPropagation();
106+
}

0 commit comments

Comments
 (0)