浏览代码

feat: table support keepDOM (#1969)

Co-authored-by: shijia.me <[email protected]>
Co-authored-by: pointhalo <[email protected]>
Shi Jia 1 年之前
父节点
当前提交
1ef1e0efbd

+ 1 - 0
content/show/table/index-en-US.md

@@ -4991,6 +4991,7 @@ render(App);
 | groupBy | Grouping basis, generally a method of a key name or a return value of a string or number in the dataSource element | string\|number<br/>\|(record: any) => string\|number |  | **0.29.0** |
 | hideExpandedColumn | Whether to hide the expansion button column and turn off the rendering of the expansion button when it is turned on | boolean | true |
 | indentSize | indent size of TableCell | number | 20 |
+| keepDOM | Whether to not destroy the collapsed DOM when folding a row | boolean | false |
 | loading | Table is loading or not | boolean | false |
 | pagination | Paging component configuration | boolean\|TablePaginationProps | true |
 | prefixCls | Style name prefix | string |  |

+ 1 - 0
content/show/table/index.md

@@ -5002,6 +5002,7 @@ render(App);
 | groupBy | 分组依据,一般为 dataSource 元素中某个键名或者返回值为字符串、数字的一个方法 | string\|number<br/>\|(record: RecordType) => string\|number |  | **0.29.0** |
 | hideExpandedColumn | 当表格可展开时,展开按钮默认会与第一列文案渲染在同一个单元格内,设为 false 时默认将展开按钮单独作为一列渲染 | boolean | true |
 | indentSize | 树形结构 TableCell 的缩进大小 | number | 20 |
+| keepDOM | 折叠行时是否不销毁被折叠的 DOM | boolean | false |
 | loading | 页面是否加载中 | boolean | false |
 | pagination | 分页组件配置 | boolean\|TablePaginationProps | true |
 | prefixCls | 样式名前缀 | string |  |

+ 11 - 0
cypress/e2e/table.spec.js

@@ -229,4 +229,15 @@ describe('table', () => {
             expect(style.height).eq('0px');
         });
     })
+
+    it('test keepDOM', () => {
+        cy.visit('http://localhost:6006/iframe.html?id=table--keep-dom&viewMode=story');
+        cy.get('[data-cy=expand] .semi-table-row-hidden').should('have.length', 3);
+        cy.get('[data-cy=tree] .semi-table-row-hidden').should('have.length', 5);
+        cy.get('[data-cy=tree] .semi-table-expand-icon').eq(0).click();
+        cy.get('[data-cy=tree] .semi-table-row-hidden').should('have.length', 3);
+        cy.get('[data-cy=section] .semi-table-row-hidden').should('have.length', 10);
+        cy.get('[data-cy=section] .semi-table-expand-icon').eq(0).click({ force: true });
+        cy.get('[data-cy=section] .semi-table-row-hidden').should('have.length', 7);
+    });
 });

+ 3 - 0
packages/semi-foundation/table/table.scss

@@ -296,6 +296,9 @@ $module: #{$prefix}-table;
                         background-color: $color-table_row_expanded-bg-default;
                     }
                 }
+                &-hidden {
+                    display: none;
+                }
             }
 
             & > .#{$module}-cell-fixed {

+ 6 - 1
packages/semi-ui/table/Body/BaseRow.tsx

