diff --git a/assets/index.less b/assets/index.less
index f75d255dd..8cfa66765 100644
--- a/assets/index.less
+++ b/assets/index.less
@@ -18,6 +18,23 @@
border-bottom: 0;
}
+.cellScrollBar(@bgColor: #fff) {
+ .@{tablePrefixCls}-cell-scrollbar::after {
+ position: absolute;
+ content: '';
+ top: 0;
+ bottom: 0;
+ left: -1px;
+ width: 1px;
+ background: @bgColor;
+
+ .@{tablePrefixCls}-rtl& {
+ right: -1px;
+ left: auto;
+ }
+ }
+}
+
.@{tablePrefixCls} {
font-size: @font-size-base;
color: @text-color;
@@ -165,20 +182,7 @@
text-align: center;
}
- .@{tablePrefixCls}-cell-scrollbar::after {
- position: absolute;
- content: '';
- top: 0;
- bottom: 0;
- left: -1px;
- width: 1px;
- background: @table-head-background-color;
-
- .@{tablePrefixCls}-rtl& {
- right: -1px;
- left: auto;
- }
- }
+ .cellScrollBar(@table-head-background-color);
}
&-header {
@@ -205,19 +209,31 @@
border-radius: 5px 0 0 0;
}
- &-body {
+ &-body,
+ &-summary {
.tableBorder();
border-top: 0;
}
- &-fixed-column &-body::after {
- content: '';
- position: absolute;
- right: 0;
- top: 0;
- bottom: 0;
- border-right: @border;
- z-index: 1;
+ &-fixed-column {
+ &-body,
+ &-summary {
+ &::after {
+ content: '';
+ position: absolute;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ border-right: @border;
+ z-index: 1;
+ }
+ }
+ }
+
+ &-fixed-column &-summary {
+ &::-webkit-scrollbar {
+ display: none;
+ }
}
// ================= Expand =================
@@ -286,6 +302,7 @@
td {
background: #fff;
}
+ .cellScrollBar();
}
&-sticky {
&-header {
diff --git a/docs/examples/fixedColumns.tsx b/docs/examples/fixedColumns.tsx
index d65281c28..1042a1701 100644
--- a/docs/examples/fixedColumns.tsx
+++ b/docs/examples/fixedColumns.tsx
@@ -61,22 +61,26 @@ const Demo = () => (
b || c}
- scroll={{ x: 1200 }}
+ scroll={{ x: 1200, y: 200 }}
data={data}
- summary={() => (
- <>
-
-
-
- Summary
-
-
- Content
-
- Right
-
- >
- )}
+ summary={{
+ render: () => (
+ <>
+
+
+
+ Summary
+
+
+ Content
+
+ Right
+
+ >
+ ),
+ fixed: true,
+ position: 'bottom',
+ }}
/>
);
diff --git a/src/Footer/Cell.tsx b/src/Footer/Cell.tsx
index a8d562599..1b45c00a4 100644
--- a/src/Footer/Cell.tsx
+++ b/src/Footer/Cell.tsx
@@ -20,9 +20,11 @@ export default function SummaryCell({
rowSpan,
align,
}: SummaryCellProps) {
- const { prefixCls, fixedInfoList } = React.useContext(TableContext);
+ const { prefixCls, isSummaryFixed, fixedInfoList, summaryFixedInfoList } = React.useContext(
+ TableContext,
+ );
- const fixedInfo = fixedInfoList[index];
+ const fixedInfo = isSummaryFixed ? summaryFixedInfoList[index] : fixedInfoList[index];
return (
| ;
+export default function FooterRow({ children, ...props }: FooterRowProps) {
+ const {
+ prefixCls,
+ scrollbarSize,
+ isSummaryFixed,
+ summaryFixedInfoList,
+ columnsWithScrollbar,
+ } = React.useContext(TableContext);
+
+ const summaryListLength = summaryFixedInfoList.length;
+ const summaryColumn = columnsWithScrollbar[summaryListLength - 1];
+
+ let additionalProps: React.HTMLAttributes;
+
+ if (isSummaryFixed && summaryColumn.onHeaderCell) {
+ additionalProps = summaryColumn.onHeaderCell(summaryColumn);
+ }
+
+ return (
+
+ {children}
+ {isSummaryFixed && !!scrollbarSize && (
+ |
+ )}
+
+ );
}
diff --git a/src/Footer/index.tsx b/src/Footer/index.tsx
index d6dd703ec..9be650eef 100644
--- a/src/Footer/index.tsx
+++ b/src/Footer/index.tsx
@@ -8,8 +8,12 @@ export interface FooterProps {
}
function Footer({ children }: FooterProps) {
- const { prefixCls } = React.useContext(TableContext);
- return {children};
+ const { prefixCls, isSummaryShowTop } = React.useContext(TableContext);
+ return isSummaryShowTop ? (
+ {children}
+ ) : (
+ {children}
+ );
}
export default Footer;
diff --git a/src/Header/FixedHeader.tsx b/src/Header/FixedHeader.tsx
index 40494e1c7..0466abcd0 100644
--- a/src/Header/FixedHeader.tsx
+++ b/src/Header/FixedHeader.tsx
@@ -7,6 +7,8 @@ import Header from './Header';
import ColGroup from '../ColGroup';
import type { ColumnsType, ColumnType } from '../interface';
import TableContext from '../context/TableContext';
+import useScrollBarColumns from '../hooks/useScrollBarColumns';
+import useCalcStickyOffsets from '../hooks/useCalcStickyOffsets';
function useColumnWidth(colWidths: readonly number[], columCount: number) {
return useMemo(() => {
@@ -56,8 +58,6 @@ const FixedHeader = React.forwardRef>(
) => {
const { prefixCls, scrollbarSize, isSticky } = React.useContext(TableContext);
- const combinationScrollBarSize = isSticky && !fixHeader ? 0 : scrollbarSize;
-
// Pass wheel to scroll event
const scrollRef = React.useRef(null);
@@ -88,38 +88,31 @@ const FixedHeader = React.forwardRef>(
);
// Add scrollbar column
- const lastColumn = flattenColumns[flattenColumns.length - 1];
- const ScrollBarColumn: ColumnType = {
- fixed: lastColumn ? lastColumn.fixed : null,
- onHeaderCell: () => ({
- className: `${prefixCls}-cell-scrollbar`,
- }),
- };
-
- const columnsWithScrollbar = useMemo>(
- () => (combinationScrollBarSize ? [...columns, ScrollBarColumn] : columns),
- [combinationScrollBarSize, columns],
- );
-
- const flattenColumnsWithScrollbar = useMemo(
- () => (combinationScrollBarSize ? [...flattenColumns, ScrollBarColumn] : flattenColumns),
- [combinationScrollBarSize, flattenColumns],
- );
+ const { combinationScrollBarSize, columnsWithScrollbar } = useScrollBarColumns>({
+ columns,
+ prefixCls,
+ scrollbarSize,
+ isSticky,
+ fixHeader,
+ });
+
+ const { columnsWithScrollbar: flattenColumnsWithScrollbar } = useScrollBarColumns<
+ ColumnType[]
+ >({
+ columns: flattenColumns,
+ prefixCls,
+ scrollbarSize,
+ isSticky,
+ fixHeader,
+ });
// Calculate the sticky offsets
- const headerStickyOffsets = useMemo(() => {
- const { right, left } = stickyOffsets;
- return {
- ...stickyOffsets,
- left:
- direction === 'rtl' ? [...left.map(width => width + combinationScrollBarSize), 0] : left,
- right:
- direction === 'rtl'
- ? right
- : [...right.map(width => width + combinationScrollBarSize), 0],
- isSticky,
- };
- }, [combinationScrollBarSize, stickyOffsets, isSticky]);
+ const headerStickyOffsets = useCalcStickyOffsets({
+ stickyOffsets,
+ combinationScrollBarSize,
+ direction,
+ isSticky,
+ });
const mergedColumnWidth = useColumnWidth(colWidths, columCount);
diff --git a/src/Table.tsx b/src/Table.tsx
index 63ce3640d..82ad2d96a 100644
--- a/src/Table.tsx
+++ b/src/Table.tsx
@@ -54,6 +54,7 @@ import type {
ColumnType,
CustomizeScrollBody,
TableSticky,
+ TableDirection,
} from './interface';
import TableContext from './context/TableContext';
import BodyContext from './context/BodyContext';
@@ -71,6 +72,8 @@ import { findAllChildrenKeys, renderExpandIcon } from './utils/expandUtil';
import { getCellFixedInfo } from './utils/fixUtil';
import StickyScrollBar from './stickyScrollBar';
import useSticky from './hooks/useSticky';
+import useScrollBarColumns from './hooks/useScrollBarColumns';
+import useCalcStickyOffsets from './hooks/useCalcStickyOffsets';
// Used for conditions cache
const EMPTY_DATA = [];
@@ -78,6 +81,10 @@ const EMPTY_DATA = [];
// Used for customize scroll
const EMPTY_SCROLL_TARGET = {};
+// Used for body content render
+const BODY_CONTENT_TBODY = 'body';
+const BODY_CONTENT_TSUMMARY = 'summary';
+
export const INTERNAL_HOOKS = 'rc-table-internal-hook';
interface MemoTableContentProps {
@@ -87,6 +94,10 @@ interface MemoTableContentProps {
props: any;
}
+type SummaryRender = (data: readonly RecordType[]) => React.ReactNode;
+
+type SummaryPosition = 'top' | 'bottom';
+
const MemoTableContent = React.memo(
({ children }) => children as React.ReactElement,
@@ -123,7 +134,13 @@ export interface TableProps extends LegacyExpandableProps<
// Additional Part
title?: PanelRender;
footer?: PanelRender;
- summary?: (data: readonly RecordType[]) => React.ReactNode;
+ summary?:
+ | SummaryRender
+ | {
+ render: SummaryRender;
+ fixed?: boolean;
+ position?: SummaryPosition;
+ };
// Customize
id?: string;
@@ -133,7 +150,7 @@ export interface TableProps extends LegacyExpandableProps<
onHeaderRow?: GetComponentProps[]>;
emptyText?: React.ReactNode | (() => React.ReactNode);
- direction?: 'ltr' | 'rtl';
+ direction?: TableDirection;
// =================================== Internal ===================================
/**
@@ -199,6 +216,17 @@ function Table(props: TableProps = summary as any;
+ let summaryFixed: boolean = false;
+ let summaryPosition: SummaryPosition = 'bottom';
+
+ if (typeof summary === 'object' && summary !== null) {
+ summaryRender = summary.render;
+ summaryFixed = summary.fixed;
+ summaryPosition = summary.position;
+ }
+
// ===================== Effects ======================
const [scrollbarSize, setScrollbarSize] = React.useState(0);
@@ -372,6 +400,7 @@ function Table(props: TableProps();
const scrollHeaderRef = React.useRef();
const scrollBodyRef = React.useRef();
+ const scrollSummaryRef = React.useRef();
const [pingedLeft, setPingedLeft] = React.useState(false);
const [pingedRight, setPingedRight] = React.useState(false);
const [colsWidths, updateColsWidths] = useLayoutState(new Map());
@@ -396,6 +425,16 @@ function Table(props: TableProps[]
+ >({
+ columns: flattenColumns,
+ prefixCls,
+ scrollbarSize,
+ isSticky,
+ fixHeader,
+ });
+
if (fixHeader) {
scrollYStyle = {
overflowY: 'scroll',
@@ -460,6 +499,7 @@ function Table(props: TableProps(props: TableProps
);
- const bodyColGroup = (
- width)} columns={flattenColumns} />
+ const flattenColWidths = flattenColumns.map(({ width }) => width);
+
+ const BodyColGroup = () => (
+
);
- const footerTable = summary && ;
+ const BodyColGroupWithScrollBar = () => (
+
+ );
+
+ const footerTable = summaryRender && ;
const customizeScrollBody = getComponent(['body']) as CustomizeScrollBody;
+ const isTableFixed = fixHeader || isSticky;
+
+ const isSummaryShowTop = isTableFixed && summaryPosition === 'top';
+ const isSummaryFixed = isTableFixed && summaryFixed;
+
+ const getBodyContent = (type: string, mergeSummary?: boolean) => {
+ const isBody = type === BODY_CONTENT_TBODY;
+ const ref = isBody ? scrollBodyRef : scrollSummaryRef;
+ const tableContent = isBody ? bodyTable : footerTable;
+
+ return (
+
+
+ {!isBody && summaryFixed ? : }
+ {tableContent}
+ {mergeSummary && type !== BODY_CONTENT_TSUMMARY && footerTable}
+
+
+ );
+ };
+
if (
process.env.NODE_ENV !== 'production' &&
typeof customizeScrollBody === 'function' &&
@@ -575,7 +658,7 @@ function Table(props: TableProps(props: TableProps
-
- {bodyColGroup}
- {bodyTable}
- {footerTable}
-
-
+ bodyContent = summaryFixed ? (
+ <>
+ {footerTable && isSummaryShowTop && getBodyContent(BODY_CONTENT_TSUMMARY)}
+ {getBodyContent(BODY_CONTENT_TBODY)}
+ {footerTable && !isSummaryShowTop && getBodyContent(BODY_CONTENT_TSUMMARY)}
+ >
+ ) : (
+ getBodyContent(BODY_CONTENT_TBODY, true)
);
}
@@ -666,7 +736,7 @@ function Table(props: TableProps
- {bodyColGroup}
+
{showHeader !== false && }
{bodyTable}
{footerTable}
@@ -714,6 +784,13 @@ function Table(props: TableProps{fullTable};
}
+ const headerStickyOffsets = useCalcStickyOffsets({
+ stickyOffsets,
+ combinationScrollBarSize,
+ direction,
+ isSticky,
+ });
+
const TableContextValue = React.useMemo(
() => ({
prefixCls,
@@ -723,7 +800,13 @@ function Table(props: TableProps
getCellFixedInfo(colIndex, colIndex, flattenColumns, stickyOffsets, direction),
),
+ summaryFixedInfoList: columnsWithScrollbar.map((_, colIndex) =>
+ getCellFixedInfo(colIndex, colIndex, columnsWithScrollbar, headerStickyOffsets, direction),
+ ),
+ columnsWithScrollbar,
isSticky,
+ isSummaryShowTop,
+ isSummaryFixed,
}),
[
prefixCls,
@@ -731,9 +814,12 @@ function Table(props: TableProps[];
+
isSticky: boolean;
+
+ isSummaryShowTop: boolean;
+
+ isSummaryFixed: boolean;
}
const TableContext = React.createContext(null);
diff --git a/src/hooks/useCalcStickyOffsets.ts b/src/hooks/useCalcStickyOffsets.ts
new file mode 100644
index 000000000..60762c270
--- /dev/null
+++ b/src/hooks/useCalcStickyOffsets.ts
@@ -0,0 +1,30 @@
+import * as React from 'react';
+import { StickyOffsets, TableDirection } from '../interface';
+
+interface ICalcStickyOffsets {
+ stickyOffsets: StickyOffsets;
+ combinationScrollBarSize: number;
+ direction: TableDirection;
+ isSticky: boolean;
+}
+
+export default function useCalcStickyOffsets({
+ stickyOffsets,
+ combinationScrollBarSize,
+ direction,
+ isSticky,
+}: ICalcStickyOffsets): StickyOffsets {
+ const headerStickyOffsets = React.useMemo(() => {
+ const { right, left } = stickyOffsets;
+ return {
+ ...stickyOffsets,
+ left:
+ direction === 'rtl' ? [...left.map(width => width + combinationScrollBarSize), 0] : left,
+ right:
+ direction === 'rtl' ? right : [...right.map(width => width + combinationScrollBarSize), 0],
+ isSticky,
+ };
+ }, [combinationScrollBarSize, stickyOffsets, isSticky]);
+
+ return headerStickyOffsets;
+}
diff --git a/src/hooks/useScrollBarColumns.ts b/src/hooks/useScrollBarColumns.ts
new file mode 100644
index 000000000..f24bd7822
--- /dev/null
+++ b/src/hooks/useScrollBarColumns.ts
@@ -0,0 +1,41 @@
+import * as React from 'react';
+import { ColumnType } from '../interface';
+
+interface IScrollBarColumns {
+ columns: any;
+ prefixCls: string;
+ scrollbarSize: number;
+ isSticky: boolean;
+ fixHeader: boolean;
+}
+
+export default function useScrollBarColumns({
+ columns,
+ prefixCls,
+ scrollbarSize,
+ isSticky,
+ fixHeader,
+}: IScrollBarColumns): {
+ combinationScrollBarSize: number;
+ columnsWithScrollbar: T;
+} {
+ const combinationScrollBarSize = isSticky && !fixHeader ? 0 : scrollbarSize;
+
+ const lastColumn = columns[columns.length - 1];
+ const ScrollBarColumn: ColumnType = {
+ fixed: lastColumn ? lastColumn.fixed : null,
+ onHeaderCell: () => ({
+ className: `${prefixCls}-cell-scrollbar`,
+ }),
+ };
+
+ const columnsWithScrollbar = React.useMemo(
+ () => (combinationScrollBarSize ? [...columns, ScrollBarColumn] : columns),
+ [combinationScrollBarSize, columns],
+ );
+
+ return {
+ combinationScrollBarSize,
+ columnsWithScrollbar,
+ };
+}
diff --git a/src/interface.ts b/src/interface.ts
index a338955c6..2bfd2fa0a 100644
--- a/src/interface.ts
+++ b/src/interface.ts
@@ -25,6 +25,8 @@ export type DefaultRecordType = Record;
export type TableLayout = 'auto' | 'fixed';
+export type TableDirection = 'ltr' | 'rtl';
+
// ==================== Row =====================
export type RowClassName = (
record: RecordType,
diff --git a/tests/Table.spec.js b/tests/Table.spec.js
index 2cc5ad977..47458c22a 100644
--- a/tests/Table.spec.js
+++ b/tests/Table.spec.js
@@ -201,6 +201,34 @@ describe('Table.Basic', () => {
expect(wrapper.find('tfoot').render()).toMatchSnapshot();
});
+
+ it('support data type', () => {
+ const wrapper = mount(
+ (
+
+
+ Light
+
+ Bamboo
+
+ ),
+ fixed: true,
+ position: 'top',
+ }}
+ scroll={{ x: 1200, y: 200 }}
+ />,
+ );
+
+ expect(wrapper.find('.rc-table-summary-content').render()).toMatchSnapshot();
+ });
});
it('renders with id correctly', () => {
diff --git a/tests/__snapshots__/Table.spec.js.snap b/tests/__snapshots__/Table.spec.js.snap
index c19869220..991a09865 100644
--- a/tests/__snapshots__/Table.spec.js.snap
+++ b/tests/__snapshots__/Table.spec.js.snap
@@ -737,7 +737,7 @@ exports[`Table.Basic renders rowSpan correctly 1`] = `
exports[`Table.Basic summary support data type 1`] = `
`;
+exports[`Table.Basic summary support data type 2`] = `
+
+
+
+ Light
+ |
+
+ Bamboo
+ |
+ |
+
+
+`;
+
exports[`Table.Basic syntactic sugar 1`] = `
|