Selaa lähdekoodia

docs(table): use function component and add some notice (#315)

走鹃 3 vuotta sitten
vanhempi
sitoutus
3dea3ef0d1
27 muutettua tiedostoa jossa 5126 lisäystä ja 943 poistoa
  1. 626 399
      content/show/table/index-en-US.md
  2. 845 544
      content/show/table/index.md
  3. 131 0
      packages/semi-ui/table/_story/Demos/columnRender.jsx
  4. 125 0
      packages/semi-ui/table/_story/Demos/controlledPagination.jsx
  5. 110 0
      packages/semi-ui/table/_story/Demos/customFilter.jsx
  6. 82 0
      packages/semi-ui/table/_story/Demos/default.jsx
  7. 594 0
      packages/semi-ui/table/_story/Demos/dynamic.jsx
  8. 114 0
      packages/semi-ui/table/_story/Demos/expand.jsx
  9. 102 0
      packages/semi-ui/table/_story/Demos/filterAndSorter.jsx
  10. 117 0
      packages/semi-ui/table/_story/Demos/fixed.jsx
  11. 131 0
      packages/semi-ui/table/_story/Demos/fullRender.jsx
  12. 109 0
      packages/semi-ui/table/_story/Demos/group.jsx
  13. 121 0
      packages/semi-ui/table/_story/Demos/headerMerge.jsx
  14. 83 0
      packages/semi-ui/table/_story/Demos/headerMergeJSX.jsx
  15. 118 0
      packages/semi-ui/table/_story/Demos/infiniteScroll.jsx
  16. 114 0
      packages/semi-ui/table/_story/Demos/onRow.jsx
  17. 101 0
      packages/semi-ui/table/_story/Demos/pagination.jsx
  18. 102 0
      packages/semi-ui/table/_story/Demos/resizable copy.jsx
  19. 183 0
      packages/semi-ui/table/_story/Demos/resizable.jsx
  20. 130 0
      packages/semi-ui/table/_story/Demos/rowSelection.jsx
  21. 218 0
      packages/semi-ui/table/_story/Demos/sortTree.jsx
  22. 183 0
      packages/semi-ui/table/_story/Demos/sortable.jsx
  23. 155 0
      packages/semi-ui/table/_story/Demos/span.jsx
  24. 109 0
      packages/semi-ui/table/_story/Demos/tree.jsx
  25. 196 0
      packages/semi-ui/table/_story/Demos/treeSelect.jsx
  26. 103 0
      packages/semi-ui/table/_story/Demos/virtualized.jsx
  27. 124 0
      packages/semi-ui/table/_story/Demos/zebra.jsx

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 626 - 399
content/show/table/index-en-US.md


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 845 - 544
content/show/table/index.md


+ 131 - 0
packages/semi-ui/table/_story/Demos/columnRender.jsx

@@ -0,0 +1,131 @@
+import React from 'react';
+import { Table, Avatar, Button, Empty, Typography } from '@douyinfe/semi-ui';
+import { IconDelete } from '@douyinfe/semi-icons';
+import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
+const { Text } = Typography;
+
+const raw = [
+    {
+        key: '1',
+        name: 'Semi Design 设计稿标题可能有点长这时候应该显示 Tooltip.fig',
+        nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png',
+        size: '2M',
+        owner: '姜鹏志',
+        updateTime: '2020-02-02 05:13',
+        avatarBg: 'grey'
+
+    },
+    {
+        key: '2',
+        name: 'Semi Design 分享演示文稿',
+        nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+        size: '2M',
+        owner: '郝宣',
+        updateTime: '2020-01-17 05:31',
+        avatarBg: 'red'
+    },
+    {
+        key: '3',
+        name: '设计文档',
+        nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+        size: '34KB',
+        owner: 'Zoey Edwards',
+        updateTime: '2020-01-26 11:01',
+        avatarBg: 'light-blue'
+    },
+    {
+        key: '4',
+        name: 'Semi Pro 设计文档可能也有点长所以也会显示Tooltip',
+        nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+        size: '34KB',
+        owner: '姜琪',
+        updateTime: '2020-01-26 11:01',
+        avatarBg: 'green'
+    }
+];
+
+function App() {
+    const [dataSource, setData] = useState(raw);
+
+    const removeRecord = (key) => {
+        let newDataSource = [...dataSource];
+        if (key != null) {
+            let idx = newDataSource.findIndex(data => data.key === key);
+
+            if (idx > -1) {
+                newDataSource.splice(idx, 1);
+                setData(newDataSource);
+            }
+        }
+    };
+    const resetData = () => {
+        const newDataSource = [...raw];
+        setData(newDataSource);
+    };
+
+    const columns = [
+        {
+            title: '标题',
+            dataIndex: 'name',
+            width: 400,
+            render: (text, record, index) => {
+                return (
+                    <span style={{ display: 'flex', alignItems: 'center' }}>
+                        <Avatar size="small" shape="square" src={record.nameIconSrc} style={{ marginRight: 12 }}></Avatar>
+                        {/* 宽度计算方式为单元格设置宽度 - 非文本内容宽度 */}
+                        <Text heading={5} ellipsis={{ showTooltip: true }} style={{ width: 'calc(400px - 76px)' }}>
+                            {text}
+                        </Text>
+                    </span>
+                );
+            }
+        },
+        {
+            title: '大小',
+            dataIndex: 'size',
+            width: 150,
+        },
+        {
+            title: '所有者',
+            dataIndex: 'owner',
+            width: 300,
+            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',
+            width: 200,
+        },
+        {
+            title: '',
+            dataIndex: 'operate',
+            render: (text, record) => <Button icon={<IconDelete />} theme='borderless' onClick={() => removeRecord(record.key)} />
+        },
+    ];
+
+    const empty = (
+        <Empty
+            image={<IllustrationNoResult />}
+            darkModeImage={<IllustrationNoResultDark />}
+            description={'搜索无结果'}
+        />
+    );
+
+
+    return (
+        <>
+            <Button onClick={resetData} style={{ marginBottom: 10 }}>重置</Button>
+            <Table style={{ minHeight: 350 }} columns={columns} dataSource={dataSource} pagination={false} empty={empty} />
+        </>
+    );
+}
+
+render(App);

+ 125 - 0
packages/semi-ui/table/_story/Demos/controlledPagination.jsx

@@ -0,0 +1,125 @@
+import React, { useState, useMemo } from 'react';
+import { Table, Avatar } from '@douyinfe/semi-ui';
+import * as dateFns from 'date-fns';
+
+const DAY = 24 * 60 * 60 * 1000;
+const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
+const pageSize = 5;
+
+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 Pro 设计稿',
+                value: 'Semi Pro 设计稿',
+            },
+        ],
+        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,
+        render: (value) => {
+            return dateFns.format(new Date(value), 'yyyy-MM-dd');
+        }
+    }
+];
+
+const getData = () => {
+    const data = [];
+    for (let i = 0; i < 46; i++) {
+        const isSemiDesign = i % 2 === 0;
+        const randomNumber = (i * 1000) % 199;
+        data.push({
+            key: '' + i,
+            name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+            owner: isSemiDesign ? '姜鹏志' : '郝宣',
+            size: randomNumber,
+            updateTime: new Date().valueOf() + randomNumber * DAY,
+            avatarBg: isSemiDesign ? 'grey' : 'red'
+        });
+    }
+    return data;
+};
+
+const data = getData();
+
+function App() {
+    const [dataSource, setData] = useState([]);
+    const [loading, setLoading] = useState(false);
+    const [currentPage, setPage] = useState(1);
+
+    const fetchData = (currentPage = 1) => {
+        setLoading(true);
+        setPage(currentPage);
+        return new Promise((res, rej) => {
+            setTimeout(() => {
+                const data = getData();
+                let dataSource = data.slice((currentPage - 1) * pageSize, currentPage * pageSize);
+                res(dataSource);
+            }, 300);
+        }).then(dataSource => {
+            setLoading(false);
+            setData(dataSource);
+        });
+    };
+
+    const handlePageChange = page => {
+        fetchData(page);
+    };
+
+    useEffect(() => {
+        fetchData();
+    }, []);
+
+    return (
+        <Table
+            columns={columns}
+            dataSource={dataSource}
+            pagination={{
+                currentPage,
+                pageSize: 5,
+                total: data.length,
+                onPageChange: handlePageChange
+            }}
+            loading={loading}
+        />
+    );
+}
+
+render(App);

+ 110 - 0
packages/semi-ui/table/_story/Demos/customFilter.jsx

@@ -0,0 +1,110 @@
+import React, { useState, useMemo } from 'react';
+import { Table, Avatar, Dropdown } from '@douyinfe/semi-ui';
+import * as dateFns from 'date-fns';
+
+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 Pro 设计稿',
+                value: 'Semi Pro 设计稿',
+            },
+        ],
+        onFilter: (value, record) => record.name.includes(value),
+        renderFilterDropdownItem: ({ text, checked, onChange }) => (
+            <Dropdown.Item onClick={onChange} active={checked}>
+                {text}
+            </Dropdown.Item>
+        ),
+        filterDropdownProps: {
+            showTick: true,
+        },
+        sorter: (a, b) => a.name.length - b.name.length > 0 ? 1 : -1,
+    },
+    {
+        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,
+        render: (value) => {
+            return dateFns.format(new Date(value), 'yyyy-MM-dd');
+        }
+    }
+];
+
+function App() {
+    const [dataSource, setData] = useState([]);
+
+    const rowSelection = useMemo(() => ({
+        onChange: (selectedRowKeys, selectedRows) => {
+            console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
+        },
+        getCheckboxProps: record => ({
+            disabled: record.name === 'Michael James', // Column configuration not to be checked
+            name: record.name,
+        }),
+    }), []);
+    const scroll = useMemo(() => ({ y: 300 }), []);
+
+    const getData = () => {
+        const data = [];
+        for (let i = 0; i < 46; i++) {
+            const isSemiDesign = i % 2 === 0;
+            const randomNumber = (i * 1000) % 199;
+            data.push({
+                key: '' + i,
+                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+                owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                size: randomNumber,
+                updateTime: new Date().valueOf() + randomNumber * DAY,
+                avatarBg: isSemiDesign ? 'grey' : 'red'
+            });
+        }
+        return data;
+    };
+
+    useEffect(() => {
+        const data = getData();
+        setData(data);
+    }, []);
+
+    return <Table columns={columns} dataSource={dataSource} rowSelection={rowSelection} scroll={scroll} />;
+}
+
+render(App);

