Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions assets/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
z-index: 1;
}

&-fix-right:last-child {
&-fix-right:last-child:not(&-fix-sticky) {
border-right-color: transparent;
}

Expand Down Expand Up @@ -290,7 +290,7 @@
&-sticky {
&-header {
position: sticky;
z-index: 10;
z-index: 2;
}
&-scroll {
position: fixed;
Expand All @@ -300,6 +300,7 @@
border-top: 1px solid #f3f3f3;
opacity: 0.6;
transition: transform 0.1s ease-in 0s;
z-index: 2;
&:hover {
transform: scaleY(1.2);
transform-origin: center bottom;
Expand All @@ -311,6 +312,9 @@
&:hover {
background-color: #999;
}
&-active {
background-color: #999;
}
}
}
}
Expand Down
77 changes: 77 additions & 0 deletions examples/stickyHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,54 @@ interface RecordType {
a?: string;
b?: string;
c?: string;
d?: number;
key?: string;
}

const fixedColumns: ColumnType<RecordType>[] = [
{ title: 'title1', dataIndex: 'a', key: 'a', width: 100, fixed: 'left' },
{ title: 'title2', dataIndex: 'b', key: 'b', width: 100, fixed: 'left', ellipsis: true },
{ title: 'title3', dataIndex: 'c', key: 'c' },
{ title: 'title4', dataIndex: 'b', key: 'd' },
{ title: 'title5', dataIndex: 'b', key: 'e' },
{ title: 'title6', dataIndex: 'b', key: 'f' },
{
title: (
<div>
title7
<br />
<br />
<br />
Hello world!
</div>
),
dataIndex: 'b',
key: 'g',
},
{ title: 'title8', dataIndex: 'b', key: 'h' },
{ title: 'title9', dataIndex: 'b', key: 'i' },
{ title: 'title10', dataIndex: 'b', key: 'j' },
{ title: 'title11', dataIndex: 'b', key: 'k' },
{ title: 'title12', dataIndex: 'b', key: 'l', width: 100, fixed: 'right' },
];

const fixedData = [
{
a: '123',
b: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
d: 3,
key: '1',
},
{ a: 'cdd', b: 'edd12221', d: 3, key: '2' },
{ a: '133', c: 'edd12221', d: 2, key: '3' },
{ a: '133', c: 'edd12221', d: 2, key: '4' },
{ a: '133', c: 'edd12221', d: 2, key: '5' },
{ a: '133', c: 'edd12221', d: 2, key: '6' },
{ a: '133', c: 'edd12221', d: 2, key: '7' },
{ a: '133', c: 'edd12221', d: 2, key: '8' },
{ a: '133', c: 'edd12221', d: 2, key: '9' },
];

const columns: ColumnType<{ a: string; b: string; c: string }>[] = [
{ title: 'title1', dataIndex: 'a', key: 'a', width: 100 },
{ title: 'title2', dataIndex: 'b', key: 'b', width: 100, align: 'right' },
Expand Down Expand Up @@ -108,6 +154,37 @@ const Demo = () => (
marginBottom: 100,
}}
/>

<h2>Sticky with fixed columns</h2>
<div style={{ width: 800 }}>
<Table<RecordType>
columns={fixedColumns}
data={fixedData}
sticky
scroll={{
x: 1200,
}}
style={{
marginBottom: 100,
}}
/>
</div>

<h2>Sticky with fixed columns and scroll.y</h2>
<div style={{ width: 800 }}>
<Table<RecordType>
columns={fixedColumns}
data={fixedData}
sticky
scroll={{
x: 1200,
y: 300,
}}
style={{
marginBottom: 100,
}}
/>
</div>
</div>
);

Expand Down
4 changes: 4 additions & 0 deletions src/Cell/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export interface CellProps<RecordType extends DefaultRecordType> {
additionalProps?: React.HTMLAttributes<HTMLElement>;

rowType?: 'header' | 'body' | 'footer';

isSticky?: boolean;
}

