Skip to content

Commit 26bacd3

Browse files
authored
fix: sticky with fixed columns (#510)
* fix: sticky with fixed columns * fix sticky with scroll.y * fix type error * fix compile error * fix lgtm * add test
1 parent 86d96b2 commit 26bacd3

File tree

10 files changed

+233
-25
lines changed

10 files changed

+233
-25
lines changed

assets/index.less

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
z-index: 1;
6262
}
6363

64-
&-fix-right:last-child {
64+
&-fix-right:last-child:not(&-fix-sticky) {
6565
border-right-color: transparent;
6666
}
6767

@@ -290,7 +290,7 @@
290290
&-sticky {
291291
&-header {
292292
position: sticky;
293-
z-index: 10;
293+
z-index: 2;
294294
}
295295
&-scroll {
296296
position: fixed;
@@ -300,6 +300,7 @@
300300
border-top: 1px solid #f3f3f3;
301301
opacity: 0.6;
302302
transition: transform 0.1s ease-in 0s;
303+
z-index: 2;
303304
&:hover {
304305
transform: scaleY(1.2);
305306
transform-origin: center bottom;
@@ -311,6 +312,9 @@
311312
&:hover {
312313
background-color: #999;
313314
}
315+
&-active {
316+
background-color: #999;
317+
}
314318
}
315319
}
316320
}

examples/stickyHeader.tsx

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,54 @@ interface RecordType {
88
a?: string;
99
b?: string;
1010
c?: string;
11+
d?: number;
12+
key?: string;
1113
}
1214

15+
const fixedColumns: ColumnType<RecordType>[] = [
16+
{ title: 'title1', dataIndex: 'a', key: 'a', width: 100, fixed: 'left' },
17+
{ title: 'title2', dataIndex: 'b', key: 'b', width: 100, fixed: 'left', ellipsis: true },
18+
{ title: 'title3', dataIndex: 'c', key: 'c' },
19+
{ title: 'title4', dataIndex: 'b', key: 'd' },
20+
{ title: 'title5', dataIndex: 'b', key: 'e' },
21+
{ title: 'title6', dataIndex: 'b', key: 'f' },
22+
{
23+
title: (
24+
<div>
25+
title7
26+
<br />
27+
<br />
28+
<br />
29+
Hello world!
30+
</div>
31+
),
32+
dataIndex: 'b',
33+
key: 'g',
34+
},
35+
{ title: 'title8', dataIndex: 'b', key: 'h' },
36+
{ title: 'title9', dataIndex: 'b', key: 'i' },
37+
{ title: 'title10', dataIndex: 'b', key: 'j' },
38+
{ title: 'title11', dataIndex: 'b', key: 'k' },
39+
{ title: 'title12', dataIndex: 'b', key: 'l', width: 100, fixed: 'right' },
40+
];
41+
42+
const fixedData = [
43+
{
44+
a: '123',
45+
b: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
46+
d: 3,
47+
key: '1',
48+
},
49+
{ a: 'cdd', b: 'edd12221', d: 3, key: '2' },
50+
{ a: '133', c: 'edd12221', d: 2, key: '3' },
51+
{ a: '133', c: 'edd12221', d: 2, key: '4' },
52+
{ a: '133', c: 'edd12221', d: 2, key: '5' },
53+
{ a: '133', c: 'edd12221', d: 2, key: '6' },
54+
{ a: '133', c: 'edd12221', d: 2, key: '7' },
55+
{ a: '133', c: 'edd12221', d: 2, key: '8' },
56+
{ a: '133', c: 'edd12221', d: 2, key: '9' },
57+
];
58+
1359
const columns: ColumnType<{ a: string; b: string; c: string }>[] = [
1460
{ title: 'title1', dataIndex: 'a', key: 'a', width: 100 },
1561
{ title: 'title2', dataIndex: 'b', key: 'b', width: 100, align: 'right' },
@@ -108,6 +154,37 @@ const Demo = () => (
108154
marginBottom: 100,
109155
}}
110156
/>
157+
158+
<h2>Sticky with fixed columns</h2>
159+
<div style={{ width: 800 }}>
160+
<Table<RecordType>
161+
columns={fixedColumns}
162+
data={fixedData}
163+
sticky
164+
scroll={{
165+
x: 1200,
166+
}}
167+
style={{
168+
marginBottom: 100,
169+
}}
170+
/>
171+
</div>
172+
173+
<h2>Sticky with fixed columns and scroll.y</h2>
174+
<div style={{ width: 800 }}>
175+
<Table<RecordType>
176+
columns={fixedColumns}
177+
data={fixedData}
178+
sticky
179+
scroll={{
180+
x: 1200,
181+
y: 300,
182+
}}
183+
style={{
184+
marginBottom: 100,
185+
}}
186+
/>
187+
</div>
111188
</div>
112189
);
113190

src/Cell/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ export interface CellProps<RecordType extends DefaultRecordType> {
5858
additionalProps?: React.HTMLAttributes<HTMLElement>;
5959

6060
rowType?: 'header' | 'body' | 'footer';
61+
62+
isSticky?: boolean;
6163
}
6264