+ 82 - 0
packages/semi-ui/table/_story/Demos/default.jsx

@@ -0,0 +1,82 @@
+import React from 'react';
+import { Table, Avatar } from '@douyinfe/semi-ui';
+import { IconMore } from '@douyinfe/semi-icons';
+
+function App() {
+    const columns = [
+        {
+            title: '标题',
+            dataIndex: 'name',
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" shape="square" src={record.nameIconSrc} style={{ marginRight: 12 }}></Avatar>
+                        {text}
+                    </div>
+                );
+            }
+        },
+        {
+            title: '大小',
+            dataIndex: 'size',
+        },
+        {
+            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',
+        },
+        {
+            title: '',
+            dataIndex: 'operate',
+            render: () => {
+                return <IconMore />;
+            }
+        },
+    ];
+    const data = [
+        {
+            key: '1',
+            name: 'Semi Design 设计稿.fig',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png',
+            size: '2M',
+            owner: '姜鹏志',
+            updateTime: '2020-02-02 05:13',
+            avatarBg: 'grey'
+
+        },
+        {
+            key: '2',
+            name: 'Semi Design 分享演示文稿',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+            size: '2M',
+            owner: '郝宣',
+            updateTime: '2020-01-17 05:31',
+            avatarBg: 'red'
+        },
+        {
+            key: '3',
+            name: '设计文档',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+            size: '34KB',
+            owner: 'Zoey Edwards',
+            updateTime: '2020-01-26 11:01',
+            avatarBg: 'light-blue'
+        },
+    ];
+
+    return <Table columns={columns} dataSource={data} pagination={false} />;
+}
+
+render(App);

+ 594 - 0
packages/semi-ui/table/_story/Demos/dynamic.jsx

@@ -0,0 +1,594 @@
+import React from 'react';
+import { Table, Switch, ButtonGroup, Button, Avatar } from '@douyinfe/semi-ui';
+import * as dateFns from 'date-fns';
+
+const DAY = 24 * 60 * 60 * 1000;
+const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
+
+class App extends React.Component {
+    constructor(props) {
+        super(props);
+        const dataTotalSize = 46;
+        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 Pro 设计稿',
+                        value: 'Semi Pro 设计稿',
+                    },
+                ],
+                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,
+                render: (value) => {
+                    return dateFns.format(new Date(value), 'yyyy-MM-dd');
+                }
+            }
+        ];
+
+        this.getData = () => {
+            const data = [];
+            for (let i = 0; i < dataTotalSize; i++) {
+                const isSemiDesign = i % 2 === 0;
+                const randomNumber = (i * 1000) % 199;
+                data.push({
+                    key: '' + i,
+                    name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+                    owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                    size: randomNumber,
+                    updateTime: new Date().valueOf() + randomNumber * DAY,
+                    avatarBg: isSemiDesign ? 'grey' : 'red'
+                });
+            }
+            return data;
+        };
+
+        const data = this.getData();
+        this.data = data;
+
+        this.mergeColumns = (column, columns, keys = ['dataIndex']) => {
+            columns = [...columns];
+            columns.forEach((curColumn, index) => {
+                let isTarget = !!(keys && keys.length);
+
+                for (let key of keys) {
+                    if (column[key] !== curColumn[key]) {
+                        isTarget = false;
+                        break;
+                    }
+                }
+
+                if (isTarget) {
+                    columns[index] = { ...curColumn, ...column };
+                }
+            });
+
+            return columns;
+        };
+
+        this.filterData = (filters, dataSource) => {
+            dataSource = [...dataSource];
+            filters.forEach(filter => {
+                let filteredValue = filter.filteredValue;
+                let dataIndex = filter.dataIndex;
+                if (Array.isArray(filteredValue) && filteredValue.length && dataIndex) {
+                    dataSource = dataSource.filter(
+                        data => filteredValue.filter(value => String(data[dataIndex]).indexOf(value) > -1).length
+                    );
+                }
+            });
+
+            return dataSource;
+        };
+
+        this.getSelfSorterColumn = columns => {
+            columns = columns || this.state.columns;
+            return columns.filter(column => !!column.sorter)[0];
+        };
+
+        this.getSelfFilterColumns = columns => {
+            columns = columns || this.state.columns;
+            return columns.filter(column => Array.isArray(column.filteredValue) && column.filteredValue.length);
+        };
+
+        this.sortData = (sortObj, dataSource) => {
+            let { sorter, sortOrder, dataIndex } = sortObj;
+
+            if (sorter && sortOrder && typeof sorter !== 'function') {
+                sorter = (a, b) => (a[dataIndex] > b[dataIndex] ? 1 : -1);
+            }
+
+            if (typeof sorter === 'function') {
+                dataSource = [...dataSource].sort(sorter);
+
+                if (sortOrder === 'descend') {
+                    dataSource = dataSource.reverse();
+                }
+            }
+
+            return dataSource;
+        };
+
+        this.fetchData = (currentPage = 1, sorter = {}, filters = []) => {
+            // console.log(`FetchData currentPage: `, currentPage);
+            let pagination = { ...this.state.pagination, currentPage };
+            return new Promise((res, rej) => {
+                setTimeout(() => {
+                    let data = [...this.data];
+                    data = this.sortData(sorter, data);
+                    data = this.filterData(filters, data);
+                    let dataSource = data.slice(
+                        (currentPage - 1) * pagination.pageSize,
+                        currentPage * pagination.pageSize
+                    );
+                    pagination.total = data.length;
+                    res({
+                        dataSource,
+                        pagination,
+                        sorter,
+                        filters,
+                    });
+                }, 1500);
+            });
+        };
+
+        this.setPage = (currentPage, sorter, filters) => {
+            if (this.state.loading) {
+                return;
+            }
+            if (typeof currentPage !== 'number') {
+                currentPage = (this.state.pagination && this.state.pagination.currentPage) || 1;
+            }
+
+            sorter = sorter || this.getSelfSorterColumn();
+            filters = filters || this.getSelfFilterColumns();
+
+            this.setState({ loading: true });
+            this.fetchData(currentPage, sorter, filters)
+                .then(({ dataSource, pagination, sorter, filters }) => {
+                    let columns = [...this.state.columns];
+                    columns = this.mergeColumns(sorter, columns);
+                    for (let filterObj of filters) {
+                        columns = this.mergeColumns(filterObj, columns);
+                    }
+                    this.setState({
+                        loading: false,
+                        pagination,
+                        dataSource,
+                        columns,
+                    });
+                })
+                .catch(err => {
+                    console.error(err);
+                    this.setState({ loading: false });
+                });
+        };
+
+        this.toggleFixHeader = checked => {
+            let scroll = { ...this.state.scroll };
+
+            if (checked) {
+                scroll.y = 300;
+            } else {
+                scroll.y = null;
+            }
+
+            this.setState({ scroll });
+        };
+
+        this.toggleFixColumns = checked => {
+            let columns = [...this.state.columns];
+            let scroll = { ...this.state.scroll };
+            let expandCellFixed = this.state.expandCellFixed;
+            let rowSelection = this.state.rowSelection;
+
+            if (checked) {
+                columns[0].fixed = true;
+
+                if (rowSelection) {
+                    rowSelection = { ...rowSelection, fixed: true };
+                }
+                if (columns.length > 1) {
+                    columns[columns.length - 1].fixed = 'right';
+                }
+                scroll.x = '150%';
+                expandCellFixed = true;
+            } else {
+                columns.forEach(column => {
+                    column.fixed = false;
+                });
+                scroll.x = null;
+                expandCellFixed = false;
+
+                if (rowSelection) {
+                    rowSelection = { ...rowSelection, fixed: false };
+                }
+            }
+
+            this.setState({
+                rowSelection,
+                expandCellFixed,
+                columns,
+                scroll,
+            });
+        };
+
+        this.toggleRowSelection = checked => {
+            let rowSelection = this.state.rowSelection;
+            // const anyColumnFixed = this.state.columns.some(column => !!column.fixed);
+
+            if (checked) {
+                rowSelection = {
+                    width: 48,
+                    fixed: true,
+                    onChange: (selectedRowKeys, selectedRows) =>
+                        console.log(
+                            'Selection changed, selectedRowKeys: ',
+                            selectedRowKeys,
+                            'selectedRows: ',
+                            selectedRows
+                        ),
+                };
+            } else {
+                rowSelection = null;
+            }
+
+            this.setState({ rowSelection });
+        };
+
+        this.toggleLoading = checked => {
+            let loading = this.state.loading;
+
+            if (checked) {
+                loading = true;
+            } else {
+                loading = false;
+            }
+
+            this.setState({ loading });
+        };
+
+        this.toggleExpandedRowRender = checked => {
+            let expandedRowRender = this.state.expandedRowRender;
+
+            if (checked) {
+                expandedRowRender = record => {
+                    return {
+                        children: <p>{record.description}</p>,
+                        fixed: 'left',
+                    };
+                };
+            } else {
+                expandedRowRender = null;
+            }
+
+            this.setState({ expandedRowRender });
+        };
+
+        this.toggleShowSorter = checked => {
+            let columns = [...this.state.columns];
+
+            if (checked) {
+                columns.forEach(column => column.dataIndex === 'age' && (column.sorter = true));
+            } else {
+                columns.forEach(column => (column.sorter = null));
+            }
+
+            this.setState({ columns });
+        };
+
+        this.toggleShowFilter = checked => {
+            let columns = [...this.state.columns];
+
+            if (checked) {
+                columns.forEach(column => {
+                    if (column.dataIndex === 'name') {
+                        column.filters = [
+                            {
+                                text: '姓名中包含 1',
+                                value: '1',
+                            },
+                            {
+                                text: '姓名中包含 2',
+                                value: '2',
+                            },
+                            {
+                                text: '姓名中包含 3',
+                                value: '3',
+                            },
+                        ];
+                        column.filteredValue = [];
+                    }
+                });
+            } else {
+                columns.forEach(column => {
+                    column.filters = null;
+                    column.filteredValue = null;
+                });
+            }
+
+            this.setState({ columns });
+
+            if(!checked) {
+                this.setPage(null, null, []);
+            }
+        };
+
+        this.onChange = (data = {}) => {
+            console.log('Table changed: ', data);
+            let { pagination, sorter, filters } = data;
+            this.setPage(pagination.currentPage, sorter, filters);
+        };
+
+        this.onExpandedRowsChange = rows => {
+            console.log('Expanded rows changed to: ', rows);
+
+            const expandedRowKeys = (Array.isArray(rows) && rows.map(row => row.key)) || [];
+
+            this.setState({ expandedRowKeys });
+        };
+
+        this.toggleExpandedRowKeys = checked => {
+            let expandedRowKeys = [];
+
+            if (checked) {
+                let dataSource = [...this.state.dataSource];
+                expandedRowKeys.push(
+                    ...dataSource.reduce((arr, data) => {
+                        if (data.key) {
+                            arr.push(data.key);
+                        }
+                        return arr;
+                    }, [])
+                );
+                this.toggleExpandedRowRender(true);
+            }
+            this.setState({ expandedRowKeys });
+        };
+
+        this.toggleBordered = checked => {
+            let bordered = false;
+
+            if (checked) {
+                bordered = true;
+            }
+
+            this.setState({ bordered });
+        };
+
+        this.toggleResizable = checked => {
+            let resizable = !!checked || false;
+
+            this.setState({ resizable, bordered: resizable });
+        };
+
+        this.toggleHideHeader = checked => {
+            let showHeader = true;
+
+            if (checked) {
+                showHeader = false;
+            }
+
+            this.setState({ showHeader });
+        };
+
+        this.toggleFooter = checked => {
+            const footer = checked ? dataSource => <p style={{ margin: 0 }}>This is footer.</p> : null;
+
+            this.setState({ footer });
+        };
+
+        this.toggleTitle = checked => {
+            const title = checked ? 'This is title.' : null;
+
+            this.setState({ title });
+        };
+
+        this.toggleHidePagination = checked => {
+            let pagination = checked
+                ? false
+                : {
+                    currentPage: 1,
+                    pageSize: 8,
+                    total: data.length,
+                    onPageChange: page => this.setPage(page),
+                };
+
+            this.setState({ pagination });
+        };
+
+        this.toggleDataSource = checked => {
+            if (checked) {
+                this.setState({ dataSource: [] });
+            } else {
+                this.setPage();
+            }
+        };
+
+        this.switchPagination = position => {
+            let pagination = this.state.pagination;
+
+            const defaultPagination = {
+                currentPage: 1,
+                pageSize: 8,
+                total: data.length,
+                onPageChange: page => this.setPage(page),
+            };
+
+            const positions = ['bottom', 'top', 'both'];
+
+            if (position === true || position === false) {
+                pagination = position ? { ...defaultPagination, ...pagination } : false;
+            } else if (positions.includes(position)) {
+                pagination = { ...defaultPagination, ...pagination, position };
+            }
+
+            this.setState({ pagination });
+        };
+
+        this.state = {
+            loading: false,
+            columns,
+            scroll: {},
+            rowSelection: null,
+            expandedRowRender: null,
+            expandCellFixed: false,
+            defaultExpandedRowKeys: [],
+            title: null,
+            footer: null,
+            expandedRowKeys: [],
+            showHeader: true,
+            resizable: false,
+            pagination: {
+                currentPage: 1,
+                pageSize: 8,
+                total: data.length,
+                onPageChange: page => this.setPage(page),
+            },
+            dataSource: [],
+        };
+
+        this.TableSwitch = function TableSwitch({
+            text,
+            children,
+            checked,
+            onChange,
+            style = { display: 'inline-flex', alignItems: 'center', margin: 5 },
+        }) {
+            const switchProps = { onChange };
+
+            if (checked != null) {
+                switchProps.checked = !!checked;
+            }
+            return (
+                <span style={style}>
+                    <span>{text}</span>
+                    {children != null ? children : <Switch size="small" {...switchProps} />}
+                </span>
+            );
+        };
+    }
+
+    componentDidMount() {
+        this.setPage(1);
+    }
+
+    render() {
+        let {
+            columns,
+            dataSource,
+            pagination,
+            loading,
+            scroll,
+            rowSelection,
+            expandedRowRender,
+            expandCellFixed,
+            expandedRowKeys,
+            bordered,
+            resizable,
+            title,
+            footer,
+            showHeader,
+            defaultExpandedRowKeys,
+        } = this.state;
+
+        const wrapStyle = { marginBottom: 15, display: 'flex', justifyContent: 'space-around', flexWrap: 'wrap' };
+
+        const TableSwitch = this.TableSwitch;
+
+        return (
+            <div>
+                <div style={wrapStyle}>
+                    <TableSwitch text="固定表头:" checked={scroll && scroll.y} onChange={this.toggleFixHeader} />
+                    <TableSwitch text="隐藏表头:" onChange={this.toggleHideHeader} />
+                    <TableSwitch text="显示标题:" onChange={this.toggleTitle} />
+                    <TableSwitch text="显示底部:" onChange={this.toggleFooter} />
+                    <TableSwitch text="固定列:" onChange={this.toggleFixColumns} />
+                    <TableSwitch text="显示选择列:" onChange={this.toggleRowSelection} />
+                    <TableSwitch text="显示加载状态:" onChange={this.toggleLoading} checked={loading} />
+                    <TableSwitch
+                        text="无数据:"
+                        onChange={this.toggleDataSource}
+                        checked={!dataSource || !dataSource.length}
+                    />
+                    <TableSwitch text="开启排序功能:" onChange={this.toggleShowSorter} />
+                    <TableSwitch text="开启过滤功能:" onChange={this.toggleShowFilter} />
+                    <TableSwitch
+                        text="开启行展开功能:"
+                        onChange={this.toggleExpandedRowRender}
+                        checked={typeof expandedRowRender === 'function'}
+                    />
+                    <TableSwitch text="展开当前所有行:" onChange={this.toggleExpandedRowKeys} />
+                    <TableSwitch text="显示边框:" onChange={this.toggleBordered} checked={bordered} />
+                    <TableSwitch text="开启列伸缩功能:" onChange={this.toggleResizable} />
+                    <TableSwitch text="分页控件:">
+                        <ButtonGroup>
+                            <Button onClick={() => this.switchPagination('bottom')}>Bottom</Button>
+                            <Button onClick={() => this.switchPagination('top')}>Top</Button>
+                            <Button onClick={() => this.switchPagination('both')}>Both</Button>
+                            <Button onClick={() => this.switchPagination(false)}>None</Button>
+                        </ButtonGroup>
+                    </TableSwitch>
+                </div>
+                <Table
+                    defaultExpandedRowKeys={defaultExpandedRowKeys}
+                    onExpandedRowsChange={this.onExpandedRowsChange}
+                    title={title}
+                    footer={footer}
+                    showHeader={showHeader}
+                    bordered={bordered}
+                    onChange={this.onChange}
+                    expandCellFixed={expandCellFixed}
+                    expandedRowRender={expandedRowRender}
+                    expandedRowKeys={expandedRowKeys}
+                    rowSelection={rowSelection}
+                    scroll={scroll}
+                    columns={columns}
+                    dataSource={dataSource}
+                    pagination={pagination}
+                    loading={loading}
+                    resizable={resizable}
+                />
+            </div>
+        );
+    }
+}
+
+render(App);

