Skip to content

Commit 907b275

Browse files
feat(VirtualTable): enable columns resize (#697)
1 parent fe0cad4 commit 907b275

File tree

9 files changed

+282
-90
lines changed

9 files changed

+282
-90
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
.ydb-cell-with-popover {
22
display: flex;
33

4+
max-width: 100%;
5+
46
&__popover {
57
display: inline-block;
68
overflow: hidden;
79

810
max-width: 100%;
911

12+
vertical-align: middle;
1013
white-space: nowrap;
1114
text-overflow: ellipsis;
15+
16+
.yc-popover__handler {
17+
display: inline;
18+
}
1219
}
1320
}

src/components/VirtualTable/TableHead.tsx

Lines changed: 127 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
1-
import {useState} from 'react';
1+
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
2+
3+
import type {
4+
HandleTableColumnsResize,
5+
TableColumnsWidthSetup,
6+
} from '../../utils/hooks/useTableResize';
27

38
import type {Column, OnSort, SortOrderType, SortParams} from './types';
49
import {ASCENDING, DEFAULT_SORT_ORDER, DEFAULT_TABLE_ROW_HEIGHT, DESCENDING} from './constants';
510
import {b} from './shared';
611

12+
const COLUMN_NAME_HTML_ATTRIBUTE = 'data-columnname';
13+
714
// Icon similar to original DataTable icons to keep the same tables across diferent pages and tabs
815
const SortIcon = ({order}: {order?: SortOrderType}) => {
916
return (
1017
<svg
11-
className={b('icon', {desc: order === DESCENDING})}
18+
className={b('sort-icon', {desc: order === DESCENDING})}
1219
viewBox="0 0 10 6"
1320
width="10"
1421
height="6"
@@ -27,7 +34,7 @@ interface ColumnSortIconProps {
2734
const ColumnSortIcon = ({sortOrder, sortable, defaultSortOrder}: ColumnSortIconProps) => {
2835
if (sortable) {
2936
return (
30-
<span className={b('sort-icon', {shadow: !sortOrder})}>
37+
<span className={b('sort-icon-container', {shadow: !sortOrder})}>
3138
<SortIcon order={sortOrder || defaultSortOrder} />
3239
</span>
3340
);
@@ -36,21 +43,129 @@ const ColumnSortIcon = ({sortOrder, sortable, defaultSortOrder}: ColumnSortIconP
3643
}
3744
};
3845

46+
interface TableHeadCellProps<T> {
47+
column: Column<T>;
48+
sortOrder?: SortOrderType;
49+
defaultSortOrder: SortOrderType;
50+
onSort?: (columnName: string) => void;
51+
rowHeight: number;
52+
onCellMount?: (element: Element) => void;
53+
onCellUnMount?: (element: Element) => void;
54+
}
55+
56+
export const TableHeadCell = <T,>({
57+
column,
58+
sortOrder,
59+
defaultSortOrder,
60+
onSort,
61+
rowHeight,
62+
onCellMount,
63+
onCellUnMount,
64+
}: TableHeadCellProps<T>) => {
65+
const cellWrapperRef = useRef<HTMLDivElement>(null);
66+
67+
useEffect(() => {
68+
const cellWrapper = cellWrapperRef.current;
69+
if (cellWrapper) {
70+
onCellMount?.(cellWrapper);
71+
}
72+
return () => {
73+
if (cellWrapper) {
74+
onCellUnMount?.(cellWrapper);
75+
}
76+
};
77+
}, [onCellMount, onCellUnMount]);
78+
79+
const content = column.header ?? column.name;
80+
81+
return (
82+
<th>
83+
<div
84+
ref={cellWrapperRef}
85+
className={b('head-cell-wrapper', {
86+
resizeable: column.resizeable,
87+
})}
88+
style={{
89+
height: `${rowHeight}px`,
90+
width: `${column.width}px`,
91+
}}
92+
{...{
93+
[COLUMN_NAME_HTML_ATTRIBUTE]: column.name,
94+
}}
95+
>
96+
<div
97+
className={b(
98+
'head-cell',
99+
{align: column.align, sortable: column.sortable},
100+
column.className,
101+
)}
102+
onClick={() => {
103+
if (column.sortable) {
104+
onSort?.(column.name);
105+
}
106+
}}
107+
>
108+
<div className={b('head-cell-content')}>{content}</div>
109+
<ColumnSortIcon
110+
sortOrder={sortOrder}
111+
sortable={column.sortable}
112+
defaultSortOrder={defaultSortOrder}
113+
/>
114+
</div>
115+
</div>
116+
</th>
117+
);
118+
};
119+
39120
interface TableHeadProps<T> {
40121
columns: Column<T>[];
41122
onSort?: OnSort;
123+
onColumnsResize?: HandleTableColumnsResize;
42124
defaultSortOrder?: SortOrderType;
43125
rowHeight?: number;
44126
}
45127

46128
export const TableHead = <T,>({
47129
columns,
48130
onSort,
131+
onColumnsResize,
49132
defaultSortOrder = DEFAULT_SORT_ORDER,
50133
rowHeight = DEFAULT_TABLE_ROW_HEIGHT,
51134
}: TableHeadProps<T>) => {
52135
const [sortParams, setSortParams] = useState<SortParams>({});
53136

137+
const isTableResizeable = Boolean(onColumnsResize);
138+
139+
const resizeObserver: ResizeObserver | undefined = useMemo(() => {
140+
if (!isTableResizeable) {
141+
return undefined;
142+
}
143+
144+
return new ResizeObserver((entries) => {
145+
const columnsWidth: TableColumnsWidthSetup = {};
146+
entries.forEach((entry) => {
147+
// @ts-ignore ignore custrom property usage
148+
const id = entry.target.attributes[COLUMN_NAME_HTML_ATTRIBUTE]?.value;
149+
columnsWidth[id] = entry.contentRect.width;
150+
});
151+
152+
onColumnsResize?.(columnsWidth);
153+
});
154+
}, [onColumnsResize, isTableResizeable]);
155+
156+
const handleCellMount = useCallback(
157+
(element: Element) => {
158+
resizeObserver?.observe(element);
159+
},
160+
[resizeObserver],
161+
);
162+
const handleCellUnMount = useCallback(
163+
(element: Element) => {
164+
resizeObserver?.unobserve(element);
165+
},
166+
[resizeObserver],
167+
);
168+
54169
const handleSort = (columnId: string) => {
55170
let newSortParams: SortParams = {};
56171

@@ -95,34 +210,20 @@ export const TableHead = <T,>({
95210
<thead className={b('head')}>
96211
<tr>
97212
{columns.map((column) => {
98-
const content = column.header ?? column.name;
99213
const sortOrder =
100214
sortParams.columnId === column.name ? sortParams.sortOrder : undefined;
101215

102216
return (
103-
<th
217+
<TableHeadCell
104218
key={column.name}
105-
className={b(
106-
'th',
107-
{align: column.align, sortable: column.sortable},
108-
column.className,
109-
)}
110-
style={{
111-
height: `${rowHeight}px`,
112-
}}
113-
onClick={() => {
114-
handleSort(column.name);
115-
}}
116-
>
117-
<div className={b('head-cell')}>
118-
{content}
119-
<ColumnSortIcon
120-
sortOrder={sortOrder}
121-
sortable={column.sortable}
122-
defaultSortOrder={defaultSortOrder}
123-
/>
124-
</div>
125-
</th>
219+
column={column}
220+
sortOrder={sortOrder}
221+
defaultSortOrder={defaultSortOrder}
222+
onSort={handleSort}
223+
rowHeight={rowHeight}
224+
onCellMount={handleCellMount}
225+
onCellUnMount={handleCellUnMount}
226+
/>
126227
);
127228
})}
128229
</tr>

src/components/VirtualTable/TableRow.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,25 @@ import {b} from './shared';
88

99
interface TableCellProps {
1010
height: number;
11+
width: number;
1112
align?: AlignType;
1213
children: ReactNode;
1314
className?: string;
1415
}
1516

16-
const TableRowCell = ({children, className, height, align = DEFAULT_ALIGN}: TableCellProps) => {
17+
const TableRowCell = ({
18+
children,
19+
className,
20+
height,
21+
width,
22+
align = DEFAULT_ALIGN,
23+
}: TableCellProps) => {
24+
// Additional maxWidth to ensure overflow hidden for <td>
1725
return (
18-
<td className={b('td', {align: align}, className)} style={{height: `${height}px`}}>
26+
<td
27+
className={b('row-cell', {align: align}, className)}
28+
style={{height: `${height}px`, width: `${width}px`, maxWidth: `${width}px`}}
29+
>
1930
{children}
2031
</td>
2132
);
@@ -35,6 +46,7 @@ export const LoadingTableRow = <T,>({index, columns, height}: LoadingTableRowPro
3546
<TableRowCell
3647
key={`${column.name}${index}`}
3748
height={height}
49+
width={column.width}
3850
align={column.align}
3951
className={column.className}
4052
>
@@ -64,6 +76,7 @@ export const TableRow = <T,>({row, index, columns, getRowClassName, height}: Tab
6476
<TableRowCell
6577
key={`${column.name}${index}`}
6678
height={height}
79+
width={column.width}
6780
align={column.align}
6881
className={column.className}
6982
>

0 commit comments

Comments
 (0)