6365
function Cell<RecordType extends DefaultRecordType>(
@@ -83,6 +85,7 @@ function Cell<RecordType extends DefaultRecordType>(
8385
ellipsis,
8486
align,
8587
rowType,
88+
isSticky,
8689
}: CellProps<RecordType>,
8790
ref: React.Ref<any>,
8891
): React.ReactElement {
@@ -188,6 +191,7 @@ function Cell<RecordType extends DefaultRecordType>(
188191
[`${cellPrefixCls}-fix-right-last`]: lastFixRight,
189192
[`${cellPrefixCls}-ellipsis`]: ellipsis,
190193
[`${cellPrefixCls}-with-append`]: appendNode,
194+
[`${cellPrefixCls}-fix-sticky`]: (isFixLeft || isFixRight) && isSticky,
191195
},
192196
additionalProps.className,
193197
cellClassName,

src/Header/FixedHeader.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ function FixedHeader<RecordType>({
3737
fixHeader,
3838
...props
3939
}: FixedHeaderProps<RecordType>) {
40-
const { prefixCls, scrollbarSize } = React.useContext(TableContext);
40+
const { prefixCls, scrollbarSize, isSticky } = React.useContext(TableContext);
41+
42+
const combinationScrollBarSize = isSticky && !fixHeader ? 0 : scrollbarSize;
4143

4244
// Add scrollbar column
4345
const lastColumn = flattenColumns[flattenColumns.length - 1];
@@ -49,31 +51,34 @@ function FixedHeader<RecordType>({
4951
};
5052

5153
const columnsWithScrollbar = useMemo<ColumnsType<RecordType>>(
52-
() => (scrollbarSize && fixHeader ? [...columns, ScrollBarColumn] : columns),
53-
[scrollbarSize, columns, fixHeader],
54+
() => (combinationScrollBarSize ? [...columns, ScrollBarColumn] : columns),
55+
[combinationScrollBarSize, columns],
5456
);
5557

5658
const flattenColumnsWithScrollbar = useMemo<ColumnType<RecordType>[]>(
57-
() => (scrollbarSize ? [...flattenColumns, ScrollBarColumn] : flattenColumns),
58-
[scrollbarSize, flattenColumns],
59+
() => (combinationScrollBarSize ? [...flattenColumns, ScrollBarColumn] : flattenColumns),
60+
[combinationScrollBarSize, flattenColumns],
5961
);
6062

6163
// Calculate the sticky offsets
6264
const headerStickyOffsets = useMemo(() => {
6365
const { right, left } = stickyOffsets;
6466
return {
6567
...stickyOffsets,
66-
left: direction === 'rtl' ? [...left.map(width => width + scrollbarSize), 0] : left,
67-
right: direction === 'rtl' ? right : [...right.map(width => width + scrollbarSize), 0],
68+
left:
69+
direction === 'rtl' ? [...left.map(width => width + combinationScrollBarSize), 0] : left,
70+
right:
71+
direction === 'rtl' ? right : [...right.map(width => width + combinationScrollBarSize), 0],
72+
isSticky,
6873
};
69-
}, [scrollbarSize, stickyOffsets]);
74+
}, [combinationScrollBarSize, stickyOffsets, isSticky]);
7075

7176
const mergedColumnWidth = useColumnWidth(colWidths, columCount);
7277

7378
return (
7479
<table style={{ tableLayout: 'fixed', visibility: mergedColumnWidth ? null : 'hidden' }}>
7580
<ColGroup
76-
colWidths={mergedColumnWidth ? [...mergedColumnWidth, scrollbarSize] : []}
81+
colWidths={mergedColumnWidth ? [...mergedColumnWidth, combinationScrollBarSize] : []}
7782
columCount={columCount + 1}
7883
columns={flattenColumnsWithScrollbar}
7984
/>

src/Table.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -704,8 +704,18 @@ function Table<RecordType extends DefaultRecordType>(props: TableProps<RecordTyp
704704
fixedInfoList: flattenColumns.map((_, colIndex) =>
705705
getCellFixedInfo(colIndex, colIndex, flattenColumns, stickyOffsets, direction),
706706
),
707+
isSticky,
707708
}),
708-
[prefixCls, getComponent, scrollbarSize, direction, flattenColumns, stickyOffsets, direction],
709+
[
710+
prefixCls,
711+
getComponent,
712+
scrollbarSize,
713+
direction,
714+
flattenColumns,
715+
stickyOffsets,
716+
direction,
717+
isSticky,
718+
],
709719
);
710720