+ 114 - 0
packages/semi-ui/table/_story/Demos/expand.jsx

@@ -0,0 +1,114 @@
+import React from 'react';
+import { Table, Avatar, Descriptions, Tag } from '@douyinfe/semi-ui';
+import { IconMore } from '@douyinfe/semi-icons';
+
+const columns = [
+    {
+        title: '标题',
+        width: 500,
+        dataIndex: 'name',
+        render: (text, record, index) => {
+            return (
+                <span>
+                    <Avatar size="small" shape="square" src={record.nameIconSrc} style={{ marginRight: 12 }}></Avatar>
+                    {text}
+                </span>
+            );
+        }
+    },
+    {
+        title: '大小',
+        dataIndex: 'size',
+    },
+    {
+        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',
+    },
+    {
+        title: '',
+        dataIndex: 'operate',
+        render: () => {
+            return <IconMore />;
+        }
+    },
+];
+
+const data = [
+    {
+        key: '1',
+        name: 'Semi Design 设计稿.fig',
+        nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png',
+        size: '2M',
+        owner: '姜鹏志',
+        updateTime: '2020-02-02 05:13',
+        avatarBg: 'grey'
+    },
+    {
+        key: '2',
+        name: 'Semi Design 分享演示文稿',
+        nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+        size: '2M',
+        owner: '郝宣',
+        updateTime: '2020-01-17 05:31',
+        avatarBg: 'red'
+    },
+    {
+        key: '3',
+        name: '设计文档',
+        nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+        size: '34KB',
+        owner: 'Zoey Edwards',
+        updateTime: '2020-01-26 11:01',
+        avatarBg: 'light-blue'
+    },
+];
+
+const expandData = [
+    { key: '实际用户数量', value: '1,480,000' },
+    { key: '7天留存', value: '98%' },
+    { key: '安全等级', value: '3级' },
+    { key: '垂类标签', value: <Tag style={{ margin: 0 }}>电商</Tag> },
+    { key: '认证状态', value: '未认证' },
+]
+
+function App() {
+
+    const expandRowRender = (record, index) => {
+        return <Descriptions align="justify" data={expandData} />
+    };
+
+    const rowSelection = {
+        getCheckboxProps: record => ({
+            disabled: record.name === '设计文档', // Column configuration not to be checked
+            name: record.name,
+        }),
+        onSelect: (record, selected) => {
+            console.log(`select row: ${selected}`, record);
+        },
+        onSelectAll: (selected, selectedRows) => {
+            console.log(`select all rows: ${selected}`, selectedRows);
+        },
+        onChange: (selectedRowKeys, selectedRows) => {
+            console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
+        },
+    };
+
+    return <Table rowKey="name" columns={columns} dataSource={data} expandedRowRender={expandRowRender} rowSelection={rowSelection} pagination={false} />;
+}
+
+render(App);

+ 102 - 0
packages/semi-ui/table/_story/Demos/filterAndSorter.jsx