function Cell<RecordType extends DefaultRecordType>(
Expand All @@ -83,6 +85,7 @@ function Cell<RecordType extends DefaultRecordType>(
ellipsis,
align,
rowType,
isSticky,
}: CellProps<RecordType>,
ref: React.Ref<any>,
): React.ReactElement {
Expand Down Expand Up @@ -188,6 +191,7 @@ function Cell<RecordType extends DefaultRecordType>(
[`${cellPrefixCls}-fix-right-last`]: lastFixRight,
[`${cellPrefixCls}-ellipsis`]: ellipsis,
[`${cellPrefixCls}-with-append`]: appendNode,
[`${cellPrefixCls}-fix-sticky`]: (isFixLeft || isFixRight) && isSticky,
},
additionalProps.className,
cellClassName,
Expand Down
23 changes: 14 additions & 9 deletions src/Header/FixedHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ function FixedHeader<RecordType>({
fixHeader,
...props
}: FixedHeaderProps<RecordType>) {
const { prefixCls, scrollbarSize } = React.useContext(TableContext);
const { prefixCls, scrollbarSize, isSticky } = React.useContext(TableContext);

const combinationScrollBarSize = isSticky && !fixHeader ? 0 : scrollbarSize;

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

const columnsWithScrollbar = useMemo<ColumnsType<RecordType>>(
() => (scrollbarSize && fixHeader ? [...columns, ScrollBarColumn] : columns),
[scrollbarSize, columns, fixHeader],
() => (combinationScrollBarSize ? [...columns, ScrollBarColumn] : columns),
[combinationScrollBarSize, columns],
);

const flattenColumnsWithScrollbar = useMemo<ColumnType<RecordType>[]>(
() => (scrollbarSize ? [...flattenColumns, ScrollBarColumn] : flattenColumns),
[scrollbarSize, flattenColumns],
() => (combinationScrollBarSize ? [...flattenColumns, ScrollBarColumn] : flattenColumns),
[combinationScrollBarSize, flattenColumns],
);

// Calculate the sticky offsets
const headerStickyOffsets = useMemo(() => {
const { right, left } = stickyOffsets;
return {
...stickyOffsets,
left: direction === 'rtl' ? [...left.map(width => width + scrollbarSize), 0] : left,
right: direction === 'rtl' ? right : [...right.map(width => width + scrollbarSize), 0],
left:
direction === 'rtl' ? [...left.map(width => width + combinationScrollBarSize), 0] : left,
right:
direction === 'rtl' ? right : [...right.map(width => width + combinationScrollBarSize), 0],
isSticky,
};
}, [scrollbarSize, stickyOffsets]);
}, [combinationScrollBarSize, stickyOffsets, isSticky]);

const mergedColumnWidth = useColumnWidth(colWidths, columCount);

return (
<table style={{ tableLayout: 'fixed', visibility: mergedColumnWidth ? null : 'hidden' }}>
<ColGroup
colWidths={mergedColumnWidth ? [...mergedColumnWidth, scrollbarSize] : []}
colWidths={mergedColumnWidth ? [...mergedColumnWidth, combinationScrollBarSize] : []}
columCount={columCount + 1}
columns={flattenColumnsWithScrollbar}
/>
Expand Down
12 changes: 11 additions & 1 deletion src/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -704,8 +704,18 @@ function Table<RecordType extends DefaultRecordType>(props: TableProps<RecordTyp
fixedInfoList: flattenColumns.map((_, colIndex) =>
getCellFixedInfo(colIndex, colIndex, flattenColumns, stickyOffsets, direction),
),
isSticky,
}),
[prefixCls, getComponent, scrollbarSize, direction, flattenColumns, stickyOffsets, direction],
[
prefixCls,
getComponent,
scrollbarSize,
direction,
flattenColumns,
stickyOffsets,
direction,
isSticky,
],
);