@@ -60,7 +60,9 @@ export interface BaseRowProps {
     store?: Store;
     style?: React.CSSProperties;
     virtualized?: Virtualized;
-    visible: boolean // required
+    visible: boolean; // required
+    /** whether display none */
+    displayNone?: boolean;
 }
 
 export default class TableRow extends BaseComponent<BaseRowProps, Record<string, any>> {
@@ -74,6 +76,7 @@ export default class TableRow extends BaseComponent<BaseRowProps, Record<string,
         expandIcon: PropTypes.oneOfType([PropTypes.bool, PropTypes.func, PropTypes.node]),
         expandableRow: PropTypes.bool,
         expanded: PropTypes.bool,
+        displayNone: PropTypes.bool,
         expandedRow: PropTypes.bool,
         fixed: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
         height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
@@ -332,6 +335,7 @@ export default class TableRow extends BaseComponent<BaseRowProps, Record<string,
             record,
             hovered,
             expanded,
+            displayNone,
             expandableRow,
             level,
             expandedRow,
@@ -356,6 +360,7 @@ export default class TableRow extends BaseComponent<BaseRowProps, Record<string,
                         [`${prefixCls}-row-selected`]: selected,
                         [`${prefixCls}-row-expanded`]: expanded,
                         [`${prefixCls}-row-hovered`]: hovered,
+                        [`${prefixCls}-row-hidden`]: displayNone,
                     },
                     customClassName
                 );

+ 3 - 0
packages/semi-ui/table/Body/ExpandedRow.tsx