@@ -0,0 +1,102 @@
+import React, { useState, useMemo } from 'react';
+import { Table, Avatar } from '@douyinfe/semi-ui';
+import * as dateFns from 'date-fns';
+
+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 Pro 设计稿',
+                value: 'Semi Pro 设计稿',
+            },
+        ],
+        onFilter: (value, record) => record.name.includes(value),
+        sorter: (a, b) => a.name.length - b.name.length > 0 ? 1 : -1,
+    },
+    {
+        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,
+        render: (value) => {
+            return dateFns.format(new Date(value), 'yyyy-MM-dd');
+        }
+    }
+];
+
+function App() {
+    const [dataSource, setData] = useState([]);
+
+    const rowSelection = useMemo(() => ({
+        onChange: (selectedRowKeys, selectedRows) => {
+            console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
+        },
+        getCheckboxProps: record => ({
+            disabled: record.name === 'Michael James', // Column configuration not to be checked
+            name: record.name,
+        }),
+    }), []);
+    const scroll = useMemo(() => ({ y: 300 }), []);
+
+    const getData = () => {
+        const data = [];
+        for (let i = 0; i < 46; i++) {
+            const isSemiDesign = i % 2 === 0;
+            const randomNumber = (i * 1000) % 199;
+            data.push({
+                key: '' + i,
+                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+                owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                size: randomNumber,
+                updateTime: new Date().valueOf() + randomNumber * DAY,
+                avatarBg: isSemiDesign ? 'grey' : 'red'
+            });
+        }
+        return data;
+    };
+
+    useEffect(() => {
+        const data = getData();
+        setData(data);
+    }, []);
+
+    return <Table columns={columns} dataSource={dataSource} rowSelection={rowSelection} scroll={scroll} />;
+}
+
+render(App);

+ 117 - 0
packages/semi-ui/table/_story/Demos/fixed.jsx

@@ -0,0 +1,117 @@
+import React, { useState, useMemo } from 'react';
+import { Table, Avatar } from '@douyinfe/semi-ui';
+import { IconMore } from '@douyinfe/semi-icons';
+import * as dateFns from 'date-fns';
+
+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',
+        fixed: true,
+        width: 250,
+        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 Pro 设计稿',
+                value: 'Semi Pro 设计稿',
+            },
+        ],
+        onFilter: (value, record) => record.name.includes(value),
+    },
+    {
+        title: '大小',
+        dataIndex: 'size',
+        width: 200,
+        sorter: (a, b) => a.size - b.size > 0 ? 1 : -1,
+        render: (text) => `${text} KB`
+    },
+    {
+        title: '所有者',
+        dataIndex: 'owner',
+        width: 200,
+        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',
+        width: 200,
+        sorter: (a, b) => a.updateTime - b.updateTime > 0 ? 1 : -1,
+        render: (value) => {
+            return dateFns.format(new Date(value), 'yyyy-MM-dd');
+        }
+    },
+    {
+        title: '',
+        dataIndex: 'operate',
+        fixed: 'right',
+        align: 'center',
+        width: 100,
+        render: () => {
+            return <IconMore />;
+        }
+    },
+];
+
+function App() {
+    const [dataSource, setData] = useState([]);
+
+    const scroll = useMemo(() => ({ y: 300, x: 1200 }), []);
+    const rowSelection = useMemo(() => ({
+        onChange: (selectedRowKeys, selectedRows) => {
+            console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
+        },
+        getCheckboxProps: record => ({
+            disabled: record.name === 'Michael James', // Column configuration not to be checked
+            name: record.name,
+        }),
+        fixed: true,
+    }), []);
+
+    const getData = () => {
+        const data = [];
+        for (let i = 0; i < 46; i++) {
+            const isSemiDesign = i % 2 === 0;
+            const randomNumber = (i * 1000) % 199;
+            data.push({
+                key: '' + i,
+                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+                owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                size: randomNumber,
+                updateTime: new Date().valueOf() + randomNumber * DAY,
+                avatarBg: isSemiDesign ? 'grey' : 'red'
+            });
+        }
+        return data;
+    };
+
+    useEffect(() => {
+        const data = getData();
+        setData(data);
+    }, []);
+
+    return <Table columns={columns} dataSource={dataSource} rowSelection={rowSelection} scroll={scroll} />;
+}
+
+render(App);

+ 131 - 0
packages/semi-ui/table/_story/Demos/fullRender.jsx

@@ -0,0 +1,131 @@
+import React, { useState, useEffect, useMemo } from 'react';
+import { Table, Avatar } from '@douyinfe/semi-ui';
+
+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: ({ sorter, filter, selection }) => (
+            <span style={{ display: 'inline-flex', alignItems: 'center', paddingLeft: 20 }}>
+                {selection}
+                <span style={{ marginLeft: 8 }}>Name</span>
+                {sorter}
+                {filter}
+            </span>
+        ),
+        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 Pro 设计稿',
+                value: 'Semi Pro 设计稿',
+            },
+        ],
+        onFilter: (value, record) => record.name.includes(value),
+        useFullRender: true,
+        // 此处从render的第四个形参中解构出 展开按钮、选择按钮、文本等内容
+        render: (text, record, index, { expandIcon, selection, indentText }) => {
+            return (
+                <span style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
+                    {indentText}
+                    {expandIcon}
+                    {selection}
+                    <span style={{ marginLeft: 8 }}>
+                        <Avatar size="small" shape="square" src={figmaIconUrl} style={{ marginRight: 12 }}></Avatar>
+                        {text}
+                    </span>
+                </span>
+            );
+        },
+    },
+    {
+        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,
+        render: (value) => {
+            return dateFns.format(new Date(value), 'yyyy-MM-dd');
+        }
+    }
+];
+
+const getData = (total) => {
+    const data = [];
+    for (let i = 0; i < total; i++) {
+        const isSemiDesign = i % 2 === 0;
+        const randomNumber = (i * 1000) % 199;
+        data.push({
+            key: '' + i,
+            name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+            owner: isSemiDesign ? '姜鹏志' : '郝宣',
+            size: randomNumber,
+            updateTime: new Date().valueOf() + randomNumber * DAY,
+            avatarBg: isSemiDesign ? 'grey' : 'red'
+        });
+    }
+    return data;
+};
+
+function Demo() {
+    const [dataSource, setDataSource] = useState([]);
+    const total = 46;
+    const pagination = useMemo(() => ({
+        pageSize: 12,
+    }), []);
+
+    const rowSelection = useMemo(() => {
+        return {
+            hidden: true,
+            fixed: 'left',
+        };
+    }, []);
+
+    useEffect(() => {
+        const data = getData(total);
+        setDataSource(data);
+    }, [total]);
+
+    return (
+        <Table
+            pagination={pagination}
+            rowSelection={rowSelection}
+            columns={columns}
+            dataSource={dataSource}
+            onChange={(...args) => console.log(...args)}
+            expandedRowRender={record => <article>{record.name}</article>}
+        />
+    );
+}
+
+render(Demo)

+ 109 - 0
packages/semi-ui/table/_story/Demos/group.jsx

