From fd37534316cc6ae61787be753922b06752d358b7 Mon Sep 17 00:00:00 2001 From: LuckyFBB <976060700@qq.com> Date: Wed, 9 Sep 2026 14:24:10 +0800 Subject: [PATCH] feat(table): support nested columns and static members #662 --- src/table/__tests__/table.test.tsx | 55 ++++++++++++++++++++++++ src/table/demos/declarative.tsx | 23 ++++++++++ src/table/demos/dynamicTitle.tsx | 37 ++++++++++++++++ src/table/demos/group.tsx | 65 ++++++++++++++++++++++++++++ src/table/index.md | 8 +++- src/table/index.tsx | 69 ++++++++++++++++++++++-------- 6 files changed, 237 insertions(+), 20 deletions(-) create mode 100644 src/table/demos/declarative.tsx create mode 100644 src/table/demos/dynamicTitle.tsx create mode 100644 src/table/demos/group.tsx diff --git a/src/table/__tests__/table.test.tsx b/src/table/__tests__/table.test.tsx index 2f125069c..465dea851 100644 --- a/src/table/__tests__/table.test.tsx +++ b/src/table/__tests__/table.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { fireEvent, render } from '@testing-library/react'; +import { Table as AntdTable } from 'antd'; import '@testing-library/jest-dom/extend-expect'; import Table from '../index'; @@ -21,6 +22,18 @@ const dataSource = [ ]; describe('test Table', () => { + test('should preserve antd Table static members', () => { + expect(Table.defaultProps).toBe(AntdTable.defaultProps); + expect(Table.SELECTION_COLUMN).toBe(AntdTable.SELECTION_COLUMN); + expect(Table.EXPAND_COLUMN).toBe(AntdTable.EXPAND_COLUMN); + expect(Table.SELECTION_ALL).toBe(AntdTable.SELECTION_ALL); + expect(Table.SELECTION_INVERT).toBe(AntdTable.SELECTION_INVERT); + expect(Table.SELECTION_NONE).toBe(AntdTable.SELECTION_NONE); + expect(Table.Column).toBe(AntdTable.Column); + expect(Table.ColumnGroup).toBe(AntdTable.ColumnGroup); + expect(Table.Summary).toBe(AntdTable.Summary); + }); + test('should support tooltip column attribute', async () => { jest.useFakeTimers(); const { container } = render( @@ -38,4 +51,46 @@ describe('test Table', () => { container.parentElement?.querySelector('.ant-tooltip:not(.ant-tooltip-hidden)') ).toBeInTheDocument(); }); + + test('should support declarative Table.Column', () => { + const { getByText } = render( + + + +
+ ); + + expect(getByText('Name')).toBeInTheDocument(); + expect(getByText('ZhangSan')).toBeInTheDocument(); + }); + + test('should preserve function column title', () => { + const title = jest.fn(() => 'Dynamic Name'); + const { getByText } = render( + + ); + + expect(title).toHaveBeenCalled(); + expect(getByText('Dynamic Name')).toBeInTheDocument(); + }); + + test('should support tooltip in nested columns', () => { + const columns = [ + { + title: 'Information', + children: [ + { + dataIndex: 'name', + title: 'Name', + tooltip: 'This is Name!', + }, + ], + }, + ]; + const { container } = render( +
+ ); + + expect(container.querySelectorAll('.dtc-table__tooltip')).toHaveLength(1); + }); }); diff --git a/src/table/demos/declarative.tsx b/src/table/demos/declarative.tsx new file mode 100644 index 000000000..a0ea9ad27 --- /dev/null +++ b/src/table/demos/declarative.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { Table } from 'dt-react-component'; + +interface DataType { + id: number; + name: string; + age: number; + address: string; +} + +const dataSource: DataType[] = [ + { id: 1, name: 'ZhangSan', age: 17, address: 'New York No. 1 Lake Park' }, + { id: 2, name: 'LiSi', age: 20, address: 'Bei Jing No. 1 Lake Park' }, + { id: 3, name: 'WangWu', age: 23, address: 'Zhe Jiang No. 1 Lake Park' }, +]; + +export default () => ( + rowKey="id" dataSource={dataSource} pagination={false}> + title="Name" dataIndex="name" /> + title="Age" dataIndex="age" /> + title="Address" dataIndex="address" /> +
+); diff --git a/src/table/demos/dynamicTitle.tsx b/src/table/demos/dynamicTitle.tsx new file mode 100644 index 000000000..088248669 --- /dev/null +++ b/src/table/demos/dynamicTitle.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Table } from 'dt-react-component'; + +import type { ColumnsType } from '..'; + +interface DataType { + id: number; + name: string; + age: number; +} + +const columns: ColumnsType = [ + { + title: 'Name', + dataIndex: 'name', + }, + { + title: ({ sortColumns }) => { + const sortOrder = sortColumns?.find(({ column }) => column.dataIndex === 'age')?.order; + const sortText = sortOrder === 'ascend' ? 'ascending' : 'descending'; + return sortOrder ? `Age (${sortText})` : 'Age'; + }, + dataIndex: 'age', + sorter: (previous, next) => previous.age - next.age, + tooltip: 'Click the sorter to see the title update', + }, +]; + +const dataSource: DataType[] = [ + { id: 1, name: 'ZhangSan', age: 23 }, + { id: 2, name: 'LiSi', age: 17 }, + { id: 3, name: 'WangWu', age: 20 }, +]; + +export default () => ( + rowKey="id" columns={columns} dataSource={dataSource} pagination={false} /> +); diff --git a/src/table/demos/group.tsx b/src/table/demos/group.tsx new file mode 100644 index 000000000..4ac2dd79d --- /dev/null +++ b/src/table/demos/group.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { Table } from 'dt-react-component'; + +import type { ColumnsType } from '..'; + +interface DataType { + id: number; + name: string; + age: number; + city: string; + address: string; +} + +const columns: ColumnsType = [ + { + title: 'Basic Information', + tooltip: 'Personal information', + children: [ + { + title: 'Name', + dataIndex: 'name', + tooltip: 'User name', + }, + { + title: 'Age', + dataIndex: 'age', + }, + ], + }, + { + title: 'Contact Address', + children: [ + { + title: 'City', + dataIndex: 'city', + }, + { + title: 'Address', + dataIndex: 'address', + tooltip: 'Detailed contact address', + }, + ], + }, +]; + +const dataSource: DataType[] = [ + { + id: 1, + name: 'ZhangSan', + age: 17, + city: 'New York', + address: 'No. 1 Lake Park', + }, + { + id: 2, + name: 'LiSi', + age: 20, + city: 'Beijing', + address: 'No. 1 Lake Park', + }, +]; + +export default () => ( + rowKey="id" columns={columns} dataSource={dataSource} pagination={false} /> +); diff --git a/src/table/index.md b/src/table/index.md index 31f6379e7..4cad473e0 100644 --- a/src/table/index.md +++ b/src/table/index.md @@ -14,7 +14,13 @@ demo: ## 示例 -基础使用 + + + + + + + ## API diff --git a/src/table/index.tsx b/src/table/index.tsx index 1d98220b6..ba5fb5fd5 100644 --- a/src/table/index.tsx +++ b/src/table/index.tsx @@ -2,7 +2,12 @@ import React, { forwardRef, useMemo } from 'react'; import { QuestionOutlined } from '@dtinsight/react-icons'; import { Table as InternalTable, Tooltip } from 'antd'; import type { LabelTooltipType, WrapperTooltipProps } from 'antd/lib/form/FormItemLabel'; -import type { ColumnType as PrimitiveColumnType, TableProps } from 'antd/lib/table'; +import type { + ColumnGroupType as PrimitiveColumnGroupType, + ColumnsType as PrimitiveColumnsType, + ColumnType as PrimitiveColumnType, + TableProps, +} from 'antd/lib/table'; import classNames from 'classnames'; import './index.scss'; @@ -21,12 +26,19 @@ export interface ColumnType extends PrimitiveColumnType { tooltip?: LabelTooltipType; } -export interface ITableProps extends TableProps { - columns?: ColumnType[]; +export interface ColumnGroupType extends Omit, 'children'> { + tooltip?: LabelTooltipType; + children: ColumnsType; +} + +export type ColumnsType = (ColumnGroupType | ColumnType)[]; + +export interface ITableProps extends Omit, 'columns'> { + columns?: ColumnsType; } export type RefTable = ( - props: React.PropsWithChildren> & { ref?: React.Ref } + props: React.PropsWithChildren> & { ref?: React.Ref } ) => React.ReactElement; function toTooltipProps(tooltip: LabelTooltipType): WrapperTooltipProps | null { @@ -43,10 +55,11 @@ function toTooltipProps(tooltip: LabelTooltipType): WrapperTooltipProps | null { }; } -function convertColumns(columns?: ColumnType[]): PrimitiveColumnType[] { - if (!columns?.length) return []; +function convertColumns(columns?: ColumnsType): PrimitiveColumnsType | undefined { + if (!columns) return undefined; + return columns.map((col) => { - const { tooltip, title } = col; + const { tooltip, title, ...restColumn } = col; const tooltipProps = toTooltipProps(tooltip); let tooltipNode: React.ReactNode | null = null; @@ -61,24 +74,40 @@ function convertColumns(columns?: ColumnType[]): PrimitiveColumnType[] { ); } - const titleNode = ( - <> - {title} - {tooltipNode} - + const convertedTitle = tooltipNode ? ( + typeof title === 'function' ? ( + (...args: Parameters) => ( + <> + {title(...args)} + {tooltipNode} + + ) + ) : ( + <> + {title} + {tooltipNode} + + ) + ) : ( + title ); + if ('children' in col) { + return { + ...restColumn, + title: convertedTitle, + children: convertColumns(col.children) || [], + } as PrimitiveColumnGroupType; + } + return { - ...col, - title: titleNode, - }; + ...restColumn, + title: convertedTitle, + } as PrimitiveColumnType; }); } -function Table = any>( - props: ITableProps, - ref: React.Ref -) { +function Table(props: ITableProps, ref: React.Ref) { const { columns, className, ...reset } = props; const cols = useMemo(() => convertColumns(columns), [columns]); @@ -93,6 +122,7 @@ function Table = any>( } const ForwardTable = forwardRef(Table) as unknown as RefTable & { + defaultProps?: Partial>; SELECTION_COLUMN: typeof SELECTION_COLUMN; EXPAND_COLUMN: typeof EXPAND_COLUMN; SELECTION_ALL: typeof SELECTION_ALL; @@ -104,6 +134,7 @@ const ForwardTable = forwardRef(Table) as unknown as RefTable & { Summary: typeof InternalTable.Summary; }; +ForwardTable.defaultProps = InternalTable.defaultProps as Partial>; ForwardTable.SELECTION_COLUMN = SELECTION_COLUMN; ForwardTable.EXPAND_COLUMN = EXPAND_COLUMN; ForwardTable.SELECTION_ALL = SELECTION_ALL;