Skip to content

Commit 4cb6a43

Browse files
authored
Add column resize functionality (#57)
* Add column resize functionality * Use sash color * Prevent resizing if colspan is set to fill (multi session)
1 parent 486e5e0 commit 4cb6a43

6 files changed

Lines changed: 150 additions & 9 deletions

File tree

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,13 @@
4444
"antd": "^5.22.1",
4545
"jszip": "^3.10.1",
4646
"node-fetch": "^2.6.7",
47-
"throttle-debounce": "5.0.2",
4847
"primeflex": "^3.3.1",
48+
"re-resizable": "^6.11.2",
4949
"react": "^18.2.0",
5050
"react-dom": "^18.2.0",
5151
"react-markdown": "^9.0.1",
5252
"remark-gfm": "^4.0.0",
53+
"throttle-debounce": "5.0.2",
5354
"vscode-messenger": "^0.4.5",
5455
"vscode-messenger-common": "^0.4.5",
5556
"vscode-messenger-webview": "^0.4.5",
@@ -58,9 +59,9 @@
5859
},
5960
"devDependencies": {
6061
"@types/node": "^20.17.17",
61-
"@types/throttle-debounce": "5.0.2",
6262
"@types/react": "^18.0.26",
6363
"@types/react-dom": "^18.0.9",
64+
"@types/throttle-debounce": "5.0.2",
6465
"@types/vscode": "^1.63.2",
6566
"@types/vscode-webview": "^1.57.0",
6667
"@types/xml2js": "^0.4.9",

src/components/tree/components/treetable.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
********************************************************************************/
77

88
/* AntD Table Variables */
9+
910
.css-var-r0 {
1011
--ant-font-family: var(--vscode-font-family);
1112
--ant-color-text: var(--vscode-sideBar-foreground);
@@ -98,3 +99,16 @@
9899
.ant-table .ant-table-tbody-virtual-scrollbar-horizontal {
99100
display: none;
100101
}
102+
103+
.ant-table .resizable-handle {
104+
border-left: var(--vscode-sideBarSectionHeader-border) 2px solid;
105+
}
106+
107+
.ant-table .resizable-handle:hover,
108+
.ant-table .resizable-handle:active {
109+
border-color: var(--vscode-sash-hoverBorder);
110+
}
111+
112+
.ant-table .ant-table-row.ant-table-row-resizing .tree-actions {
113+
display: none;
114+
}

src/components/tree/components/treetable.tsx

Lines changed: 121 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,21 @@ import './treetable.css';
1010

1111
import { ConfigProvider, Table } from 'antd';
1212
import { ColumnType, ExpandableConfig } from 'antd/es/table/interface';
13+
import { Resizable } from 're-resizable';
1314
import { default as React, useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';
1415
import { debounce } from 'throttle-debounce';
1516
import { CommandDefinition, findNestedValue } from '../../../common';
17+
import { Commands } from '../../../manifest';
1618
import { CDTTreeItem, CDTTreeItemResource, CDTTreeTableActionColumn, CDTTreeTableActionColumnCommand, CDTTreeTableColumnDefinition, CDTTreeTableStringColumn, CTDTreeWebviewContext } from '../types';
1719
import ActionCell from './cells/ActionCell';
1820
import StringCell from './cells/StringCell';
1921
import { ExpandIcon } from './expand-icon';
2022
import { SearchOverlay } from './search-overlay';
2123
import { TreeNavigator } from './treetable-navigator';
2224
import { classNames, filterTree, getAncestors, traverseTree, useClickHook } from './utils';
23-
import { Commands } from '../../../manifest';
25+
26+
const COLUMN_MIN_WIDTH = 50;
27+
const ACTION_COLUMN_WIDTH = 16 * 5;
2428

2529
/**
2630
* Component to render a tree table.
@@ -81,6 +85,12 @@ export type ComponentTreeTableProps<T extends CDTTreeItemResource = CDTTreeItemR
8185
}
8286
};
8387

88+
interface ResizeableCell {
89+
resizeable?: boolean;
90+
maxWidth?: number;
91+
onDidColumnResize?: (event: MouseEvent | TouchEvent, width: number) => void;
92+
}
93+
8494
interface BodyRowProps extends React.HTMLAttributes<HTMLDivElement> {
8595
'data-row-key': string;
8696
record: CDTTreeItem<CDTTreeItemResource>;
@@ -91,6 +101,7 @@ const BodyRow = React.forwardRef<HTMLDivElement, BodyRowProps>((props, ref) => {
91101
return (
92102
<div
93103
ref={ref}
104+
data-test
94105
tabIndex={0}
95106
key={props['data-row-key']}
96107
{...props}
@@ -99,6 +110,72 @@ const BodyRow = React.forwardRef<HTMLDivElement, BodyRowProps>((props, ref) => {
99110
);
100111
});
101112

113+
114+
interface BodyCellProps extends React.HTMLAttributes<HTMLDivElement>, ResizeableCell {
115+
}
116+
117+
const BodyCell = React.forwardRef<HTMLDivElement, BodyCellProps>((props, ref) => {
118+
const { resizeable, onDidColumnResize, maxWidth, className, onResize, style, ...rest } = props;
119+
const [width, setWidth] = useState<string | number | undefined>(props.style?.width);
120+
121+
useEffect(() => {
122+
if (resizeable) {
123+
setWidth(props.style?.width);
124+
}
125+
}, [props.style?.width]);
126+
127+
const cell = <div
128+
ref={ref}
129+
style={{ minWidth: '50px', ...style }}
130+
{...rest}
131+
className={className}
132+
onResize={onResize}
133+
/>;
134+
135+
if (!resizeable) {
136+
return cell;
137+
}
138+
139+
return (
140+
<Resizable
141+
minWidth={50}
142+
maxWidth={maxWidth}
143+
className={classNames('ant-table-cell-resizable', className ?? '')}
144+
size={{
145+
width,
146+
}}
147+
onResizeStart={(_event, _direction, ref) => {
148+
const row = ref.closest('.ant-table-row');
149+
row?.classList.add('ant-table-row-resizing');
150+
}}
151+
onResize={(event, _direction, ref, _delta) => {
152+
onDidColumnResize?.(event, ref.clientWidth);
153+
154+
}}
155+
onResizeStop={(event, _direction, ref, _delta) => {
156+
onDidColumnResize?.(event, ref.clientWidth);
157+
const row = ref.closest('.ant-table-row');
158+
row?.classList.remove('ant-table-row-resizing');
159+
}}
160+
handleClasses={{
161+
right: 'resizable-handle',
162+
}}
163+
enable={{
164+
bottom: false,
165+
bottomLeft: false,
166+
bottomRight: false,
167+
left: false,
168+
right: true,
169+
top: false,
170+
topLeft: false,
171+
topRight: false
172+
}}
173+
{...rest}
174+
>
175+
</Resizable>
176+
);
177+
});
178+
102179
function useWindowSize() {
103180
const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight });
104181
useLayoutEffect(() => {
@@ -279,6 +356,32 @@ export const AntDComponentTreeTable = <T extends CDTTreeItemResource,>(props: Co
279356

280357

281358
// ==== Columns ====
359+
const [columnWidths, setColumnWidths] = useState<Record<string, number>>({});
360+
const [prevWindowWidth, setPrevWindowWidth] = useState(width);
361+
const availableWidth = useMemo(() => width - ACTION_COLUMN_WIDTH - COLUMN_MIN_WIDTH * (props.columnDefinitions?.filter(c => c.resizable).length ?? 0), [width, props.columnDefinitions]);
362+
363+
const handleResize = (field: string) =>
364+
(_: MouseEvent | TouchEvent, width: number) => {
365+
setColumnWidths((prev) => ({ ...prev, [field]: width }));
366+
};
367+
368+
useEffect(() => {
369+
const delta = width - prevWindowWidth;
370+
if (delta < 0) {
371+
// Shrink columns that are too wide
372+
setColumnWidths((prev) => {
373+
const newWidths = { ...prev };
374+
for (const key in newWidths) {
375+
const currentWidth = newWidths[key];
376+
if (currentWidth > availableWidth) {
377+
newWidths[key] = Math.max(currentWidth + delta, COLUMN_MIN_WIDTH);
378+
}
379+
}
380+
return newWidths;
381+
});
382+
}
383+
setPrevWindowWidth(width);
384+
}, [width]);
282385

283386
const getActions = useCallback((record: CDTTreeItem<T>, column: CDTTreeTableActionColumn) => {
284387
const actions: CDTTreeTableActionColumnCommand[] = [];
@@ -344,19 +447,30 @@ export const AntDComponentTreeTable = <T extends CDTTreeItemResource,>(props: Co
344447

345448
const columns = useMemo(() => {
346449
return props.columnDefinitions?.map<ColumnType<CDTTreeItem<T>>>(colDef => {
450+
const resizeable: ResizeableCell = {
451+
resizeable: colDef.resizable,
452+
maxWidth: availableWidth,
453+
onDidColumnResize: handleResize(colDef.field)
454+
};
455+
347456
if (colDef.type === 'string') {
348457
return {
349458
title: colDef.field,
350459
dataIndex: ['columns', colDef.field],
351-
width: 0,
460+
width: columnWidths[colDef.field] ?? 0,
352461
ellipsis: true,
353462
render: renderStringCell,
354463
className: colDef.field,
355464
onCell: (record) => {
356465
const column = findNestedValue<CDTTreeTableStringColumn>(record, ['columns', colDef.field]);
466+
357467
return !column || !column.colSpan
358-
? {}
468+
? {
469+
...resizeable,
470+
} as React.HTMLAttributes<unknown> & React.TdHTMLAttributes<unknown>
359471
: {
472+
...resizeable,
473+
resizeable: column.colSpan !== 'fill',
360474
colSpan: column.colSpan === 'fill' ? props.columnDefinitions?.length : column.colSpan,
361475
style: { zIndex: 1 }
362476
};
@@ -367,17 +481,18 @@ export const AntDComponentTreeTable = <T extends CDTTreeItemResource,>(props: Co
367481
return {
368482
title: colDef.field,
369483
dataIndex: ['columns', colDef.field],
370-
width: 16 * 5,
484+
width: ACTION_COLUMN_WIDTH,
371485
render: renderActionCell,
372486
};
373487
}
374488
return {
489+
...resizeable,
375490
title: colDef.field,
376491
dataIndex: ['columns', colDef.field, 'label'],
377492
width: 200,
378493
};
379494
}) ?? [];
380-
}, [props.columnDefinitions, renderStringCell, renderActionCell]);
495+
}, [props.columnDefinitions, columnWidths, renderStringCell, renderActionCell]);
381496

382497
// ==== Handlers ====
383498

@@ -464,7 +579,7 @@ export const AntDComponentTreeTable = <T extends CDTTreeItemResource,>(props: Co
464579
ref={tblRef}
465580
columns={columns}
466581
dataSource={filteredData}
467-
components={{ body: { row: BodyRow } }}
582+
components={{ body: { row: BodyRow, cell: BodyCell } }}
468583
virtual
469584
scroll={{ x: width, y: height - 2 }}
470585
showHeader={false}

src/components/tree/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,12 @@ export interface CDTTreeTableColumnDefinition {
166166
* The field that is used to get the value for this column. See {@link CDTTreeItem.columns}.
167167
*/
168168
field: string;
169+
/**
170+
* Whether the column is resizable. Default is false.
171+
* The resize handle is display on the right side.
172+
* That means the column after this column is also resized.
173+
*/
174+
resizable?: boolean;
169175
}
170176

171177
// ==== Model ====

src/plugin/peripheral/tree/peripheral-tree-data-provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export class PeripheralTreeDataProvider implements CDTTreeDataProvider<Periphera
6363

6464
getColumnDefinitions(): CDTTreeTableColumnDefinition[] {
6565
return [
66-
{ type: 'string', field: 'title' },
66+
{ type: 'string', field: 'title', resizable: true },
6767
{ type: 'string', field: 'value' },
6868
{ type: 'action', field: 'actions' }];
6969
}

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4149,6 +4149,11 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
41494149
minimist "^1.2.0"
41504150
strip-json-comments "~2.0.1"
41514151

4152+
re-resizable@^6.11.2:
4153+
version "6.11.2"
4154+
resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.11.2.tgz#2e8f7119ca3881d5b5aea0ffa014a80e5c1252b3"
4155+
integrity sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==
4156+
41524157
react-dom@^18.2.0:
41534158
version "18.3.1"
41544159
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"

0 commit comments

Comments
 (0)