@@ -31,6 +31,7 @@ export interface TableExpandedRowProps {
     store?: Store;
     style?: React.CSSProperties;
     virtualized?: Virtualized
+    displayNone?: boolean;
 }
 
 /**
@@ -85,6 +86,7 @@ export default class TableExpandedRow extends PureComponent<TableExpandedRowProp
             virtualized,
             indentSize,
             cellWidths,
+            displayNone
         } = this.props;
         const { tableWidth, anyColumnFixed, getCellWidths } = this.context;
         const cell: ExpandedRowRenderReturnType = expandedRowRender(record, index, expanded);
@@ -147,6 +149,7 @@ export default class TableExpandedRow extends PureComponent<TableExpandedRowProp
                 virtualized={virtualized}
                 indentSize={indentSize}
                 cellWidths={baseRowCellWidths}
+                displayNone={displayNone}
             />
         );
     }

+ 18 - 9
packages/semi-ui/table/Body/index.tsx

@@ -73,7 +73,8 @@ export interface BodyProps extends BaseProps {
     rowExpandable?: RowExpandable<Record<string, any>>;
     renderExpandIcon: (record: Record<string, any>, isNested: boolean) => ReactNode | null;
     headerRef?: React.MutableRefObject<HTMLDivElement> | ((instance: any) => void);
-    onScroll?: VirtualizedOnScroll
+    onScroll?: VirtualizedOnScroll;
+    keepDOM?: boolean;
 }
 
 export interface BodyState {
@@ -488,6 +489,7 @@ class Body extends BaseComponent<BodyProps, BodyState> {
             index,
             rowKey,
             virtualized,
+            displayNone
         } = props;
         let key = getRecordKey(record, rowKey);
 
@@ -516,6 +518,7 @@ class Body extends BaseComponent<BodyProps, BodyState> {
                 virtualized={virtualized}
                 key={genExpandedRowKey(key)}
                 cellWidths={this.cellWidths}
+                displayNone={displayNone}
             />
         );
     };
@@ -595,7 +598,7 @@ class Body extends BaseComponent<BodyProps, BodyState> {
      * @returns {ReactNode[]} renderedRows
      */
     renderGroupedRows = () => {
-        const { groups, dataSource: data, rowKey, expandedRowKeys } = this.props;
+        const { groups, dataSource: data, rowKey, expandedRowKeys, keepDOM } = this.props;
         const { flattenedColumns } = this.context;
         const groupsInData = new Map();
         const renderedRows: ReactNode[] = [];
@@ -635,7 +638,7 @@ class Body extends BaseComponent<BodyProps, BodyState> {
             );
 
             // Render the grouped content when the group is expanded
-            if (expanded) {
+            if (expanded || keepDOM) {
                 const dataInGroup: any[] = [];
 
                 group.forEach((recordKey: string) => {
@@ -649,20 +652,21 @@ class Body extends BaseComponent<BodyProps, BodyState> {
                 /**
                  * Render the contents of the group row
                  */
-                renderedRows.push(this.renderBodyRows(dataInGroup));
+                renderedRows.push(this.renderBodyRows(dataInGroup, undefined, [], !expanded));
             }
         });
 
         return renderedRows;
     };
 
-    renderBodyRows(data: Record<string, any>[] = [], level = 0, renderedRows: ReactNode[] = []) {
+    renderBodyRows(data: Record<string, any>[] = [], level = 0, renderedRows: ReactNode[] = [], displayNone = false) {
         const {
             rowKey,
             expandedRowRender,
             expandedRowKeys,
             childrenRecordName,
             rowExpandable,
+            keepDOM
         } = this.props;
 
         const hasExpandedRowRender = typeof expandedRowRender === 'function';
@@ -684,6 +688,7 @@ class Body extends BaseComponent<BodyProps, BodyState> {
                     ...this.props,
                     columns: flattenedColumns,
                     expandBtnShouldInRow,
+                    displayNone,
                     record,
                     key,
                     level,
@@ -693,7 +698,8 @@ class Body extends BaseComponent<BodyProps, BodyState> {
 
             // render expand row
             const expanded = isExpanded(expandedRowKeys, key);
-            if (hasExpandedRowRender && rowExpandable && rowExpandable(record) && expanded) {
+            const shouldRenderExpandedRows = expanded || keepDOM;
+            if (hasExpandedRowRender && rowExpandable && rowExpandable(record) && shouldRenderExpandedRows) {
                 const currentExpandRow = this.renderExpandedRow({
                     ...this.props,
                     columns: flattenedColumns,
@@ -701,6 +707,7 @@ class Body extends BaseComponent<BodyProps, BodyState> {
                     index,
                     record,
                     expanded,
+                    displayNone: displayNone || !expanded,
                 });
                 /**
                   * If expandedRowRender returns falsy, this expanded row will not be rendered
@@ -712,8 +719,8 @@ class Body extends BaseComponent<BodyProps, BodyState> {
             }
 
             // render tree data
-            if (recordHasChildren && expanded) {
-                const nestedRows = this.renderBodyRows(recordChildren, level + 1);
+            if (recordHasChildren && shouldRenderExpandedRows) {
+                const nestedRows = this.renderBodyRows(recordChildren, level + 1, [], displayNone || !expanded);
                 renderedRows.push(...nestedRows);
             }
         });
@@ -838,7 +845,9 @@ export interface RenderExpandedRowProps {
     index?: number;
     rowKey?: RowKey<Record<string, any>>;
     virtualized?: Virtualized;
-    level?: number
+    level?: number;
+    keepDOM?: boolean;
+    displayNone?: boolean;
 }
 
 export interface RenderSectionRowProps {

+ 2 - 1
packages/semi-ui/table/_story/table.stories.jsx

@@ -104,7 +104,8 @@ export {
     FixedResizableRowSelection,
     SorterSortOrder,
     FixedPagination,
-    ShowHeader
+    ShowHeader,
+    KeepDOM
 } from './v2';
 export { default as FixSelectAll325 } from './Demos/rowSelection';
 

+ 297 - 0
packages/semi-ui/table/_story/v2/KeepDOM/index.tsx

@@ -0,0 +1,297 @@
+import React from 'react';
+import { Table, Avatar } from '@douyinfe/semi-ui';
+
+const ExpandDemo = () => {
+    const expandColumns = [
+        {
+            title: 'Name',
+            dataIndex: 'name',
+            width: 250,
+            fixed: 'left',
+            key: 'name',
+            render: (text, record, index, { expandIcon: realExpandIcon }) => (
+                <>
+                    {/* {record.description ? realExpandIcon : null} */}
+                    {text}
+                </>
+            ),
+        },
+        { title: 'Age', dataIndex: 'age', key: 'age', width: 200 },
+        { title: 'Address', dataIndex: 'address', key: 'address' },
+        {
+            width: 200,
+            title: 'Action',
+            dataIndex: '',
+            key: 'x',
+            render: () => <a>Delete</a>,
+            fixed: 'right',
+        },
+    ];
+
+    const expandData = [
+        {
+            key: 1,
+            name: 'John Brown',
+            age: 32,
+            address: 'New York No. 1 Lake Park',
+            description: 'My name is John Brown, I am 32 years old, living in New York No. 1 Lake Park.',
+        },
+        {
+            key: 2,
+            name: 'Jim Green',
+            age: 42,
+            address: 'London No. 1 Lake Park',
+            // description: 'My name is Jim Green, I am 42 years old, living in London No. 1 Lake Park.',
+        },
+        {
+            key: 3,
+            name: 'Joe Black',
+            age: 32,
+            address: 'Sidney No. 1 Lake Park',
+            description: 'My name is Joe Black, I am 32 years old, living in Sidney No. 1 Lake Park.',
+        },
+    ];
+
+    return (
+        <Table
+            data-cy="expand"
+            keepDOM
+            columns={expandColumns}
+            // defaultExpandAllRows
+            // rowKey={'kkk'}
+            expandedRowRender={(record, index, expanded) => (
+                <article style={{ margin: 0 }}>
+                    <p>
+                        {index}: {expanded ? 'expanded' : 'unexpanded'}
+                    </p>
+                    <p>{record.description}</p>
+                </article>
+            )}
+            onExpand={(expanded, expandedRow, domEvent) => {
+                domEvent && domEvent.stopPropagation();
+                console.log(expanded, expandedRow, domEvent);
+            }}
+            onRow={(record, index) => ({
+                onClick: () => {
+                    console.log(`Row ${index} clicked: `, record);
+                },
+            })}
+            hideExpandedColumn={false}
+            expandCellFixed={true}
+            dataSource={expandData}
+            scroll={{ x: '160%' }}
+        />
+    );
+};
+
+function TreeKeepDOM() {
+    const columns = [
+        {
+            title: 'Key',
+            dataIndex: 'dataKey',
+            key: 'dataKey',
+        },
+        {
+            title: '名称',
+            dataIndex: 'name',
+            key: 'name',
+            width: 200,
+        },
+        {
+            title: '数据类型',
+            dataIndex: 'type',
+            key: 'type',
+            width: 400,
+        },
+        {
+            title: '描述',
+            dataIndex: 'description',
+            key: 'description',
+        },
+        {
+            title: '默认值',
+            dataIndex: 'default',
+            key: 'default',
+            width: 100,
+        },
+    ];
+
+    const data = [
+        {
+            key: 1,
+            dataKey: 'videos_info',
+            name: '视频信息',
+            type: 'Object 对象',
+            description: '视频的元信息',
+            default: '无',
+            children: [
+                {
+                    key: 11,
+                    dataKey: 'status',
+                    name: '视频状态',
+                    type: 'Enum <Integer> 枚举',
+                    description: '视频的可见、推荐状态',
+                    default: '1',
+                },
+                {
+                    key: 12,
+                    dataKey: 'vid',
+                    name: '视频 ID',
+                    type: 'String 字符串',
+                    description: '标识视频的唯一 ID',
+                    default: '无',
+                    children: [
+                        {
+                            dataKey: 'video_url',
+                            name: '视频地址',
+                            type: 'String 字符串',
+                            description: '视频的唯一链接',
+                            default: '无',
+                        },
+                    ],
+                },
+            ],
+        },
+        {
+            key: 2,
+            dataKey: 'text_info',
+            name: '文本信息',
+            type: 'Object 对象',
+            description: '视频的元信息',
+            default: '无',
+            children: [
+                {
+                    key: 21,
+                    dataKey: 'title',
+                    name: '视频标题',
+                    type: 'String 字符串',
+                    description: '视频的标题',
+                    default: '无',
+                },
+                {
+                    key: 22,
+                    dataKey: 'video_description',
+                    name: '视频描述',
+                    type: 'String 字符串',
+                    description: '视频的描述',
+                    default: '无',
+                },
+            ],
+        },
+    ];
+
+    return <Table data-cy="tree" keepDOM columns={columns} dataSource={data} />;
+}
+
+function SectionDemo() {
+
+    const DAY = 24 * 60 * 60 * 1000;
+    const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
+
+    const columns = [
+        {
+            title: '标题',
+            dataIndex: 'name',
+            width: 400,
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" shape="square" src={figmaIconUrl} style={{ marginRight: 12 }}></Avatar>
+                        {text}
+                    </div>
+                );
+            },
+            filters: [
+                {
+                    text: 'Semi Design 设计稿',
+                    value: 'Semi Design 设计稿',
+                },
+                {
+                    text: 'Semi D2C 设计稿',
+                    value: 'Semi D2C 设计稿',
+                },
+            ],
+            onFilter: (value, record) => record.name.includes(value),
+        },
+        {
+            title: '大小',
+            dataIndex: 'size',
+            sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
+            render: text => `${text} KB`,
+        },
+        {
+            title: '所有者',
+            dataIndex: 'owner',
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" color={record.avatarBg} style={{ marginRight: 4 }}>
+                            {typeof text === 'string' && text.slice(0, 1)}
+                        </Avatar>
+                        {text}
+                    </div>
+                );
+            },
+        },
+        {
+            title: '更新日期',
+            dataIndex: 'updateTime',
+            sorter: (a, b) => (a.updateTime - b.updateTime > 0 ? 1 : -1),
+        },
+    ];
+
+    const getData = () => {
+        const data = [];
+        for (let i = 0; i < 46; i++) {
+            const isSemiDesign = i % 2 === 0;
+            const randomNumber = ((i * 1000) % 19) + 100;
+            data.push({
+                key: '' + i,
+                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi D2C 设计稿${i}.fig`,
+                owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                size: randomNumber,
+                updateTime: new Date('2023-12-07').valueOf() + (i * 1000) % 199,
+                avatarBg: isSemiDesign ? 'grey' : 'red',
+            });
+        }
+        return data;
+    };
+
+    const data = getData();
+
+    const rowKey = record =>
+        `${record.owner && record.owner.toLowerCase()}-${record.name && record.name.toLowerCase()}`;
+
+    return (
+        <div style={{ padding: '20px 0px' }}>
+            <Table
+                data-cy="section"
+                keepDOM
+                dataSource={data}
+                rowKey={rowKey}
+                groupBy={'size'}
+                columns={columns}
+                renderGroupSection={groupKey => <strong>根据文件大小分组 {groupKey} KB</strong>}
+                onGroupedRow={(group, index) => {
+                    return {
+                        onClick: e => {
+                            console.log(`Grouped row clicked: `, group, index);
+                        },
+                    };
+                }}
+                clickGroupedRowToExpand // if you want to click the entire row to expand
+                scroll={{ y: 480 }}
+            />
+        </div>
+    );
+}
+
+export default function Demo() {
+    return (
+        <div>
+            <ExpandDemo />
+            <TreeKeepDOM />
+            <SectionDemo />
+        </div>
+    )
+}

+ 1 - 0
packages/semi-ui/table/_story/v2/index.js

@@ -25,3 +25,4 @@ export { default as FixedResizableRowSelection } from './FixedResizableRowSelect
 export { default as SorterSortOrder } from './SorterSortOrder';
 export { default as FixedPagination } from './FixedPagination';
 export { default as ShowHeader } from './ShowHeader';
+export { default as KeepDOM } from './KeepDOM';

+ 1 - 0
packages/semi-ui/table/interface.ts

@@ -49,6 +49,7 @@ export interface TableProps<RecordType extends Record<string, any> = any> extend
     hideExpandedColumn?: boolean;
     id?: string;
     indentSize?: number;
+    keepDOM?: boolean;
     loading?: boolean;
     pagination?: TablePagination;
     prefixCls?: string;