const BodyContextValue = React.useMemo(
Expand Down
2 changes: 2 additions & 0 deletions src/context/TableContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export interface TableContextProps {
direction: 'ltr' | 'rtl';

fixedInfoList: FixedInfo[];

isSticky: boolean;
}

const TableContext = React.createContext<TableContextProps>(null);
Expand Down
4 changes: 2 additions & 2 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,15 @@ export interface ColumnType<RecordType> extends ColumnSharedType<RecordType> {

export type ColumnsType<RecordType = unknown> = (
| ColumnGroupType<RecordType>
| ColumnType<RecordType>
)[];
| ColumnType<RecordType>)[];

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

// ================= Fix Column =================
export interface StickyOffsets {
left: number[];
right: number[];
isSticky?: boolean;
}

// ================= Customized =================
Expand Down
24 changes: 15 additions & 9 deletions src/stickyScrollBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import addEventListener from 'rc-util/lib/Dom/addEventListener';
import getScrollBarSize from 'rc-util/lib/getScrollBarSize';
import classNames from 'classnames';
import { getOffset } from 'rc-util/lib/Dom/css';
import TableContext from './context/TableContext';
import { useFrameState } from './hooks/useFrame';
Expand All @@ -17,7 +18,7 @@ const StickyScrollBar: React.ForwardRefRenderFunction<unknown, StickyScrollBarPr
) => {
const { prefixCls } = React.useContext(TableContext);
const bodyScrollWidth = scrollBodyRef.current?.scrollWidth || 0;
const bodyWidth = scrollBodyRef.current?.offsetWidth || 0;
const bodyWidth = scrollBodyRef.current?.clientWidth || 0;
const scrollBarWidth = bodyScrollWidth && bodyWidth * (bodyWidth / bodyScrollWidth);

const scrollBarRef = React.useRef<HTMLDivElement>();
Expand All @@ -29,33 +30,36 @@ const StickyScrollBar: React.ForwardRefRenderFunction<unknown, StickyScrollBarPr
isHiddenScrollBar: false,
});
const refState = React.useRef<{
isScollBarDragable: boolean;
delta: number;
x: number;
}>({
isScollBarDragable: false,
delta: 0,
x: 0,
});
const [isActive, setActive] = React.useState(false);

const onMouseUp: React.MouseEventHandler<HTMLDivElement> = event => {
refState.current.isScollBarDragable = false;
setActive(false);
event.preventDefault();
};

const onMouseDown: React.MouseEventHandler<HTMLDivElement> = event => {
event.persist();
refState.current.isScollBarDragable = true;
refState.current.delta = event.pageX - frameState.scrollLeft;
refState.current.x = 0;
setActive(true);
event.preventDefault();
};

const onMouseMove: React.MouseEventHandler<HTMLDivElement> = event => {
event.preventDefault();
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
const { buttons } = event || (window?.event as any);
if (!refState.current.isScollBarDragable || buttons === 0) {
if (!isActive || buttons === 0) {
// If out body mouse up, we can set isActive false when mouse move
if (isActive) {
setActive(false);
}
return;
}
let left: number =
Expand Down Expand Up @@ -118,7 +122,7 @@ const StickyScrollBar: React.ForwardRefRenderFunction<unknown, StickyScrollBarPr
onMouseUpListener.remove();
onMouseMoveListener.remove();
};
}, [scrollBarWidth]);
}, [scrollBarWidth, isActive]);

React.useEffect(() => {
const onScrollListener = addEventListener(window, 'scroll', onContainerScroll, false);
Expand All @@ -134,7 +138,7 @@ const StickyScrollBar: React.ForwardRefRenderFunction<unknown, StickyScrollBarPr
...state,
scrollLeft:
(scrollBodyRef.current.scrollLeft / scrollBodyRef.current?.scrollWidth) *
scrollBodyRef.current?.offsetWidth,
scrollBodyRef.current?.clientWidth,
}));
}
}, [frameState.isHiddenScrollBar]);
Expand All @@ -155,7 +159,9 @@ const StickyScrollBar: React.ForwardRefRenderFunction<unknown, StickyScrollBarPr
<div
onMouseDown={onMouseDown}
ref={scrollBarRef}
className={`${prefixCls}-sticky-scroll-bar`}
className={classNames(`${prefixCls}-sticky-scroll-bar`, {
[`${prefixCls}-sticky-scroll-bar-active`]: isActive,
})}
style={{
width: `${scrollBarWidth}px`,
transform: `translate3d(${frameState.scrollLeft}px, 0, 0)`,
Expand Down
3 changes: 3 additions & 0 deletions src/utils/fixUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export interface FixedInfo {
// For Rtl Direction
lastFixRight: boolean;
firstFixLeft: boolean;

isSticky: boolean;
}

export function getCellFixedInfo(
Expand Down Expand Up @@ -62,5 +64,6 @@ export function getCellFixedInfo(
firstFixRight,
lastFixRight,
firstFixLeft,
isSticky: stickyOffsets.isSticky,
};
}
Loading