Skip to content

Commit e237b55

Browse files
committed
feat: columnResizable
1 parent b98bb3e commit e237b55

File tree

16 files changed

+606
-109
lines changed

16 files changed

+606
-109
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ React.render(<Table columns={columns} data={data} />, mountNode);
133133
| fixed | String \| Boolean | | this column will be fixed when table scroll horizontally: true or 'left' or 'right' |
134134
| align | String | | specify how cell content is aligned |
135135
| ellipsis | Boolean | | specify whether cell content be ellipsized |
136+
| resizable | Boolean | | column resize |
136137
| rowScope | 'row' \| 'rowgroup' | | Set scope attribute for all cells in this column |
137138
| onCell | Function(record, index) | | Set custom props per each cell. |
138139
| onHeaderCell | Function(record) | | Set custom props per each header cell. |

assets/index.less

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@
5555
}
5656
}
5757

58+
&-column-resizing {
59+
cursor: col-resize;
60+
}
61+
5862
// ================== Cell ==================
5963
&-cell {
6064
background: #f4f4f4;
@@ -64,6 +68,26 @@
6468
z-index: calc(2 + var(--z-offset));
6569
}
6670

71+
&-resize-handle {
72+
position: absolute;
73+
top: 0;
74+
width: 4px;
75+
height: 100%;
76+
cursor: col-resize;
77+
background: red;
78+
z-index: 1;
79+
}
80+
81+
&-resize-line {
82+
position: absolute;
83+
width: 4px;
84+
background: red;
85+
height: 100%;
86+
top: 0;
87+
transform: translateX(-50%);
88+
z-index: 100;
89+
}
90+
6791
&-fix-start-shadow,
6892
&-fix-end-shadow {
6993
&::after {
@@ -75,6 +99,7 @@
7599
opacity: 0;
76100
content: '';
77101
transition: opacity 0.3s;
102+
pointer-events: none;
78103
}
79104

80105
&-show::after {

docs/examples/column-resize.tsx

Lines changed: 104 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,113 @@
1-
import React from 'react';
2-
import { Resizable } from 'react-resizable';
3-
import Table from 'rc-table';
1+
import React, { useState } from 'react';
2+
import Table, { INTERNAL_HOOKS } from 'rc-table';
3+
import type { ColumnType } from 'rc-table';
44
import '../../assets/index.less';
5-
import 'react-resizable/css/styles.css';
6-
import type { ColumnType } from '@/interface';
5+
import { useCheckbox } from './utils/useInput';
76

8-
const ResizableTitle = props => {
9-
const { onResize, width, ...restProps } = props;
7+
const data = [
8+
{ a: '123', b: 'xxxxxxxx xxxxxxxx', d: 3, key: '1' },
9+
{ a: 'cdd', b: 'edd12221 edd12221', d: 3, key: '2' },
10+
{ a: '133', c: 'edd12221 edd12221', d: 2, key: '3' },
11+
{ a: '133', c: 'edd12221 edd12221', d: 2, key: '4' },
12+
{ a: '133', c: 'edd12221 edd12221', d: 2, key: '5' },
13+
{ a: '133', c: 'edd12221 edd12221', d: 2, key: '6' },
14+
{ a: '133', c: 'edd12221 edd12221', d: 2, key: '7' },
15+
{ a: '133', c: 'edd12221 edd12221', d: 2, key: '8' },
16+
{ a: '133', c: 'edd12221 edd12221', d: 2, key: '9' },
17+
];
1018

11-
if (!width) {
12-
return <th {...restProps} />;
13-
}
19+
const Demo = () => {
20+
const [widthMap, setWidthMap] = useState<Map<React.Key, number>>(new Map());
21+
const [isRtl, isRtlProps] = useCheckbox(false);
22+
23+
const columns1 = [
24+
{ title: 'title1', dataIndex: 'aaa', key: 'aaa', width: 100 },
25+
{ title: 'title2', dataIndex: 'bbb', key: 'bbb', width: 100 },
26+
].map(i => ({
27+
...i,
28+
resizable: true,
29+
width: widthMap.get(i.key ?? i.dataIndex) ?? i.width,
30+
})) as ColumnType<any>[];
31+
32+
const columns2 = [
33+
{ title: 'title1', dataIndex: 'a', key: 'a', fixed: 'left', width: 200 },
34+
{ title: 'title2', dataIndex: 'b', key: 'b', fixed: 'left' },
35+
{ title: 'title3', dataIndex: 'c', key: 'c' },
36+
{ title: 'title4', dataIndex: 'b', key: 'd' },
37+
{ title: 'title5', dataIndex: 'b', key: 'e' },
38+
{ title: 'title6', dataIndex: 'b', key: 'f' },
39+
{ title: 'title7', dataIndex: 'b', key: 'g' },
40+
{ title: 'title8', dataIndex: 'b', key: 'h' },
41+
{ title: 'title9', dataIndex: 'b', key: 'i' },
42+
{ title: 'title10', dataIndex: 'b', key: 'j' },
43+
{ title: 'title11', dataIndex: 'b', key: 'k', fixed: 'right' },
44+
{ title: 'title12', dataIndex: 'b', key: 'l', fixed: 'right', minWidth: 50 },
45+
].map(i => ({
46+
...i,
47+
resizable: true,
48+
width: widthMap.get(i.key ?? i.dataIndex) ?? i.width ?? 150,
49+
})) as ColumnType<any>[];
1450

1551
return (
16-
<Resizable width={width} height={0} onResize={onResize}>
17-
<th {...restProps} />
18-
</Resizable>
52+
<div>
53+
table width: 800px {'columns=[{width: 100, width: 100}]'} 情况
54+
<Table
55+
style={{ width: 800 }}
56+
scroll={{ y: 300, x: columns1.reduce((t, c) => t + (c.width as number), 0) }}
57+
columns={columns1}
58+
data={data}
59+
onColumnResizeEnd={({ columnWidths }) => {
60+
setWidthMap(prev => {
61+
const result = new Map(prev);
62+
columnWidths.forEach(i => {
63+
result.set(i.columnKey, i.width);
64+
});
65+
return result;
66+
});
67+
}}
68+
internalHooks={INTERNAL_HOOKS}
69+
getContainerWidth={(ele, width) => {
70+
// Minus border
71+
const borderWidth = getComputedStyle(
72+
ele.querySelector('.rc-table-body'),
73+
).borderInlineStartWidth;
74+
const mergedWidth = width - parseInt(borderWidth, 10);
75+
return mergedWidth;
76+
}}
77+
/>
78+
<br />
79+
Fixed Columns
80+
<label>
81+
<input {...isRtlProps} />
82+
IsRtl
83+
</label>
84+
<Table
85+
direction={isRtl ? 'rtl' : undefined}
86+
style={{ width: 1000 }}
87+
scroll={{ y: 300, x: columns2.reduce((t, c) => t + (c.width as number), 0) }}
88+
columns={columns2}
89+
data={data}
90+
onColumnResizeEnd={({ columnWidths }) => {
91+
setWidthMap(prev => {
92+
const result = new Map(prev);
93+
columnWidths.forEach(i => {
94+
result.set(i.columnKey, i.width);
95+
});
96+
return result;
97+
});
98+
}}
99+
internalHooks={INTERNAL_HOOKS}
100+
getContainerWidth={(ele, width) => {
101+
// Minus border
102+
const borderWidth = getComputedStyle(
103+
ele.querySelector('.rc-table-body'),
104+
).borderInlineStartWidth;
105+
const mergedWidth = width - parseInt(borderWidth, 10);
106+
return mergedWidth;
107+
}}
108+
/>
109+
</div>
19110
);
20111
};
21112

22-
interface RecordType {
23-
a: string;
24-
b?: string;
25-
c?: string;
26-
d?: number;
27-
key: string;
28-
}
29-
30-
interface DemoState {
31-
columns: ColumnType<RecordType>[];
32-
}
33-
34-
class Demo extends React.Component<{}, DemoState> {
35-
state: DemoState = {
36-
columns: [
37-
{ title: 'title1', dataIndex: 'a', key: 'a', width: 100 },
38-
{ title: 'title2', dataIndex: 'b', key: 'b', width: 100 },
39-
{ title: 'title3', dataIndex: 'c', key: 'c', width: 200 },
40-
{
41-
title: 'Operations',
42-
dataIndex: '',
43-
key: 'd',
44-
render() {
45-
return <a href="#">Operations</a>;
46-
},
47-
},
48-
],
49-
};
50-
51-
components = {
52-
header: {
53-
cell: ResizableTitle,
54-
},
55-
};
56-
57-
data = [
58-
{ a: '123', key: '1' },
59-
{ a: 'cdd', b: 'edd', key: '2' },
60-
{ a: '1333', c: 'eee', d: 2, key: '3' },
61-
];
62-
63-
handleResize =
64-
index =>
65-
(e, { size }) => {
66-
this.setState(({ columns }) => {
67-
const nextColumns = [...columns];
68-
nextColumns[index] = {
69-
...nextColumns[index],
70-
width: size.width,
71-
};
72-
return { columns: nextColumns };
73-
});
74-
};
75-
76-
render() {
77-
const columns = this.state.columns.map((col, index) => ({
78-
...col,
79-
onHeaderCell: (column: ColumnType<RecordType>) =>
80-
({
81-
width: column.width,
82-
onResize: this.handleResize(index),
83-
}) as any,
84-
}));
85-
86-
return (
87-
<div>
88-
<h2>Integrate with react-resizable</h2>
89-
<Table components={this.components} columns={columns} data={this.data} />
90-
</div>
91-
);
92-
}
93-
}
94-
95113
export default Demo;

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@
9393
"react-dnd": "^2.5.4",
9494
"react-dnd-html5-backend": "^2.5.4",
9595
"react-dom": "^16.0.0",
96-
"react-resizable": "^3.0.5",
9796
"react-virtualized": "^9.12.0",
9897
"react-window": "^1.8.5",
9998
"regenerator-runtime": "^0.14.0",

src/Body/MeasureCell.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ import useLayoutEffect from '@rc-component/util/lib/hooks/useLayoutEffect';
44

55
export interface MeasureCellProps {
66
columnKey: React.Key;
7-
onColumnResize: (key: React.Key, width: number) => void;
7+
onColumnWidthChange: (key: React.Key, width: number) => void;
88
}
99

10-
export default function MeasureCell({ columnKey, onColumnResize }: MeasureCellProps) {
10+
export default function MeasureCell({ columnKey, onColumnWidthChange }: MeasureCellProps) {
1111
const cellRef = React.useRef<HTMLTableCellElement>();
1212

1313
useLayoutEffect(() => {
1414
if (cellRef.current) {
15-
onColumnResize(columnKey, cellRef.current.offsetWidth);
15+
onColumnWidthChange(columnKey, cellRef.current.offsetWidth);
1616
}
1717
}, []);
1818

src/Body/MeasureRow.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@ import isVisible from '@rc-component/util/lib/Dom/isVisible';
55

66
export interface MeasureCellProps {
77
prefixCls: string;
8-
onColumnResize: (key: React.Key, width: number) => void;
8+
onColumnWidthChange: (key: React.Key, width: number) => void;
99
columnsKey: React.Key[];
1010
}
1111

12-
export default function MeasureRow({ prefixCls, columnsKey, onColumnResize }: MeasureCellProps) {
12+
export default function MeasureRow({
13+
prefixCls,
14+
columnsKey,
15+
onColumnWidthChange,
16+
}: MeasureCellProps) {
1317
const ref = React.useRef<HTMLTableRowElement>(null);
1418

1519
return (
@@ -23,13 +27,17 @@ export default function MeasureRow({ prefixCls, columnsKey, onColumnResize }: Me
2327
onBatchResize={infoList => {
2428
if (isVisible(ref.current)) {
2529
infoList.forEach(({ data: columnKey, size }) => {
26-
onColumnResize(columnKey, size.offsetWidth);
30+
onColumnWidthChange(columnKey, size.offsetWidth);
2731
});
2832
}
2933
}}
3034
>
3135
{columnsKey.map(columnKey => (
32-
<MeasureCell key={columnKey} columnKey={columnKey} onColumnResize={onColumnResize} />
36+
<MeasureCell
37+
key={columnKey}
38+
columnKey={columnKey}
39+
onColumnWidthChange={onColumnWidthChange}
40+
/>
3341
))}
3442
</ResizeObserver.Collection>
3543
</tr>

src/Body/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ function Body<RecordType>(props: BodyProps<RecordType>) {
2525
const {
2626
prefixCls,
2727
getComponent,
28-
onColumnResize,
28+
onColumnWidthChange,
2929
flattenColumns,
3030
getRowKey,
3131
expandedKeys,
@@ -34,7 +34,7 @@ function Body<RecordType>(props: BodyProps<RecordType>) {
3434
} = useContext(TableContext, [
3535
'prefixCls',
3636
'getComponent',
37-
'onColumnResize',
37+
'onColumnWidthChange',
3838
'flattenColumns',
3939
'getRowKey',
4040
'expandedKeys',
@@ -103,7 +103,7 @@ function Body<RecordType>(props: BodyProps<RecordType>) {
103103
<MeasureRow
104104
prefixCls={prefixCls}
105105
columnsKey={columnsKey}
106-
onColumnResize={onColumnResize}
106+
onColumnWidthChange={onColumnWidthChange}
107107
/>
108108
)}
109109

src/FixedHolder/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ const FixedHolder = React.forwardRef<HTMLDivElement, FixedHeaderProps<any>>((pro
109109
return () => {
110110
scrollRef.current?.removeEventListener('wheel', onWheel);
111111
};
112-
}, []);
112+
}, [direction]);
113113

114114
// Check if all flattenColumns has width
115115
const allFlattenColumnsWithWidth = React.useMemo(

src/Header/HeaderCell.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as React from 'react';
2+
import type { CellProps } from '../Cell';
3+
import Cell from '../Cell';
4+
import useCellResize from './useCellResize';
5+
6+
interface HeaderCellProps<RecordType> extends CellProps<RecordType> {
7+
columnKey?: React.Key;
8+
resizable?: boolean;
9+
minWidth?: number;
10+
isScrollBarPreviousCell?: boolean;
11+
}
12+
13+
function HeaderCell<RecordType>({
14+
columnKey,
15+
resizable,
16+
minWidth,
17+
isScrollBarPreviousCell,
18+
...cellProps
19+
}: HeaderCellProps<RecordType>) {
20+
const resizeHandleNode = useCellResize(
21+
columnKey,
22+
typeof cellProps.fixEnd === 'number',
23+
cellProps.prefixCls,
24+
isScrollBarPreviousCell,
25+
resizable,
26+
minWidth,
27+
);
28+
29+
return <Cell {...cellProps} appendNode={resizeHandleNode} />;
30+
}
31+
32+
export default HeaderCell;

0 commit comments

Comments
 (0)