711721
const BodyContextValue = React.useMemo(

src/context/TableContext.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export interface TableContextProps {
1313
direction: 'ltr' | 'rtl';
1414

1515
fixedInfoList: FixedInfo[];
16+
17+
isSticky: boolean;
1618
}
1719

1820
const TableContext = React.createContext<TableContextProps>(null);

src/interface.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,15 @@ export interface ColumnType<RecordType> extends ColumnSharedType<RecordType> {
9191

9292
export type ColumnsType<RecordType = unknown> = (
9393
| ColumnGroupType<RecordType>
94-
| ColumnType<RecordType>
95-
)[];
94+
| ColumnType<RecordType>)[];
9695

9796
export type GetRowKey<RecordType> = (record: RecordType, index?: number) => Key;
9897

9998
// ================= Fix Column =================
10099
export interface StickyOffsets {
101100
left: number[];
102101
right: number[];
102+
isSticky?: boolean;
103103
}
104104

105105
// ================= Customized =================

src/stickyScrollBar.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from 'react';
22
import addEventListener from 'rc-util/lib/Dom/addEventListener';
33
import getScrollBarSize from 'rc-util/lib/getScrollBarSize';
4+
import classNames from 'classnames';
45
import { getOffset } from 'rc-util/lib/Dom/css';
56
import TableContext from './context/TableContext';
67
import { useFrameState } from './hooks/useFrame';
@@ -17,7 +18,7 @@ const StickyScrollBar: React.ForwardRefRenderFunction<unknown, StickyScrollBarPr
1718
) => {
1819
const { prefixCls } = React.useContext(TableContext);
1920
const bodyScrollWidth = scrollBodyRef.current?.scrollWidth || 0;
20-
const bodyWidth = scrollBodyRef.current?.offsetWidth || 0;
21+
const bodyWidth = scrollBodyRef.current?.clientWidth || 0;
2122
const scrollBarWidth = bodyScrollWidth && bodyWidth * (bodyWidth / bodyScrollWidth);
2223

2324
const scrollBarRef = React.useRef<HTMLDivElement>();
@@ -29,33 +30,36 @@ const StickyScrollBar: React.ForwardRefRenderFunction<unknown, StickyScrollBarPr
2930
isHiddenScrollBar: false,
3031
});
3132
const refState = React.useRef<{
32-
isScollBarDragable: boolean;
3333
delta: number;
3434
x: number;
3535
}>({
36-
isScollBarDragable: false,
3736
delta: 0,
3837
x: 0,
3938
});
39+
const [isActive, setActive] = React.useState(false);
4040

4141
const onMouseUp: React.MouseEventHandler<HTMLDivElement> = event => {
42-
refState.current.isScollBarDragable = false;
42+
setActive(false);
4343
event.preventDefault();
4444
};
4545

4646
const onMouseDown: React.MouseEventHandler<HTMLDivElement> = event => {
4747
event.persist();
48-
refState.current.isScollBarDragable = true;
4948
refState.current.delta = event.pageX - frameState.scrollLeft;
5049
refState.current.x = 0;
50+
setActive(true);
5151
event.preventDefault();
5252
};
5353

5454
const onMouseMove: React.MouseEventHandler<HTMLDivElement> = event => {
5555
event.preventDefault();
5656
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
5757
const { buttons } = event || (window?.event as any);
58-
if (!refState.current.isScollBarDragable || buttons === 0) {
58+
if (!isActive || buttons === 0) {
59+
// If out body mouse up, we can set isActive false when mouse move
60+
if (isActive) {
61+
setActive(false);
62+
}
5963
return;
6064
}
6165
let left: number =
@@ -118,7 +122,7 @@ const StickyScrollBar: React.ForwardRefRenderFunction<unknown, StickyScrollBarPr
118122
onMouseUpListener.remove();
119123
onMouseMoveListener.remove();
120124
};
121-
}, [scrollBarWidth]);
125+
}, [scrollBarWidth, isActive]);
122126

123127
React.useEffect(() => {
124128
const onScrollListener = addEventListener(window, 'scroll', onContainerScroll, false);
@@ -134,7 +138,7 @@ const StickyScrollBar: React.ForwardRefRenderFunction<unknown, StickyScrollBarPr
134138
...state,
135139
scrollLeft:
136140
(scrollBodyRef.current.scrollLeft / scrollBodyRef.current?.scrollWidth) *
137-
scrollBodyRef.current?.offsetWidth,
141+
scrollBodyRef.current?.clientWidth,
138142
}));
139143
}
140144
}, [frameState.isHiddenScrollBar]);
@@ -155,7 +159,9 @@ const StickyScrollBar: React.ForwardRefRenderFunction<unknown, StickyScrollBarPr
155159
<div
156160
onMouseDown={onMouseDown}
157161
ref={scrollBarRef}
158-
className={`${prefixCls}-sticky-scroll-bar`}
162+
className={classNames(`${prefixCls}-sticky-scroll-bar`, {
163+
[`${prefixCls}-sticky-scroll-bar-active`]: isActive,
164+
})}
159165
style={{
160166
width: `${scrollBarWidth}px`,
161167
transform: `translate3d(${frameState.scrollLeft}px, 0, 0)`,

src/utils/fixUtil.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export interface FixedInfo {
99
// For Rtl Direction
1010
lastFixRight: boolean;
1111
firstFixLeft: boolean;
12+
13+
isSticky: boolean;
1214
}
1315

1416
export function getCellFixedInfo(
@@ -62,5 +64,6 @@ export function getCellFixedInfo(
6264
firstFixRight,
6365
lastFixRight,
6466
firstFixLeft,
67+
isSticky: stickyOffsets.isSticky,
6568
};
6669
}

0 commit comments

Comments
 (0)