Skip to content

Commit 78cadc9

Browse files
fix(Table): affix width not correct when there is a fixed column (#4172)
* chore(Affix): update demo * fix(Table): affix width not correct when there is a fixed column * perf: replace `useRef` with `useState` * fix(Affix): prevent negative `fixedTop` * chore: stash changelog [ci skip] --------- Co-authored-by: tdesign-bot <tdesign@tencent.com>
1 parent 12cef3e commit 78cadc9

8 files changed

Lines changed: 681 additions & 194 deletions

File tree

packages/components/affix/Affix.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ const Affix = forwardRef<AffixRef, AffixProps>((props, ref) => {
5959
let fixedTop: number | false;
6060
if (props.offsetBottom !== undefined && props.offsetTop === undefined) {
6161
const bottomThreshold = containerToBottom - (offsetBottom ?? 0);
62-
if (wrapToBottom >= bottomThreshold) {
62+
// When the container has scrolled out of the viewport
63+
// the affix element should not be fixed to avoid a negative top value
64+
if (containerToBottom > 0 && wrapToBottom >= bottomThreshold) {
6365
fixedTop = bottomThreshold - wrapHeight;
6466
} else {
6567
fixedTop = false;

packages/components/affix/_example/container.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import React, { useState } from 'react';
1+
import React, { useRef } from 'react';
22
import { Affix, Button } from 'tdesign-react';
33

44
export default function ContainerExample() {
5-
const [container, setContainer] = useState(null);
5+
const containerRef = useRef<HTMLDivElement>();
66

77
const backgroundStyle = {
88
height: '1500px',
@@ -18,7 +18,7 @@ export default function ContainerExample() {
1818

1919
return (
2020
<div
21-
ref={setContainer}
21+
ref={containerRef}
2222
style={{
2323
border: '1px solid var(--component-stroke)',
2424
borderRadius: '3px',
@@ -29,10 +29,10 @@ export default function ContainerExample() {
2929
}}
3030
>
3131
<div style={backgroundStyle}>
32-
<Affix zIndex={10} offsetTop={50} container={container}>
32+
<Affix zIndex={10} offsetTop={50} container={() => containerRef.current}>
3333
<Button>Top</Button>
3434
</Affix>
35-
<Affix offsetBottom={50} container={container}>
35+
<Affix offsetBottom={50} container={() => containerRef.current}>
3636
<Button>Bottom</Button>
3737
</Affix>
3838
</div>

packages/components/table/BaseTable.tsx

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import React, { forwardRef, RefAttributes, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
1+
import React, { forwardRef, type RefAttributes, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
22
import classNames from 'classnames';
33
import { pick } from 'lodash-es';
4+
45
import log from '@tdesign/common-js/log/index';
56
import { getIEVersion } from '@tdesign/common-js/utils/helper';
67
import Affix, { type AffixRef } from '../affix';
@@ -365,14 +366,14 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
365366
const barWidth = isWidthOverflow ? scrollbarWidth : 0;
366367
const affixHeaderWrapHeight = affixHeaderHeight - barWidth;
367368
const affixHeaderWrapHeightStyle = {
368-
width: `${tableWidth.current}px`,
369+
width: `${tableWidth}px`,
369370
height: `${affixHeaderWrapHeight}px`,
370371
opacity: headerOpacity,
371372
};
372-
const affixedHeader = Boolean((headerAffixedTop || virtualConfig.isVirtualScroll) && tableWidth.current) && (
373+
const affixedHeader = Boolean((headerAffixedTop || virtualConfig.isVirtualScroll) && tableWidth) && (
373374
<div
374375
ref={affixHeaderRef}
375-
style={{ width: `${tableWidth.current - borderWidth}px`, opacity: headerOpacity }}
376+
style={{ width: `${tableWidth}px`, opacity: headerOpacity }}
376377
className={classNames([
377378
'scrollbar',
378379
{
@@ -382,7 +383,7 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
382383
>
383384
<table
384385
className={classNames(tableElmClasses)}
385-
style={{ ...tableElementStyles, width: tableElmWidth.current ? `${tableElmWidth.current}px` : undefined }}
386+
style={{ ...tableElementStyles, width: tableElmWidth ? `${tableElmWidth}px` : undefined }}
386387
>
387388
{renderColGroup(true)}
388389
{showHeader && <THead {...headProps} />}
@@ -431,7 +432,7 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
431432
const totalMarginTop = tableFootHeight + marginScrollbarWidth;
432433
const hasFooter = props.footData?.length || props.footerSummary;
433434
const affixedFooter = Boolean(
434-
(virtualConfig.isVirtualScroll || props.footerAffixedBottom) && hasFooter && tableWidth.current,
435+
(virtualConfig.isVirtualScroll || props.footerAffixedBottom) && hasFooter && tableWidth,
435436
) && (
436437
<Affix
437438
className={tableBaseClass.affixedFooterWrap}
@@ -443,15 +444,15 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
443444
>
444445
<div
445446
ref={affixFooterRef}
446-
style={{ width: `${tableWidth.current - borderWidth}px`, opacity: Number(showAffixFooter) }}
447+
style={{ width: `${tableWidth}px`, opacity: Number(showAffixFooter) }}
447448
className={classNames([
448449
'scrollbar',
449450
{ [tableBaseClass.affixedFooterElm]: props.footerAffixedBottom || virtualConfig.isVirtualScroll },
450451
])}
451452
>
452453
<table
453454
className={tableElmClasses}
454-
style={{ ...tableElementStyles, width: tableElmWidth.current ? `${tableElmWidth.current}px` : undefined }}
455+
style={{ ...tableElementStyles, width: tableElmWidth ? `${tableElmWidth}px` : undefined }}
455456
>
456457
{renderColGroup(true)}
457458
<TFoot
@@ -516,8 +517,8 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
516517
style={{
517518
...tableElementStyles,
518519
width:
519-
resizable && isWidthOverflow && tableElmWidth.current
520-
? `${tableElmWidth.current}px`
520+
resizable && isWidthOverflow && tableElmWidth
521+
? `${tableElmWidth}px`
521522
: tableElementStyles.width,
522523
}}
523524
>
@@ -633,7 +634,6 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
633634
tableElmClasses,
634635
tableElementStyles,
635636
columns,
636-
spansAndLeafNodes,
637637
showHeader,
638638
props.headerAffixedTop,
639639
],
@@ -655,7 +655,6 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
655655
tableElmWidth,
656656
affixFooterRef,
657657
borderWidth,
658-
bordered,
659658
isWidthOverflow,
660659
scrollbarWidth,
661660
tableElmClasses,
@@ -669,7 +668,6 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
669668
props.footerSummary,
670669
props.footerAffixedBottom,
671670
props.rowspanAndColspanInFooter,
672-
tableWidth.current,
673671
],
674672
);
675673

@@ -698,12 +696,12 @@ const BaseTable = forwardRef<BaseTableRef, BaseTableProps>((originalProps, ref)
698696
ref={horizontalScrollbarRef}
699697
className={classNames(['scrollbar', tableBaseClass.obviousScrollbar])}
700698
style={{
701-
width: `${tableWidth.current}px`,
699+
width: `${tableWidth}px`,
702700
overflow: 'auto',
703701
opacity: Number(showAffixFooter),
704702
}}
705703
>
706-
<div style={{ width: `${tableElmWidth.current}px`, height: '5px' }}></div>
704+
<div style={{ width: `${tableElmWidth}px`, height: '5px' }}></div>
707705
</div>
708706
</Affix>
709707
);

packages/components/table/TBody.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export interface TableBodyProps extends BaseTableProps {
2121
tableRef?: MutableRefObject<HTMLDivElement>;
2222
tableContentRef?: MutableRefObject<HTMLDivElement>;
2323
cellEmptyContent: TdBaseTableProps['cellEmptyContent'];
24-
tableWidth?: MutableRefObject<number>;
24+
tableWidth?: number;
2525
isWidthOverflow?: boolean;
2626
virtualConfig: VirtualScrollConfig;
2727
pagination?: PaginationProps;
@@ -95,7 +95,7 @@ export default function TBody(props: TableBodyProps) {
9595
<td colSpan={columns.length}>
9696
<div
9797
className={classNames([tableBaseClass.empty, { [tableFullRowClasses.innerFullRow]: props.isWidthOverflow }])}
98-
style={props.isWidthOverflow ? { width: `${props.tableWidth.current}px` } : {}}
98+
style={props.isWidthOverflow ? { width: `${props.tableWidth}px` } : {}}
9999
>
100100
{props.empty || t(global.empty)}
101101
</div>
@@ -139,7 +139,7 @@ export default function TBody(props: TableBodyProps) {
139139
<td colSpan={columns.length}>
140140
<div
141141
className={classNames({ [tableFullRowClasses.innerFullRow]: isFixedToLeft }) || undefined}
142-
style={isFixedToLeft ? { width: `${props.tableWidth.current}px` } : {}}
142+
style={isFixedToLeft ? { width: `${props.tableWidth}px` } : {}}
143143
>
144144
<div className={tableFullRowClasses.innerFullElement}>{fullRowNode}</div>
145145
</div>
@@ -153,7 +153,7 @@ export default function TBody(props: TableBodyProps) {
153153
row,
154154
index: rowIndex,
155155
columns,
156-
tableWidth: props.tableWidth.current,
156+
tableWidth: props.tableWidth,
157157
isWidthOverflow: props.isWidthOverflow,
158158
};
159159
return props.renderExpandedRow?.(p);

packages/components/table/hooks/useFixed.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,20 @@ export default function useFixed(
9191
maxHeight,
9292
headerAffixedTop,
9393
bordered,
94+
resizable: columnResizable,
9495
} = props;
96+
9597
const preFinalColumns = usePrevious(finalColumns);
96-
const [data, setData] = useState<TableRowData[]>([]);
98+
9799
const tableContentRef = useRef<HTMLDivElement>(null);
100+
const tableElmRef = useRef<HTMLTableElement>(null);
101+
const thWidthList = useRef<{ [colKey: string]: number }>({});
102+
103+
const [data, setData] = useState<TableRowData[]>([]);
98104
const [isFixedHeader, setIsFixedHeader] = useState(false);
99105
const [isWidthOverflow, setIsWidthOverflow] = useState(false);
100-
const tableElmRef = useRef<HTMLTableElement>(null);
106+
const [tableWidth, setTableWidth] = useState(0);
107+
const [tableElmWidth, setTableElmWidthState] = useState(0);
101108
// CSS 样式设置了固定 6px
102109
const [scrollbarWidth, setScrollbarWidth] = useState(6);
103110
// 固定列、固定表头、固定表尾等内容的位置信息
@@ -111,16 +118,10 @@ export default function useFixed(
111118
left: 0,
112119
top: 0,
113120
});
114-
const tableWidth = useRef(0);
115-
const tableElmWidth = useRef(0);
116-
const thWidthList = useRef<{ [colKey: string]: number }>({});
117-
118121
const [isFixedColumn, setIsFixedColumn] = useState(false);
119122
const [isFixedRightColumn, setIsFixedRightColumn] = useState(false);
120123
const [isFixedLeftColumn, setIsFixedLeftColumn] = useState(false);
121124

122-
const columnResizable = props.resizable;
123-
124125
// 没有表头吸顶,没有虚拟滚动,则不需要表头宽度计算
125126
const notNeedThWidthList = useMemo(
126127
() =>
@@ -363,17 +364,16 @@ export default function useFixed(
363364
}, []);
364365

365366
const setTableElmWidth = (width: number) => {
366-
if (tableElmWidth.current === width) return;
367-
tableElmWidth.current = width;
367+
if (tableElmWidth === width) return;
368+
setTableElmWidthState(width);
368369
};
369370

370371
const updateTableWidth = () => {
371372
const tRef = tableContentRef.current;
372-
const rect = tRef?.getBoundingClientRect?.();
373-
if (!rect) return;
374-
// 去除滚动条宽度
375-
const reduceWidth = isWidthOverflow ? scrollbarWidth : 0;
376-
tableWidth.current = rect.width - reduceWidth - (props.bordered ? 1 : 0);
373+
if (!tRef) return;
374+
// clientWidth excludes border and scrollbar
375+
setTableWidth(tRef.clientWidth);
376+
377377
const elmRect = tableElmRef?.current?.getBoundingClientRect();
378378
if (elmRect?.width) {
379379
setTableElmWidth(elmRect?.width);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
pr_number: 4172
3+
contributor: RylanBot
4+
---
5+
6+
- fix(Table): 修复存在 `fixed` 列时,吸顶表头和吸底表尾和表格不对齐的问题 @RylanBot ([#4172](https://github.com/Tencent/tdesign-react/pull/4172))
7+
- fix(Affix): 修复 `offsetBottom` 模式下容器滚出视口时 `top` 计算为负值的问题 @RylanBot ([#4172](https://github.com/Tencent/tdesign-react/pull/4172))

0 commit comments

Comments
 (0)