@douyinfe/semi-ui
Version:
A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.
1,486 lines (1,344 loc) • 215 kB
Markdown
---
localeCode: en-US
order: 81
category: Show
title: Table
subTitle: Table
icon: doc-table
dir: column
brief: Tables are used to present structured data content, usually accompanied by the ability to manipulate the data (sort, search, paginate...).
---
## How to Use
Into the header. `columns` And data. `DataSource` To render.
> Please provide a "key" for each data item in the dataSource that is different from the value of the other data items, or use the row Key parameter to specify an attribute name as the primary key, alternative row operation functions such as row selection and expansion of the table.
```jsx noInline=true import
import React from 'react';
import { Table } from '@douyinfe/semi-ui';
function App() {
const columns = [
{
title: 'Title',
dataIndex: 'name',
},
{
title: 'Size',
dataIndex: 'size',
},
{
title: 'Owner',
dataIndex: 'owner',
},
{
title: 'Update',
dataIndex: 'updateTime',
},
];
const data = [
{
key: '1',
name: 'Semi Design design draft.fig',
nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png',
size: '2M',
owner: 'Jiang Pengzhi',
updateTime: '2020-02-02 05:13',
avatarBg: 'grey',
},
{
key: '2',
name: 'Semi Design share docs',
nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
size: '2M',
owner: 'Hao Xuan',
updateTime: '2020-01-17 05:31',
avatarBg: 'red',
},
{
key: '3',
name: 'Design docs',
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} />;
}
```
## Demos
### Basic Table
For tables, the two most basic parameters are `dataSource` and `columns`, the former is the data item, the latter is the configuration of each column, both are array types.
```jsx live=true noInline=true dir="column"
import React from 'react';
import { Table, Avatar } from '@douyinfe/semi-ui';
import { IconMore } from '@douyinfe/semi-icons';
function App() {
const columns = [
{
title: 'Title',
dataIndex: 'name',
render: (text, record, index) => {
return (
<div>
<Avatar
size="small"
shape="square"
src={record.nameIconSrc}
style={{ marginRight: 12 }}
></Avatar>
{text}
</div>
);
},
},
{
title: 'Size',
dataIndex: 'size',
},
{
title: 'Owner',
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: 'Update',
dataIndex: 'updateTime',
},
{
title: '',
dataIndex: 'operate',
render: () => {
return <IconMore />;
},
},
];
const data = [
{
key: '1',
name: 'Semi Design design draft.fig',
nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png',
size: '2M',
owner: 'Jiang Pengzhi',
updateTime: '2020-02-02 05:13',
avatarBg: 'grey',
},
{
key: '2',
name: 'Semi Design share docs',
nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
size: '2M',
owner: 'Hao Xuan',
updateTime: '2020-01-17 05:31',
avatarBg: 'red',
},
{
key: '3',
name: 'Design docs',
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);
```
### JSX Writing
You can also use JSX syntax definitions `columns`, note that Table only supports `columns` JSX syntax definition. You cannot use any component package `Table.Column`.
> Note: JSX-written tables are not supported the parameter `Resizable`.
<Notice type="primary" title="Notice">
<div>When columns are written in JSX, please do not use it at the same time as the configuration method; if used at the same time, only the configuration method will take effect, and no aggregation operation will be performed.</div>
</Notice>
```jsx live=true noInline=true dir="column"
import React from 'react';
import { Table, Avatar } from '@douyinfe/semi-ui';
import { IconMore } from '@douyinfe/semi-icons';
const { Column } = Table;
function App() {
const data = [
{
key: '1',
name: 'Semi Design design draft.fig',
nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png',
size: '2M',
owner: 'Jiang Pengzhi',
updateTime: '2020-02-02 05:13',
avatarBg: 'grey',
},
{
key: '2',
name: 'Semi Design share docs',
nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
size: '2M',
owner: 'Hao Xuan',
updateTime: '2020-01-17 05:31',
avatarBg: 'red',
},
{
key: '3',
name: 'Design docs',
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 renderName = (text, record, index) => {
return (
<div>
<Avatar size="small" shape="square" src={record.nameIconSrc} style={{ marginRight: 12 }}></Avatar>
{text}
</div>
);
};
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 dataSource={data} pagination={false}>
<Column title="Title" dataIndex="name" key="name" render={renderName} />
<Column title="Size" dataIndex="size" key="size" />
<Column title="Owner" dataIndex="owner" key="owner" render={renderOwner} />
<Column title="Update" dataIndex="updateTime" key="updateTime" />
<Column title="" dataIndex="operate" key="operate" render={() => <IconMore />} />
</Table>
);
}
render(App);
```
### Row Selection Operation
This feature can be turned on by passing in `rowSelection`.
- Click the selection box in the header, and all rows in the `dataSource` that are not in the state of `disabled` will be selected. The callback function for selecting all rows is `onSelectAll`;
- Clicking on the row selection box will select the current row. Its callback function is `onSelect`;
<Notice title='注意事项'>
1. Be sure to provide a "key" for each row of data that is different from other row values, or use the rowKey parameter to specify a property name as the primary key.
2. If you encounter the problem of returning to the first page after clicking a row selection on the second page, please check whether component rendering triggers "dataSource" update (shallow equal). After the "dataSource" is updated, the uncontrolled page turner will return to the first page. Please put "dataSource" inside state.
</Notice>
```jsx live=true noInline=true dir="column"
import React from 'react';
import { Table, Avatar } from '@douyinfe/semi-ui';
import { IconMore } from '@douyinfe/semi-icons';
function App() {
const [selectedKeys, setSelectedKeys] = useState([]);
const columns = useMemo(() => [
{
title: '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: 'Size',
dataIndex: 'size',
},
{
title: 'Owner',
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: 'Update',
dataIndex: 'updateTime',
},
{
title: '',
dataIndex: 'operate',
render: () => {
return <IconMore />;
},
},
], []);
const data = useMemo(() => [
{
key: '1',
name: 'Semi Design design draft.fig',
nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png',
size: '2M',
owner: 'Jiang Pengzhi',
updateTime: '2020-02-02 05:13',
avatarBg: 'grey',
},
{
key: '2',
name: 'Semi Design share docs',
nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
size: '2M',
owner: 'Hao Xuan',
updateTime: '2020-01-17 05:31',
avatarBg: 'red',
},
{
key: '3',
name: 'Design docs',
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 D2C design draft.fig',
nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png',
size: '2M',
owner: 'Jiang Pengzhi',
updateTime: '2020-02-02 05:13',
avatarBg: 'grey',
},
{
key: '5',
name: 'Semi D2C share docs',
nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
size: '2M',
owner: 'Hao Xuan',
updateTime: '2020-01-17 05:31',
avatarBg: 'red',
},
{
key: '6',
name: 'Semi D2C Design docs',
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 === 'Design docs', // 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);
setSelectedKeys(selectedRowKeys);
},
};
const pagination = useMemo(
() => ({
pageSize: 3,
}),
[]
);
return <Table columns={columns} dataSource={data} rowSelection={rowSelection} pagination={pagination} />;
}
render(App);
```
### Custom Rendering
Users can use Column.render to customize the rendering of a column of cells, which is suitable for rendering more complex cell content.
The fourth parameter `options` of the `render` function is an object containing the following properties:
- `expandIcon`: Expand icon (when using tree data or expandable rows)
- `selection`: Selection checkbox (when row selection is enabled)
- `indentText`: Indent content (when using tree data)
- `isHovering`: Whether the current row is in hover state (supported in v2.98.0)
With the `isHovering` parameter, you can implement interaction effects such as displaying action buttons on mouse hover.
```jsx live=true noInline=true dir="column"
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 design draft title may be a bit long Tooltip should be displayed at this time.fig',
nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png',
size: '2M',
owner: 'Jiang Pengzhi',
updateTime: '2020-02-02 05:13',
avatarBg: 'grey',
},
{
key: '2',
name: 'Semi Design share docs',
nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
size: '2M',
owner: 'Hao Xuan',
updateTime: '2020-01-17 05:31',
avatarBg: 'red',
},
{
key: '3',
name: 'Design docs',
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 D2C design draft title may be a bit long Tooltip should be displayed at this time.fig',
nameIconSrc: 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png',
size: '34KB',
owner: 'Jiang Qi',
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: '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>
{/* The width calculation method is the cell setting width minus the non-text content width */}
<Text ellipsis={{ showTooltip: true }} style={{ width: 'calc(400px - 76px)' }}>
{text}
</Text>
</span>
);
},
},
{
title: 'Size',
dataIndex: 'size',
width: 150,
},
{
title: 'Owner',
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: 'Update',
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={'No result'}
/>
);
return (
<>
<Button onClick={resetData} style={{ marginBottom: 10 }}>
Reset
</Button>
<Table
style={{ minHeight: 350 }}
columns={columns}
dataSource={dataSource}
pagination={false}
empty={empty}
/>
</>
);
}
render(App);
```
### Table With Pagination
Table paging currently supports two modes: controlled and uncontrolled.
- In controlled mode, the paging state is passed entirely externally, depending on whether the pagination .currentPage field is passed. In general, the controlled mode is suitable for remotely pulling data and rendering.
- In uncontrolled mode, Table passes the incoming dataSource length as total to the Pagination component by default, and of course you can also pass a total field to overwrite the value of the Table component, but we do not recommend users to pass this field in uncontrolled paging mode.
> Note: The custom `pagination.total` field passed in uncontrolled conditions is supported.
```jsx live=true noInline=true dir="column"
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: '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 design draft',
value: 'Semi Design design draft',
},
{
text: 'Semi D2C design draft',
value: 'Semi D2C design draft',
},
],
onFilter: (value, record) => record.name.includes(value),
},
{
title: 'Size',
dataIndex: 'size',
sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
render: text => `${text} KB`,
},
{
title: 'Owner',
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: 'Update',
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 design draft${i}.fig` : `Semi D2C design draft${i}.fig`,
owner: isSemiDesign ? 'Jiang Pengzhi' : 'Hao Xuan',
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);
```
### Pull Remote Data
Under normal circumstances, the data is often not obtained at one time. We will retrieve the data from the interface when clicking on the page number, filter or sort button. In this case, please use **Controlled mode** To handle pagination. The user needs to pass in the `pagination.currentPage` field, where the rendering of the pagination component depends entirely on the incoming pagination object.
<Notice type="primary" title="Notice">
<div>1. When pagination is an object type, literal is not recommended because it causes the table to render to its original state (it looks like the pager is not working). Please try to define reference type parameters outside the render method. If hooks are used, please use useMemo or useState for storage.</div>
<div>2. In the controlled mode, Table will not paginate dataSource, please pass in current page data to dataSource</div>
</Notice>
```jsx live=true noInline=true dir="column"
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: '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 design draft',
value: 'Semi Design design draft',
},
{
text: 'Semi D2C design draft',
value: 'Semi D2C design draft',
},
],
onFilter: (value, record) => record.name.includes(value),
},
{
title: 'Size',
dataIndex: 'size',
sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
render: text => `${text} KB`,
},
{
title: 'Owner',
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: 'Update',
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 design draft${i}.fig` : `Semi D2C design draft${i}.fig`,
owner: isSemiDesign ? 'Jiang Pengzhi' : 'Hao Xuan',
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);
```
### Fixed Column or Head
You can fix the column by setting the Fixed attribute of the column and scroll.x, and fix the header by setting scroll.y.
> - It is recommended to specify scroll.x as a **fixed value** or percentage greater than the width of the table. If it is a fixed value, set it to >= the sum of all fixed column widths + the sum of all table column widths.
> - Make sure that all elements inside the table do not affect the height of the cells after rendering (e.g. containing unloaded pictures, etc.). In this case, give the stator element a definite height to ensure that the left and right Fixed columns of cells are not deranged.
> - If the column header is not aligned with the content or there is a column duplication or when the fixed column fails, specify the width width of the fixed column, if still not effective, try to recommend leaving a column with no width to accommodate the elastic layout, or check for ultra-long continuous fields to destroy the layout.
```jsx live=true noInline=true dir="column"
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: '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 design draft',
value: 'Semi Design design draft',
},
{
text: 'Semi D2C design draft',
value: 'Semi D2C design draft',
},
],
onFilter: (value, record) => record.name.includes(value),
},
{
title: 'Size',
dataIndex: 'size',
width: 200,
sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
render: text => `${text} KB`,
},
{
title: 'Owner',
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: 'Update',
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 design draft${i}.fig` : `Semi D2C design draft${i}.fig`,
owner: isSemiDesign ? 'Jiang Pengzhi' : 'Hao Xuan',
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);
```
The header can be fixed to the top of the page with the `sticky` property. v2.21 version support. When passing `top`, you can control the distance from the scroll container.
After turning on sticky, Table will automatically turn on the fixed layout, and the column width will be determined by `column.width`. Columns without a given width are automatically assigned by the browser.
<StickyHeaderTable />
```jsx live=false noInline=true dir="column"
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 D2C 设计稿',
value: 'Semi D2C 设计稿',
},
],
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 D2C 设计稿${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
sticky={{ top: 60 }}
columns={columns}
dataSource={dataSource}
rowSelection={rowSelection}
scroll={scroll}
/>
);
}
render(App);
```
### Table Header With Sorting and Filtering Function
Filters and sorting controls are integrated inside the table, and users can pass in the sorter display of the sorter open header by passing filters in Column and the filter control display of the onFilter open header.
> Note: Be sure to provide a "key" for each row of data that is different from other row values, or use the rowKey parameter to specify a property name as the primary key.
> Note: Sorting and filtering columns must set independent "dataIndex"
```jsx live=true noInline=true dir="column"
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: '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 design draft',
value: 'Semi Design design draft',
},
{
text: 'Semi D2C design draft',
value: 'Semi D2C design draft',
},
],
onFilter: (value, record) => record.name.includes(value),
sorter: (a, b) => (a.name.length - b.name.length > 0 ? 1 : -1),
},
{
title: 'Size',
dataIndex: 'size',
sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
render: text => `${text} KB`,
},
{
title: 'Owner',
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: 'Update',
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 design draft${i}.fig` : `Semi D2C design draft${i}.fig`,
owner: isSemiDesign ? 'Jiang Pengzhi' : 'Hao Xuan',
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);
```
When sorter is a function type, the sortOrder status can be obtained through the third parameter of the function. The function type is `(a?: RecordType, b?: RecordType, sortOrder?: 'ascend' | 'descend') => number`. Supported by version v2.47.
You can control whether to display the sorting tip through the `showSortTip` attribute. It is supported since v2.65 and defaults to `false`. When the tip is turned on, when there is only sorting function, the sorting prompt will be displayed when the mouse is moved to the table header; in other cases, the sorting prompt will be displayed only when the mouse is moved to the sorting icon.
**Note**: When using the `sortOrder` attribute for controlled sorting, since the next sort order cannot be predicted, `showSortTip` does not take effect and the prompt will not be displayed.
```jsx live=true noInline=true dir="column"
import React from 'react';
import { Table, Avatar } from '@douyinfe/semi-ui';
import * as dateFns from 'date-fns';
function App() {
const columns = [
{
title: 'Title',
dataIndex: 'name',
width: 400,
render: (text, record, index) => {
return (
<div>
<Avatar size="small" shape="square" src={figmaIconUrl} style={{ marginRight: 12 }}></Avatar>
{text}
</div>
);
}
},
{
title: 'Size',
dataIndex: 'size',
sorter: (r1, r2, order) => {
const a = r1.size;
const b = r2.size;
if (typeof a === "number" && typeof b === "number") {
return a - b;
} else if (typeof a === "undefined") {
return order === "ascend" ? 1 : -1;
} else if (typeof b === "undefined") {
return order === "ascend" ? -1 : 1;
} else {
return 0;
}
},
render: text => text ? `${text} KB` : 'Unknown',
},
{
title: 'Owner',
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: 'Update',
dataIndex: 'updateTime',
render: value => {
return dateFns.format(new Date(value), 'yyyy-MM-dd');
},
},
];
const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
const docIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/docs-icon.png';
const dataSource = [
{
key: '1',
name: 'Semi Design draft.fig',
nameIconSrc: figmaIconUrl,
size: 3,
owner: 'Jiang',
updateTime: '2020-02-02 05:13',
avatarBg: 'grey',
},
{
key: '2',
name: 'Semi D2C draft',
nameIconSrc: docIconUrl,
size: undefined,
owner: 'Hao',
updateTime: '2020-01-17 05:31',
avatarBg: 'red',
},
{
key: '3',
name: 'Semi D2C doc 3',
nameIconSrc: docIconUrl,
size: 1,
owner: 'Zoey Edwards',
updateTime: '2020-01-26 11:01',
avatarBg: 'light-blue',
},
{
key: '4',
name: 'Semi Design doc 4',
nameIconSrc: docIconUrl,
size: 5,
owner: 'Zoey Edwards',
updateTime: '2020-01-26 11:01',
avatarBg: 'light-blue',
},
{
key: '5',
name: 'Semi D2C doc 5',
nameIconSrc: docIconUrl,
size: undefined,
owner: 'Zoey Edwards',
updateTime: '2020-01-26 11:01',
avatarBg: 'light-blue',
},
{
key: '6',
name: 'Semi Design doc 6',
nameIconSrc: docIconUrl,
size: 2,
owner: 'Zoey Edwards',
updateTime: '2020-01-26 11:01',
avatarBg: 'light-blue',
},
];
return <Table columns={columns} dataSource={dataSource} />;
}
render(App);
```
### Custom Header Filtering
If you need to display the filter input box in the table header, you can pass ReactNode in the `title` and use it with `filteredValue`.
```jsx live=true noInline=true dir="column"
import React, { useState, useEffect, useRef } from 'react';
import { Table, Avatar, Input, Space } from '@douyinfe/semi-ui';
import * as dateFns from 'date-fns';
function App() {
const [dataSource, setData] = useState([]);
const [filteredValue, setFilteredValue] = useState([]);
const compositionRef = useRef({ isComposition: false });
const DAY = 24 * 60 * 60 * 1000;
const figmaIconUrl = 'https://lf3-static.bytednsdoc.com/obj/eden-cn/ptlz_zlp/ljhwZthlaukjlkulzlp/figma-icon.png';
const handleChange = (value) => {
if (compositionRef.current.isComposition) {
return;
}
const newFilteredValue = value ? [value] : [];
setFilteredValue(newFilteredValue);
};
const handleCompositionStart = () => {
compositionRef.current.isComposition = true;
};
const handleCompositionEnd = (event) => {
compositionRef.current.isComposition = false;
const value = event.target.value;
const newFilteredValue = value ? [value] : [];
setFilteredValue(newFilteredValue);
};
const columns = [
{
title: (
<Space>
<span>Title</span>
<Input
placeholder="Input filter value"
style={{ width: 200 }}
onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd}
onChange={handleChange}
showClear
/>
</Space>
),
dataIndex: 'name',
width: 400,
render: (text, record, index) => {
return (
<div>
<Avatar size="small" shape="square" src={figmaIconUrl} style={{ marginRight: 12 }}></Avatar>
{text}
</div>
);
},
onFilter: (value, record) => record.name.includes(value),
filteredValue,
},
{
title: 'Size',
dataIndex: 'size',
sorter: (a, b) => (a.size - b.size > 0 ? 1 : -1),
render: text => `${text} KB`,
},
{
title: 'Owner',
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: 'Update',
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 design draft${i}.fig` : `Semi D2C design draft${i}.fig`,
owner: isSemiDesign ? 'Jiang Pengzhi' : 'Hao Xuan',
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} />;
}
render(App);
```
### Custom Filter Rendering
Use `renderFilterDropdown` to customize the render filter panel. v2.52 supported.
You can call `setTempFilteredValue` to store the filter value when the user enters the filter value, and call `confirm` to trigger the actual filtering after the filter value is entered. You can also filter directly through `confirm({ filteredValue })`.
The reason for setting `tempFilteredValue` is that in scenarios where temporary filtered values need to be stored, there is no need to declare a state to save this temporary filtered value.
```typescript
type RenderFilterDropdown = (props: RenderFilterDropdownProps) => React.ReactNode;
interface RenderFilterDropdownProps {
/** Temporary filter value, the initial value is `filteredValue` or `defaultFilteredValue` */
tempFilteredValue: any[];
/** Set temporary filter value */
setTempFilteredValue: (tempFilteredValue: any[]) => void;
/** `confirm` will assign `tempFilteredValue` to `filteredValue` by default and trigger the `onChange` event. You can also set the filter value directly by passing in `filteredValue` */
confirm: (props?: { closeDropdown?: boolean; filteredValue?: any[] }) => void;
/** Clear