@@ -0,0 +1,109 @@
+import React from 'react';
+import { Table, Avatar } from '@douyinfe/semi-ui';
+
+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 Pro 设计稿',
+                value: 'Semi Pro 设计稿',
+            },
+        ],
+        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,
+        render: (value) => {
+            return dateFns.format(new Date(value), 'yyyy-MM-dd');
+        }
+    }
+];
+
+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 Pro 设计稿${i}.fig`,
+            owner: isSemiDesign ? '姜鹏志' : '郝宣',
+            size: randomNumber,
+            updateTime: new Date().valueOf() + randomNumber * DAY,
+            avatarBg: isSemiDesign ? 'grey' : 'red',
+        });
+    }
+    return data;
+};
+
+const data = getData();
+
+function Demo() {
+    const rowKey = record => `${record.owner && record.owner.toLowerCase()}-${record.name && record.name.toLowerCase()}`;
+
+    return (
+        <div style={{ padding: '20px 0px' }}>
+            <Table
+                dataSource={data}
+                rowKey={rowKey}
+                groupBy={'size'}
+                columns={columns}
+                renderGroupSection={groupKey => <strong>根据文件大小分组 {groupKey} KB</strong>}
+                onGroupedRow={(group, index) => {
+                    return {
+                        // onMouseEnter: () => {
+                        //     console.log(`Grouped row mouse enter: `, group, index);
+                        // },
+                        // onMouseLeave: () => {
+                        //     console.log(`Grouped row mouse leave: `, group, index);
+                        // },
+                        onClick: e => { console.log(`Grouped row clicked: `, group, index) }
+                    };
+                }}
+                clickGroupedRowToExpand // if you want to click the entire row to expand
+                scroll={{ y: 480 }}
+            />
+        </div>
+    );
+}
+
+render(Demo);

+ 121 - 0
packages/semi-ui/table/_story/Demos/headerMerge.jsx

@@ -0,0 +1,121 @@
+import React, { useMemo } from 'react';
+import { Table, Avatar } from '@douyinfe/semi-ui';
+import { IconMore } from '@douyinfe/semi-icons';
+
+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: '基本信息',
+        fixed: 'left',
+        children: [
+            {
+                title: '标题',
+                dataIndex: 'name',
+                width: 300,
+                fixed: true,
+                render: (text, record, index) => {
+                    return (
+                        <span>
+                            <Avatar size="small" shape="square" src={figmaIconUrl} style={{ marginRight: 12 }}></Avatar>
+                            {text}
+                        </span>
+                    );
+                },
+                filters: [
+                    {
+                        text: 'Semi Design 设计稿',
+                        value: 'Semi Design 设计稿',
+                    },
+                    {
+                        text: 'Semi Pro 设计稿',
+                        value: 'Semi Pro 设计稿',
+                    },
+                ],
+                onFilter: (value, record) => record.name.includes(value),
+            },
+            {
+                title: '大小',
+                dataIndex: 'size',
+                width: 100,
+                fixed: true,
+                sorter: (a, b) => a.size - b.size > 0 ? 1 : -1,
+                render: (text) => `${text} KB`
+            },
+        ]
+    },
+    {
+        title: '其他信息',
+        children: [
+            {
+                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,
+                render: (value) => {
+                    return dateFns.format(new Date(value), 'yyyy-MM-dd');
+                }
+            }
+        ]
+    },
+    {
+        title: '更多',
+        fixed: 'right',
+        width: 100,
+        align: 'center',
+        dataIndex: 'operate',
+        render: () => {
+            return <IconMore />;
+        }
+    },
+];
+
+const getData = (total) => {
+    const data = [];
+    for (let i = 0; i < total; i++) {
+        const isSemiDesign = i % 2 === 0;
+        const randomNumber = (i * 1000) % 199;
+        data.push({
+            key: '' + i,
+            name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+            owner: isSemiDesign ? '姜鹏志' : '郝宣',
+            size: randomNumber,
+            updateTime: new Date().valueOf() + randomNumber * DAY,
+            avatarBg: isSemiDesign ? 'grey' : 'red'
+        });
+    }
+    return data;
+};
+
+function Demo() {
+    const data = useMemo(() => {
+        const _data = getData(46);
+        return _data;
+    }, []);
+
+    return (
+        <Table
+            rowSelection={{ fixed: true }}
+            expandedRowRender={record => <article>{record.name}</article>}
+            dataSource={data}
+            scroll={{ y: 400 }}
+            onChange={(...args) => console.log(...args)}
+            columns={columns}
+        />
+    );
+}
+
+render(Demo);

+ 83 - 0
packages/semi-ui/table/_story/Demos/headerMergeJSX.jsx

@@ -0,0 +1,83 @@
+import React, { useMemo } from 'react';
+import { Table, Avatar } from '@douyinfe/semi-ui';
+import { IconMore } from '@douyinfe/semi-icons';
+import * as dateFns from 'date-fns';
+
+const DAY = 24 * 60 * 60 * 1000;
+const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
+const Column = Table.Column;
+
+const getData = (total) => {
+    const data = [];
+    for (let i = 0; i < total; i++) {
+        const isSemiDesign = i % 2 === 0;
+        const randomNumber = (i * 1000) % 199;
+        data.push({
+            key: '' + i,
+            name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+            owner: isSemiDesign ? '姜鹏志' : '郝宣',
+            size: randomNumber,
+            updateTime: new Date().valueOf() + randomNumber * DAY,
+            avatarBg: isSemiDesign ? 'grey' : 'red'
+        });
+    }
+    return data;
+};
+
+function Demo() {
+    const data = useMemo(() => {
+        const _data = getData(46);
+        return _data;
+    }, []);
+
+    const nameFilters = [
+        {
+            text: 'Semi Design 设计稿',
+            value: 'Semi Design 设计稿',
+        },
+        {
+            text: 'Semi Pro 设计稿',
+            value: 'Semi Pro 设计稿',
+        },
+    ];
+
+    const renderName = (text, record, index) => {
+        return (
+            <span>
+                <Avatar size="small" shape="square" src={figmaIconUrl} style={{ marginRight: 12 }}></Avatar>
+                {text}
+            </span>
+        );
+    };
+
+    const renderOwner = (text, record, index) => {
+        return (
+            <div>
+                <Avatar size="small" color={record.avatarBg} style={{ marginRight: 4 }}>{typeof text === 'string' && text.slice(0, 1)}</Avatar>
+                {text}
+            </div>
+        );
+    };
+
+    return (
+        <Table
+            rowSelection={{ fixed: true }}
+            expandedRowRender={record => <article>{record.name}</article>}
+            dataSource={data}
+            scroll={{ y: 400 }}
+            onChange={(...args) => console.log(...args)}
+        >
+            <Column title="基本信息" fixed="left">
+                <Column title="标题" dataIndex="name" width={300} fixed render={renderName} filters={nameFilters} onFilter={(value, record) => record.name.includes(value)} />
+                <Column title="大小" dataIndex="size" width={100} fixed render={(text) => `${text} KB`} sortOrder={(a, b) => a.size - b.size > 0 ? 1 : -1} ></Column>
+            </Column>
+            <Column title="其他信息">
+                <Column title="所有者" dataIndex="owner" render={renderOwner} />
+                <Column title="更新日期" dataIndex="updateTime" sortOrder={(a, b) => a.updateTime - b.updateTime > 0 ? 1 : -1} render={(value) => dateFns.format(new Date(value), 'yyyy-MM-dd')}></Column>
+            </Column>
+            <Column title="更多" dataIndex="operate" fixed="right" width={100} align="center" render={() => <IconMore />} />
+        </Table>
+    );
+}
+
+render(Demo);

+ 118 - 0
packages/semi-ui/table/_story/Demos/infiniteScroll.jsx

@@ -0,0 +1,118 @@
+import React, { useRef } from 'react';
+import { Table, Avatar, Button } from '@douyinfe/semi-ui';
+
+const DAY = 24 * 60 * 60 * 1000;
+
+const columns = [
+    {
+        title: '标题',
+        dataIndex: 'name',
+        width: 200,
+        fixed: true,
+        render: (text, record, index) => {
+            return (
+                <div>
+                    {text}
+                </div>
+            );
+        },
+        filters: [
+            {
+                text: 'Semi Design 设计稿',
+                value: 'Semi Design 设计稿',
+            },
+            {
+                text: 'Semi Pro 设计稿',
+                value: 'Semi Pro 设计稿',
+            },
+        ],
+        onFilter: (value, record) => record.name.includes(value),
+    },
+    {
+        title: '大小',
+        dataIndex: 'size',
+        width: 150,
+        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',
+        fixed: 'right',
+        width: 150,
+        sorter: (a, b) => a.updateTime - b.updateTime > 0 ? 1 : -1,
+        render: (value) => {
+            return dateFns.format(new Date(value), 'yyyy-MM-dd');
+        }
+    }
+];
+
+function InfiniteScrollDemo() {
+    const [data, setData] = useState([]);
+
+    const scroll = { y: 600, x: 1000 };
+    const style = { width: 750, margin: '0 auto' };
+
+    const loadMore = () => {
+        const pageSize = 20; // load 20 records every time
+        const newData = [...data];
+        const currentLength = data.length;
+        for (let i = currentLength; i < currentLength + pageSize; i++) {
+            const isSemiDesign = i % 2 === 0;
+            const randomNumber = (i * 1000) % 199;
+            newData.push({
+                key: '' + i,
+                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+                owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                size: randomNumber,
+                updateTime: new Date().valueOf() + randomNumber * DAY,
+                avatarBg: isSemiDesign ? 'grey' : 'red'
+            });
+        }
+        setData(newData);
+    };
+
+    const itemSize = 56;
+    const virtualized = {
+        itemSize,
+        onScroll: ({ scrollDirection, scrollOffset, scrollUpdateWasRequested }) => {
+            if (
+                scrollDirection === 'forward' &&
+                scrollOffset >= (data.length - Math.ceil(scroll.y / itemSize) * 1.5) * itemSize &&
+                !scrollUpdateWasRequested
+            ) {
+                loadMore();
+            }
+        },
+    };
+
+    useEffect(() => {
+        loadMore();
+    }, []);
+
+    return (
+        <Table
+            pagination={false}
+            columns={columns}
+            dataSource={data}
+            scroll={scroll}
+            style={style}
+            virtualized={virtualized}
+        />
+    );
+}
+
+render(InfiniteScrollDemo);

+ 114 - 0
packages/semi-ui/table/_story/Demos/onRow.jsx

@@ -0,0 +1,114 @@
+import React, { useMemo } from 'react';
+import { Table, Avatar } from '@douyinfe/semi-ui/';
+
+const DAY = 24 * 60 * 60 * 1000;
+const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
+
+function EventTable(props = {}) {
+    const columns = useMemo(
+        () => [
+            {
+                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 Pro 设计稿',
+                        value: 'Semi Pro 设计稿',
+                    },
+                ],
+                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,
+                render: (value) => {
+                    return dateFns.format(new Date(value), 'yyyy-MM-dd');
+                }
+            }
+        ],
+        []
+    );
+
+    const data = useMemo(() => {
+        const _data = [];
+        for (let i = 0; i < 46; i++) {
+            const isSemiDesign = i % 2 === 0;
+            const randomNumber = (i * 1000) % 199;
+            _data.push({
+                key: '' + i,
+                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+                owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                size: randomNumber,
+                updateTime: new Date().valueOf() + randomNumber * DAY,
+                avatarBg: isSemiDesign ? 'grey' : 'red'
+            });
+        }
+        return _data;
+    }, []);
+
+    const onRow = useMemo(
+        () => (record, index) => {
+            const props = {
+                className: 'my-tr-class',
+            };
+
+            if (index === 2) {
+                return {
+                    ...props,
+                    onClick: e => console.log('mouse click: ', record, index),
+                };
+            } else {
+                return {
+                    ...props,
+                };
+            }
+        },
+        []
+    );
+    const onHeaderRow = useMemo(
+        () => (columns, index) => {
+            return {
+                onMouseEnter: e => console.log('mouse enter: ', columns, index),
+                onMouseLeave: e => console.log('mouse leave: ', columns, index),
+            };
+        },
+        []
+    );
+
+    return <Table columns={columns} dataSource={data} onRow={onRow} onHeaderRow={onHeaderRow} />;
+}
+
+render(EventTable);

+ 101 - 0
packages/semi-ui/table/_story/Demos/pagination.jsx

@@ -0,0 +1,101 @@
+import React, { useState, useMemo } from 'react';
+import { Table, Avatar } from '@douyinfe/semi-ui';
+import * as dateFns from 'date-fns';
+
+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 Pro 设计稿',
+                value: 'Semi Pro 设计稿',
+            },
+        ],
+        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,
+        render: (value) => {
+            return dateFns.format(new Date(value), 'yyyy-MM-dd');
+        }
+    }
+];
+
+const DAY = 24 * 60 * 60 * 1000;
+
+function App() {
+    const [dataSource, setData] = useState([]);
+
+    const rowSelection = useMemo(() => ({
+        onChange: (selectedRowKeys, selectedRows) => {
+            console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
+        },
+        getCheckboxProps: record => ({
+            disabled: record.name === 'Michael James', // Column configuration not to be checked
+            name: record.name,
+        }),
+    }), []);
+    const scroll = useMemo(() => ({ y: 300 }), []);
+
+    const getData = () => {
+        const data = [];
+        for (let i = 0; i < 46; i++) {
+            const isSemiDesign = i % 2 === 0;
+            const randomNumber = (i * 1000) % 199;
+            data.push({
+                key: '' + i,
+                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+                owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                size: randomNumber,
+                updateTime: new Date().valueOf() + randomNumber * DAY,
+                avatarBg: isSemiDesign ? 'grey' : 'red'
+            });
+        }
+        return data;
+    };
+
+    useEffect(() => {
+        const data = getData();
+        setData(data);
+    }, []);
+
+    return <Table columns={columns} dataSource={dataSource} rowSelection={rowSelection} scroll={scroll} />;
+}
+
+render(App);

+ 102 - 0
packages/semi-ui/table/_story/Demos/resizable copy.jsx

@@ -0,0 +1,102 @@
+import React, { useMemo } from 'react';
+import { Table, Avatar } from '@douyinfe/semi-ui';
+
+const DAY = 24 * 60 * 60 * 1000;
+const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
+const pagination = { pageSize: 5 };
+
+function ResizableDemo() {
+    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 Pro 设计稿',
+                    value: 'Semi Pro 设计稿',
+                },
+            ],
+            onFilter: (value, record) => record.name.includes(value),
+        },
+        {
+            title: '大小',
+            dataIndex: 'size',
+            width: 200,
+            sorter: (a, b) => a.size - b.size > 0 ? 1 : -1,
+            render: (text) => `${text} KB`
+        },
+        {
+            title: '所有者',
+            width: 200,
+            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,
+            render: (value) => {
+                return dateFns.format(new Date(value), 'yyyy-MM-dd');
+            }
+        }
+    ];
+
+    const data = useMemo(() => {
+        const _data = [];
+        for (let i = 0; i < 46; i++) {
+            const isSemiDesign = i % 2 === 0;
+            const randomNumber = (i * 1000) % 199;
+            _data.push({
+                key: '' + i,
+                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+                owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                size: randomNumber,
+                updateTime: new Date().valueOf() + randomNumber * DAY,
+                avatarBg: isSemiDesign ? 'grey' : 'red'
+            });
+        }
+        return _data;
+    }, []);
+
+    const resizable = {
+        onResizeStart: curColumn => {
+            const className = addClass(curColumn.className, 'my-resizing');
+
+            return { className };
+        },
+        onResizeStop: curColumn => {
+            const className = removeClass(curColumn.className, 'my-resizing');
+
+            return { className };
+        }
+    };
+
+    return (
+        <div id="components-table-demo-resizable-column">
+            <Table columns={columns} dataSource={data} resizable={resizable} pagination={pagination} bordered />
+        </div>
+    );
+}
+
+render(ResizableDemo);

+ 183 - 0
packages/semi-ui/table/_story/Demos/resizable.jsx

@@ -0,0 +1,183 @@
+import React, { useState, useMemo } from 'react';
+import { Table, Avatar } from '@douyinfe/semi-ui';
+import { DndProvider, DragSource, DropTarget } from 'react-dnd';
+import HTML5Backend from 'react-dnd-html5-backend';
+
+let draggingIndex = -1;
+const PAGE_SIZE = 5;
+const DAY = 24 * 60 * 60 * 1000;
+const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
+
+function BodyRow(props) {
+    const { isOver, connectDragSource, connectDropTarget, moveRow, currentPage, ...restProps } = props;
+    const style = { ...restProps.style, cursor: 'move' };
+
+    let { className } = restProps;
+    if (isOver) {
+        console.log('true');
+        if (restProps.index > draggingIndex) {
+            className += ' drop-over-downward';
+        }
+        if (restProps.index < draggingIndex) {
+            className += ' drop-over-upward';
+        }
+    }
+
+    return connectDragSource(connectDropTarget(<tr {...restProps} className={className} style={style} />));
+}
+
+const rowSource = {
+    beginDrag(props) {
+        draggingIndex = props.index;
+        return {
+            index: props.index,
+        };
+    },
+};
+
+const rowTarget = {
+    drop(props, monitor) {
+        const dragIndex = monitor.getItem().index;
+        const hoverIndex = props.index;
+
+        if (dragIndex === hoverIndex) {
+            return;
+        }
+
+        props.moveRow(dragIndex, hoverIndex);
+
+        monitor.getItem().index = hoverIndex;
+    },
+};
+
+const DraggableBodyRow = DropTarget('row', rowTarget, (connect, monitor) => ({
+    connectDropTarget: connect.dropTarget(),
+    isOver: monitor.isOver(),
+}))(
+    DragSource('row', rowSource, connect => ({
+        connectDragSource: connect.dragSource(),
+    }))(BodyRow)
+);
+
+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 Pro 设计稿',
+                value: 'Semi Pro 设计稿',
+            },
+        ],
+        onFilter: (value, record) => record.name.includes(value),
+    },
+    {
+        title: '大小',
+        dataIndex: 'size',
+        width: 200,
+        sorter: (a, b) => a.size - b.size > 0 ? 1 : -1,
+        render: (text) => `${text} KB`
+    },
+    {
+        title: '所有者',
+        width: 200,
+        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,
+        render: (value) => {
+            return dateFns.format(new Date(value), 'yyyy-MM-dd');
+        }
+    }
+];
+
+const initData = [];
+for (let i = 0; i < 46; i++) {
+    const isSemiDesign = i % 2 === 0;
+    const randomNumber = (i * 1000) % 199;
+    initData.push({
+        key: '' + i,
+        name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+        owner: isSemiDesign ? '姜鹏志' : '郝宣',
+        size: randomNumber,
+        updateTime: new Date().valueOf() + randomNumber * DAY,
+        avatarBg: isSemiDesign ? 'grey' : 'red'
+    });
+}
+
+function DragSortingTableDemo(props) {
+    const [data, setData] = useState([...initData]);
+    const [currentPage, setCurrentPage] = useState(1);
+    const [pageData, setPageData] = useState(data.slice(0, PAGE_SIZE));
+
+    const components = useMemo(() => ({
+        body: {
+            row: DraggableBodyRow,
+        },
+    }), []);
+
+    const moveRow = (dragIndex, hoverIndex) => {
+        const totalDragIndex = (currentPage - 1) * PAGE_SIZE + dragIndex;
+        const totalHoverIndex = (currentPage - 1) * PAGE_SIZE + hoverIndex;
+        const dragRow = data[totalDragIndex];
+        const newData = [...data];
+        newData.splice(totalDragIndex, 1);
+        newData.splice(totalHoverIndex, 0, dragRow);
+        setData(newData);
+        setPageData(newData.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE));
+    };
+
+    const handlePageChange = (pageNum) => {
+        console.log(pageNum);
+        setCurrentPage(pageNum);
+        setPageData(data.slice((pageNum - 1) * PAGE_SIZE, pageNum * PAGE_SIZE));
+    };
+
+    return (
+        <div id="components-table-demo-drag-sorting">
+            <DndProvider backend={HTML5Backend}>
+                <Table
+                    columns={columns}
+                    dataSource={pageData}
+                    pagination={{
+                        pageSize: PAGE_SIZE,
+                        total: data.length,
+                        currentPage,
+                        onPageChange: handlePageChange
+                    }}
+                    components={components}
+                    onRow={(record, index) => ({
+                        index,
+                        moveRow,
+                    })}
+                />
+            </DndProvider>
+        </div>
+    );
+}
+
+render(DragSortingTableDemo);

+ 130 - 0
packages/semi-ui/table/_story/Demos/rowSelection.jsx

@@ -0,0 +1,130 @@
+import React from 'react';
+import { Table, Avatar } from '@douyinfe/semi-ui';
+import { IconMore } from '@douyinfe/semi-icons';
+
+function App() {
+    const columns = [
+        {
+            title: '标题',
+            dataIndex: 'name',
+            width: 400,
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" shape="square" src={record.nameIconSrc} style={{ marginRight: 12 }}></Avatar>
+                        {text}
+                    </div>
+                );
+            }
+        },
+        {
+            title: '大小',
+            dataIndex: 'size',
+        },
+        {
+            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',
+        },
+        {
+            title: '',
+            dataIndex: 'operate',
+            render: () => {
+                return <IconMore />;
+            }
+        },
+    ];
+    const data = [
+        {
+            key: '1',
+            name: 'Semi Design 设计稿.fig',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png',
+            size: '2M',
+            owner: '姜鹏志',
+            updateTime: '2020-02-02 05:13',
+            avatarBg: 'grey'
+
+        },
+        {
+            key: '2',
+            name: 'Semi Design 分享演示文稿',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+            size: '2M',
+            owner: '郝宣',
+            updateTime: '2020-01-17 05:31',
+            avatarBg: 'red'
+        },
+        {
+            key: '3',
+            name: '设计文档',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+            size: '34KB',
+            owner: 'Zoey Edwards',
+            updateTime: '2020-01-26 11:01',
+            avatarBg: 'light-blue'
+        },
+        {
+            key: '4',
+            name: 'Semi Pro 设计稿.fig',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png',
+            size: '2M',
+            owner: '姜鹏志',
+            updateTime: '2020-02-02 05:13',
+            avatarBg: 'grey'
+
+        },
+        {
+            key: '5',
+            name: 'Semi Pro 分享演示文稿',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+            size: '2M',
+            owner: '郝宣',
+            updateTime: '2020-01-17 05:31',
+            avatarBg: 'red'
+        },
+        {
+            key: '6',
+            name: 'Semi Pro 设计文档',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+            size: '34KB',
+            owner: 'Zoey Edwards',
+            updateTime: '2020-01-26 11:01',
+            avatarBg: 'light-blue'
+        },
+    ];
+    const rowSelection = {
+        getCheckboxProps: record => ({
+            disabled: record.name === '设计文档', // Column configuration not to be checked
+            name: record.name,
+        }),
+        onSelect: (record, selected) => {
+            console.log(`select row: ${selected}`, record);
+        },
+        onSelectAll: (selected, selectedRows) => {
+            console.log(`select all rows: ${selected}`, selectedRows);
+        },
+        onChange: (selectedRowKeys, selectedRows) => {
+            console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
+        },
+    };
+
+    const pagination = useMemo(() => ({
+        pageSize: 3
+    }), []);
+
+    return <Table columns={columns} dataSource={data} rowSelection={rowSelection} pagination={pagination} />;
+}
+
+render(App);

+ 218 - 0
packages/semi-ui/table/_story/Demos/sortTree.jsx

@@ -0,0 +1,218 @@
+import React, { useState } from 'react';
+import { Table, Button } from '@douyinfe/semi-ui';
+import { IconArrowUp, IconArrowDown } from '@douyinfe/semi-icons';
+
+const raw = [
+    {
+        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: '无',
+            }
+        ],
+    },
+];
+
+const rowKey= 'key';
+const childrenRecordName= 'children';
+
+function App() {
+    const [expandedRowKeys, setExpandedRowKeys] = useState([1, 2]);
+    const [data, setData] = useState(raw);
+
+    const switchRecord = (key1, key2) => {
+        const newData = [...data];
+
+        if (key1 != null && key2 != null) {
+            const item1 = findRecordByKey(key1, newData);
+            const item2 = findRecordByKey(key2, newData);
+
+            // you have to copy item1 and item2 first
+            const copiedItem1 = { ...item1 };
+            const copiedItem2 = { ...item2 };
+
+            coverRecord(item1, copiedItem2);
+            coverRecord(item2, copiedItem1);
+
+            setData(newData);
+        }
+    };
+
+    const findRecordByKey = (key, data) => {
+        if (Array.isArray(data) && data.length && key != null) {
+            for (let item of data) {
+                if (item[rowKey] === key) {
+                    return item;
+                }
+
+                const children = item[childrenRecordName];
+                if (Array.isArray(children) && children.length) {
+                    const item = findRecordByKey(key, children);
+
+                    if (item != null) {
+                        return item;
+                    }
+                }
+            }
+        }
+    };
+
+    const coverRecord = (obj, srcObj) => {
+        if (obj && typeof obj === 'object' && srcObj && typeof srcObj === 'object') {
+            const srcKeys = Object.keys(srcObj);
+            const copied = { ...srcObj };
+
+            Object.assign(obj, copied);
+
+            Object.keys(obj).forEach(key => {
+                if (!srcKeys.includes(key)) {
+                    delete obj[key];
+                }
+            });
+        }
+
+        return obj;
+    };
+
+    const getSameLevelRecords = (key, data = []) => {
+        if (key != null && Array.isArray(data) && data.length) {
+            if (data.find(item => item[rowKey] === key)) {
+                return data;
+            }
+            for (let item of data) {
+                const records = getSameLevelRecords(key, item[childrenRecordName]);
+
+                if (records.length) {
+                    return records;
+                }
+            }
+        }
+        return [];
+    };
+
+    const columns = [
+        {
+            title: 'Key',
+            dataIndex: 'dataKey',
+            key: 'dataKey',
+        },
+        {
+            title: '名称',
+            dataIndex: 'name',
+            key: 'name',
+            width: 200,
+        },
+        {
+            title: '数据类型',
+            dataIndex: 'type',
+            key: 'type',
+        },
+        {
+            title: '描述',
+            dataIndex: 'description',
+            key: 'description',
+        },
+        {
+            title: '默认值',
+            dataIndex: 'default',
+            key: 'default',
+            width: 100,
+        },
+        {
+            key: 'operation',
+            render: record => {
+                const records = getSameLevelRecords(record[rowKey], data);
+                const index = records.findIndex(item => item[rowKey] === record[rowKey]);
+                const upProps = {};
+                const downProps = {};
+
+                if (index > 0) {
+                    const upRow = records[index - 1];
+                    upProps.onClick = () => switchRecord(record[rowKey], upRow[rowKey]);
+                } else {
+                    upProps.disabled = true;
+                }
+
+                if (index < records.length - 1) {
+                    const downRow = records[index + 1];
+                    downProps.onClick = () => switchRecord(record[rowKey], downRow[rowKey]);
+                } else {
+                    downProps.disabled = true;
+                }
+
+                return (
+                    <>
+                        <Button icon={<IconArrowUp />} {...upProps} />
+                        <Button icon={<IconArrowDown />} {...downProps} />
+                    </>
+                );
+            },
+        },
+    ];
+
+    return (
+        <Table
+            columns={columns}
+            dataSource={data}
+            rowKey={rowKey}
+            childrenRecordName={childrenRecordName}
+            expandedRowKeys={expandedRowKeys}
+            onExpandedRowsChange={rows => setExpandedRowKeys(rows.map(item => item[rowKey]))}
+        />
+    );
+};
+
+render(App);

+ 183 - 0
packages/semi-ui/table/_story/Demos/sortable.jsx

@@ -0,0 +1,183 @@
+import React, { useState, useMemo } from 'react';
+import { Table, Avatar } from '@douyinfe/semi-ui';
+import { DndProvider, DragSource, DropTarget } from 'react-dnd';
+import HTML5Backend from 'react-dnd-html5-backend';
+
+let draggingIndex = -1;
+const PAGE_SIZE = 5;
+const DAY = 24 * 60 * 60 * 1000;
+const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
+
+function BodyRow(props) {
+    const { isOver, connectDragSource, connectDropTarget, moveRow, currentPage, ...restProps } = props;
+    const style = { ...restProps.style, cursor: 'move' };
+
+    let { className } = restProps;
+    if (isOver) {
+        console.log('true');
+        if (restProps.index > draggingIndex) {
+            className += ' drop-over-downward';
+        }
+        if (restProps.index < draggingIndex) {
+            className += ' drop-over-upward';
+        }
+    }
+
+    return connectDragSource(connectDropTarget(<tr {...restProps} className={className} style={style} />));
+}
+
+const rowSource = {
+    beginDrag(props) {
+        draggingIndex = props.index;
+        return {
+            index: props.index,
+        };
+    },
+};
+
+const rowTarget = {
+    drop(props, monitor) {
+        const dragIndex = monitor.getItem().index;
+        const hoverIndex = props.index;
+
+        if (dragIndex === hoverIndex) {
+            return;
+        }
+
+        props.moveRow(dragIndex, hoverIndex);
+
+        monitor.getItem().index = hoverIndex;
+    },
+};
+
+const DraggableBodyRow = DropTarget('row', rowTarget, (connect, monitor) => ({
+    connectDropTarget: connect.dropTarget(),
+    isOver: monitor.isOver(),
+}))(
+    DragSource('row', rowSource, connect => ({
+        connectDragSource: connect.dragSource(),
+    }))(BodyRow)
+);
+
+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 Pro 设计稿',
+                value: 'Semi Pro 设计稿',
+            },
+        ],
+        onFilter: (value, record) => record.name.includes(value),
+    },
+    {
+        title: '大小',
+        dataIndex: 'size',
+        width: 200,
+        sorter: (a, b) => a.size - b.size > 0 ? 1 : -1,
+        render: (text) => `${text} KB`
+    },
+    {
+        title: '所有者',
+        width: 200,
+        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,
+        render: (value) => {
+            return dateFns.format(new Date(value), 'yyyy-MM-dd');
+        }
+    }
+];
+
+const initData = [];
+for (let i = 0; i < 46; i++) {
+    const isSemiDesign = i % 2 === 0;
+    const randomNumber = (i * 1000) % 199;
+    initData.push({
+        key: '' + i,
+        name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+        owner: isSemiDesign ? '姜鹏志' : '郝宣',
+        size: randomNumber,
+        updateTime: new Date().valueOf() + randomNumber * DAY,
+        avatarBg: isSemiDesign ? 'grey' : 'red'
+    });
+}
+
+function DragSortingTableDemo(props) {
+    const [data, setData] = useState([...initData]);
+    const [currentPage, setCurrentPage] = useState(1);
+    const [pageData, setPageData] = useState(data.slice(0, PAGE_SIZE));
+
+    const components = useMemo(() => ({
+        body: {
+            row: DraggableBodyRow,
+        },
+    }), []);
+
+    const moveRow = (dragIndex, hoverIndex) => {
+        const totalDragIndex = (currentPage - 1) * PAGE_SIZE + dragIndex;
+        const totalHoverIndex = (currentPage - 1) * PAGE_SIZE + hoverIndex;
+        const dragRow = data[totalDragIndex];
+        const newData = [...data];
+        newData.splice(totalDragIndex, 1);
+        newData.splice(totalHoverIndex, 0, dragRow);
+        setData(newData);
+        setPageData(newData.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE));
+    };
+
+    const handlePageChange = (pageNum) => {
+        console.log(pageNum);
+        setCurrentPage(pageNum);
+        setPageData(data.slice((pageNum - 1) * PAGE_SIZE, pageNum * PAGE_SIZE));
+    };
+
+    return (
+        <div id="components-table-demo-drag-sorting">
+            <DndProvider backend={HTML5Backend}>
+                <Table
+                    columns={columns}
+                    dataSource={pageData}
+                    pagination={{
+                        pageSize: PAGE_SIZE,
+                        total: data.length,
+                        currentPage,
+                        onPageChange: handlePageChange
+                    }}
+                    components={components}
+                    onRow={(record, index) => ({
+                        index,
+                        moveRow,
+                    })}
+                />
+            </DndProvider>
+        </div>
+    );
+}
+
+render(DragSortingTableDemo);

+ 155 - 0
packages/semi-ui/table/_story/Demos/span.jsx

@@ -0,0 +1,155 @@
+import React, { useState, useMemo } from 'react';
+import { Table, Avatar } from '@douyinfe/semi-ui';
+import * as dateFns from 'date-fns';
+
+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) => {
+            const renderObject = {};
+            const children = (
+                <div>
+                    <Avatar size="small" shape="square" src={figmaIconUrl} style={{ marginRight: 12 }}></Avatar>
+                    {text}
+                </div>
+            );
+            renderObject.children = children;
+            if (index === 0) {
+                renderObject.props = {
+                    colSpan: 4,
+                }
+            }
+            if (index === 1) {
+                renderObject.props = {
+                    rowSpan: 2,
+                }
+            }
+            if (index === 2) {
+                renderObject.props = {
+                    rowSpan: 0,
+                }
+            }
+            return renderObject;
+        },
+    },
+    {
+        title: '大小',
+        dataIndex: 'size',
+        render: (text, record, index) => {
+            if (index === 0) {
+                return {
+                    children: `${text} KB`,
+                    props: {
+                        colSpan: 0,
+                    }
+                }
+            }
+            if (index === 1) {
+                return {
+                    children: `${text} KB`,
+                    props: {
+                        rowSpan: 2,
+                    }
+                }
+            }
+            if (index === 2) {
+                return {
+                    children: `${text} KB`,
+                    props: {
+                        rowSpan: 0,
+                    }
+                }
+            }
+            return `${text} KB`;
+        }
+    },
+    {
+        title: '所有者',
+        dataIndex: 'owner',
+        render: (text, record, index) => {
+            const children = (
+                <div>
+                    <Avatar size="small" color={record.avatarBg} style={{ marginRight: 4 }}>{typeof text === 'string' && text.slice(0, 1)}</Avatar>
+                    {text}
+                </div>
+            );
+            if (index === 0) {
+                return {
+                    children,
+                    props: {
+                        colSpan: 0,
+                    }
+                }
+            }
+            return children;
+        }
+    },
+    {
+        title: '更新日期',
+        dataIndex: 'updateTime',
+        sorter: (a, b) => a.updateTime - b.updateTime > 0 ? 1 : -1,
+        render: (value, record, index) => {
+            const children = dateFns.format(new Date(value), 'yyyy-MM-dd');
+            if (index === 0) {
+                return {
+                    children,
+                    props: {
+                        colSpan: 0
+                    }
+                }
+            }
+            if (index === 1) {
+                return {
+                    children,
+                    props: {
+                        rowSpan: 2
+                    }
+                }
+            }
+            if (index === 2) {
+                return {
+                    children,
+                    props: {
+                        rowSpan: 0
+                    }
+                }
+            }
+            return children;
+        }
+    }
+];
+
+const DAY = 24 * 60 * 60 * 1000;
+
+function App() {
+    const [dataSource, setData] = useState([]);
+
+    const getData = (total) => {
+        const data = [];
+        for (let i = 0; i < total; i++) {
+            const isSemiDesign = i % 2 === 0;
+            const randomNumber = (i * 1000) % 199;
+            data.push({
+                key: '' + i,
+                name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+                owner: isSemiDesign ? '姜鹏志' : '郝宣',
+                size: randomNumber,
+                updateTime: new Date().valueOf() + randomNumber * DAY,
+                avatarBg: isSemiDesign ? 'grey' : 'red'
+            });
+        }
+        return data;
+    };
+
+    useEffect(() => {
+        const data = getData(5);
+        setData(data);
+    }, []);
+
+    return <Table columns={columns} dataSource={dataSource} pagination={false} />;
+}
+
+render(App);

+ 109 - 0
packages/semi-ui/table/_story/Demos/tree.jsx

@@ -0,0 +1,109 @@
+import React from 'react';
+import { Table } from '@douyinfe/semi-ui';
+
+function App() {
+    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
+            columns={columns}
+            defaultExpandAllRows
+            dataSource={data}
+        />
+    );
+};
+
+render(App);

+ 196 - 0
packages/semi-ui/table/_story/Demos/treeSelect.jsx

@@ -0,0 +1,196 @@
+import React, { useMemo, useState, useCallback } from 'react';
+import { get, union, pullAll } from 'lodash-es';
+import { Table } from '@douyinfe/semi-ui';
+
+const childrenRecordName = 'children';
+const rowKey = 'key';
+const getKey = record => get(record, rowKey, 'key');
+
+const ChildrenDataSelectedDemo = () => {
+    const [selectedRowKeys, setSelectedRowKeys] = useState([]);
+
+    const columns = useMemo(
+        () => [
+            {
+                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 = useMemo(
+        () => [
+            {
+                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: [
+                            {
+                                key: 121,
+                                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: '无',
+                    }
+                ],
+            },
+        ],
+        []
+    );
+
+    // 自定义禁用逻辑
+    const isRecordDisabled = (record) => {
+        return false;
+    };
+
+    const traverse = (data, res) => {
+        for (let record of data) {
+            const children = get(record, 'children');
+            const disabled = isRecordDisabled(record);
+            if (!disabled) {
+                const key = getKey(record);
+                res.push(key);
+            }
+            if (Array.isArray(children)) {
+                traverse(children, res);
+            }
+        }
+    };
+
+    const getAllRowKeys = data => {
+        const allRowKeys = [];
+        traverse(data, allRowKeys);
+        console.log('allRowKeys', allRowKeys);
+        return allRowKeys;
+    };
+
+    const findShouldSelectRowKeys = (record, selected) => {
+        let shouldSelectRowKeys;
+        const children = get(record, 'children');
+        let childrenRowKeys = [];
+        if (Array.isArray(children)) {
+            traverse(children, childrenRowKeys);
+        }
+
+        const key = getKey(record);
+        if (!selected) {
+            shouldSelectRowKeys = [...selectedRowKeys];
+            pullAll(shouldSelectRowKeys, [key, ...childrenRowKeys]);
+        } else {
+            shouldSelectRowKeys = union(selectedRowKeys, [key, ...childrenRowKeys]);
+        }
+        return shouldSelectRowKeys;
+    };
+
+    // 选中一行时需要选中自己可选行
+    const doSelect = useCallback((record, selected) => {
+        const rowKeys = findShouldSelectRowKeys(record, selected);
+        setSelectedRowKeys(rowKeys);
+        console.log('select', record, rowKeys);
+    }, [selectedRowKeys, rowKey, childrenRecordName]);
+
+    // 找出所有可选的行
+    const doSelectAll = useCallback((selected, selectedRows) => {
+        console.log(selected);
+        let rowKeys = [];
+        if (selected) {
+            rowKeys = getAllRowKeys(data);
+        }
+        setSelectedRowKeys(rowKeys);
+    }, []);
+
+    const rowSelection = useMemo(
+        () => ({
+            selectedRowKeys,
+            onSelect: doSelect,
+            onSelectAll: doSelectAll,
+        }),
+        [selectedRowKeys, doSelect, doSelectAll]
+    );
+
+    return (
+        <Table
+            columns={columns}
+            rowKey={rowKey}
+            childrenRecordName={childrenRecordName}
+            rowSelection={rowSelection}
+            dataSource={data}
+            pagination={false}
+        />
+    );
+}
+
+render(ChildrenDataSelectedDemo);

+ 103 - 0
packages/semi-ui/table/_story/Demos/virtualized.jsx

@@ -0,0 +1,103 @@
+import React, { useRef } from 'react';
+import { Table, Avatar, Button } from '@douyinfe/semi-ui';
+
+const DAY = 24 * 60 * 60 * 1000;
+
+const columns = [
+    {
+        title: '标题',
+        dataIndex: 'name',
+        width: 200,
+        fixed: true,
+        render: (text, record, index) => {
+            return (
+                <div>
+                    {text}
+                </div>
+            );
+        },
+        filters: [
+            {
+                text: 'Semi Design 设计稿',
+                value: 'Semi Design 设计稿',
+            },
+            {
+                text: 'Semi Pro 设计稿',
+                value: 'Semi Pro 设计稿',
+            },
+        ],
+        onFilter: (value, record) => record.name.includes(value),
+    },
+    {
+        title: '大小',
+        dataIndex: 'size',
+        width: 150,
+        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',
+        fixed: 'right',
+        width: 150,
+        sorter: (a, b) => a.updateTime - b.updateTime > 0 ? 1 : -1,
+        render: (value) => {
+            return dateFns.format(new Date(value), 'yyyy-MM-dd');
+        }
+    }
+];
+
+const getData = () => {
+    const data = [];
+    for (let i = 0; i < 1000; i++) {
+        const isSemiDesign = i % 2 === 0;
+        const randomNumber = (i * 1000) % 199;
+        data.push({
+            key: '' + i,
+            name: isSemiDesign ? `Semi Design 设计稿${i}.fig` : `Semi Pro 设计稿${i}.fig`,
+            owner: isSemiDesign ? '姜鹏志' : '郝宣',
+            size: randomNumber,
+            updateTime: new Date().valueOf() + randomNumber * DAY,
+            avatarBg: isSemiDesign ? 'grey' : 'red'
+        });
+    }
+    return data;
+};
+
+const data = getData();
+
+function VirtualizedFixedDemo() {
+    let virtualizedListRef = useRef()
+    const scroll = { y: 400, x: 900 };
+    const style = { width: 750, margin: '0 auto' };
+
+    return (
+        <>
+            <Button onClick={() => virtualizedListRef.current.scrollToItem(100)}>Scroll to 100</Button>
+            <Table
+                pagination={false}
+                columns={columns}
+                dataSource={data}
+                scroll={scroll}
+                style={style}
+                virtualized
+                getVirtualizedListRef={ref => virtualizedListRef = ref}
+            />
+        </>
+    );
+}
+
+render(VirtualizedFixedDemo);

+ 124 - 0
packages/semi-ui/table/_story/Demos/zebra.jsx

@@ -0,0 +1,124 @@
+import React from 'react';
+import { Table, Avatar } from '@douyinfe/semi-ui';
+import { IconMore } from '@douyinfe/semi-icons';
+
+function App() {
+    const columns = [
+        {
+            title: '标题',
+            dataIndex: 'name',
+            width: 400,
+            render: (text, record, index) => {
+                return (
+                    <div>
+                        <Avatar size="small" shape="square" src={record.nameIconSrc} style={{ marginRight: 12 }}></Avatar>
+                        {text}
+                    </div>
+                );
+            }
+        },
+        {
+            title: '大小',
+            dataIndex: 'size',
+        },
+        {
+            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',
+        },
+        {
+            title: '',
+            dataIndex: 'operate',
+            render: () => {
+                return <IconMore />;
+            }
+        },
+    ];
+    const data = [
+        {
+            key: '1',
+            name: 'Semi Design 设计稿.fig',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png',
+            size: '2M',
+            owner: '姜鹏志',
+            updateTime: '2020-02-02 05:13',
+            avatarBg: 'grey'
+
+        },
+        {
+            key: '2',
+            name: 'Semi Design 分享演示文稿',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+            size: '2M',
+            owner: '郝宣',
+            updateTime: '2020-01-17 05:31',
+            avatarBg: 'red'
+        },
+        {
+            key: '3',
+            name: '设计文档',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+            size: '34KB',
+            owner: 'Zoey Edwards',
+            updateTime: '2020-01-26 11:01',
+            avatarBg: 'light-blue'
+        },
+        {
+            key: '4',
+            name: 'Semi Pro 设计稿.fig',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png',
+            size: '2M',
+            owner: '姜鹏志',
+            updateTime: '2020-02-02 05:13',
+            avatarBg: 'grey'
+
+        },
+        {
+            key: '5',
+            name: 'Semi Pro 分享演示文稿',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+            size: '2M',
+            owner: '郝宣',
+            updateTime: '2020-01-17 05:31',
+            avatarBg: 'red'
+        },
+        {
+            key: '6',
+            name: 'Semi Pro 设计文档',
+            nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
+            size: '34KB',
+            owner: 'Zoey Edwards',
+            updateTime: '2020-01-26 11:01',
+            avatarBg: 'light-blue'
+        },
+    ];
+
+    const handleRow = (record, index) => {
+        // 给偶数行设置斑马纹
+        if (index % 2 === 0) {
+            return {
+                style: {
+                    background: 'var(--semi-color-fill-0)',
+                }
+            };
+        } else {
+            return {};
+        }
+    };
+
+    return <Table columns={columns} dataSource={data} onRow={handleRow} pagination={false} />;
+}
+
+render(App);

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä