localeCode: en-US order: 53 category: Show title: List subTitle: List icon: doc-list dir: column
import { List } from '@douyinfe/semi-ui';
You can use size to size list. Supported values include large, default, Small. Header and Footer customized.
import React from 'react';
import { List } from '@douyinfe/semi-ui';
class SimpleList extends React.Component {
render() {
const data = [
'Do not go gentle into that good night,',
'Old age should burn and rave at close of day;',
'Rage, rage against the dying of the light.',
];
return (
<div>
<div style={{ marginRight: 16 }}>
<h3 style={{ marginBottom: 16 }}>Default Size</h3>
<List
header={<div>Header</div>}
footer={<div>Footer</div>}
bordered
dataSource={data}
renderItem={item => <List.Item>{item}</List.Item>}
/>
</div>
<br />
<div style={{ marginRight: 16 }}>
<h3 style={{ margin: '16px 0' }}>Small Size</h3>
<List
size="small"
header={<div>Header</div>}
footer={<div>Footer</div>}
bordered
dataSource={data}
renderItem={item => <List.Item>{item}</List.Item>}
/>
</div>
<br />
<div style={{ marginRight: 16 }}>
<h3 style={{ margin: '16px 0' }}>Large Size</h3>
<List
size="large"
header={<div>Header</div>}
footer={<div>Footer</div>}
bordered
dataSource={data}
renderItem={item => <List.Item>{item}</List.Item>}
/>
</div>
</div>
);
}
}
render(SimpleList);
List.Item has a built-in template consisting of: header, main, and extra. The alignment of header and main set by align properties using one of flex-start(default), flex-end, center, baseline, and stretch .
import React from 'react';
import { List, ButtonGroup, Button, Avatar } from '@douyinfe/semi-ui';
class ContentList extends React.Component {
render() {
const data = [
// eslint-disable-next-line react/jsx-key
<p
style={{
color: 'var(--semi-color-text-2)',
margin: '4px 0',
width: 420,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{`Life's but a walking shadow, a poor player, that struts and frets his hour upon the stage, and then is
heard no more; it is a tale told by an idiot, full of sound and fury, signifying nothing.`}
</p>,
// eslint-disable-next-line react/jsx-key
<p style={{ color: 'var(--semi-color-text-2)', margin: '4px 0', width: 500 }}>
Come what come may, time and the hour run through the roughest day.
</p>,
// eslint-disable-next-line react/jsx-key
<p style={{ color: 'var(--semi-color-text-2)', margin: '4px 0', width: 500 }}>
{`Where shall we three meet again in thunder, lightning, or in rain? When the hurlyburly's done, when the
battle's lost and won`}
</p>,
];
return (
<div style={{ padding: 12, border: '1px solid var(--semi-color-border)', margin: 12 }}>
<List
dataSource={data}
renderItem={item => (
<List.Item
header={<Avatar color="blue">SE</Avatar>}
main={
<div>
<span style={{ color: 'var(--semi-color-text-0)', fontWeight: 500 }}>Example</span>
{item}
</div>
}
extra={
<ButtonGroup theme="borderless">
<Button>Edit</Button>
<Button>More</Button>
</ButtonGroup>
}
/>
)}
/>
</div>
);
}
}
render(ContentList);
Use layout property to set list layout, one of vertical(default) or horizontal.
import React from 'react';
import { List, Avatar } from '@douyinfe/semi-ui';
class LayoutList extends React.Component {
render() {
const data = [
{
title: 'Title 1',
color: 'light-blue',
},
{
title: 'Title 2',
color: 'grey',
},
{
title: 'Title 3',
color: 'light-green',
},
];
return (
<div style={{ padding: 12, border: '1px solid var(--semi-color-border)', margin: 12 }}>
<List
dataSource={data}
layout="horizontal"
renderItem={item => (
<List.Item
header={<Avatar color={item.color}>SE</Avatar>}
main={
<div>
<span style={{ color: 'var(--semi-color-text-0)', fontWeight: 500 }}>{item.title}</span>
<p style={{ color: 'var(--semi-color-text-2)', margin: '4px 0' }}>
{` Life's but a walking shadow, a poor player, that struts and frets his hour upon
the stage, and then is heard no more; it is a tale told by an idiot, full of
sound and fury, signifying nothing.`}
</p>
</div>
}
/>
)}
/>
</div>
);
}
}
render(LayoutList);
Use grid property to set grid layout. Use span to set the number of occupying spaces for each item and gutter for spacing between items.
import React from 'react';
import { List, Descriptions, Rating, ButtonGroup, Button } from '@douyinfe/semi-ui';
class LayoutList extends React.Component {
render() {
const data = [
{
title: 'Platform A',
rating: 4.5,
feedbacks: 124,
},
{
title: 'Platform B',
rating: 4,
feedbacks: 108,
},
{
title: 'Platform C',
rating: 4.5,
feedbacks: 244,
},
{
title: 'Platform D',
feedbacks: 189,
},
];
const style = {
border: '1px solid var(--semi-color-border)',
backgroundColor: 'var(--semi-color-bg-2)',
borderRadius: '3px',
paddingLeft: '20px',
};
return (
<div>
<List
grid={{
gutter: 12,
span: 6,
}}
dataSource={data}
renderItem={item => (
<List.Item style={style}>
<div>
<h3 style={{ color: 'var(--semi-color-text-0)', fontWeight: 500 }}>{item.title}</h3>
<Descriptions
align="center"
size="small"
row
data={[
{ key: 'Rating', value: <Rating allowHalf size="small" value={item.rating} /> },
{ key: 'Feedbacks', value: item.feedbacks },
]}
/>
<div style={{ margin: '12px 0', display: 'flex', justifyContent: 'flex-end' }}>
<ButtonGroup theme="borderless" style={{ marginTop: 8 }}>
<Button>Edit</Button>
<Button>More</Button>
</ButtonGroup>
</div>
</div>
</List.Item>
)}
/>
</div>
);
}
}
render(LayoutList);
Refer to Grid for responsive dimensions.
import React from 'react';
import { List, Descriptions, Rating, ButtonGroup, Button } from '@douyinfe/semi-ui';
class Responsive extends React.Component {
render() {
const data = [
{
title: 'Platform A',
rating: 4.5,
feedbacks: 124,
},
{
title: 'Platform B',
rating: 4,
feedbacks: 108,
},
{
title: 'Platform C',
rating: 3.5,
feedbacks: 244,
},
{
title: 'Platform D',
feedbacks: 189,
},
{
title: 'Platform E',
rating: 3,
feedbacks: 128,
},
{
title: 'Platform D',
rating: 4,
feedbacks: 156,
},
];
const style = {
border: '1px solid var(--semi-color-border)',
backgroundColor: 'var(--semi-color-bg-2)',
borderRadius: '3px',
paddingLeft: '20px',
margin: '8px 2px',
};
return (
<div>
<List
grid={{
gutter: 12,
Xs: 0,
sm: 0,
md: 12,
lg: 8,
Xl: 8,
xxl: 6,
}}
dataSource={data}
renderItem={item => (
<List.Item style={style}>
<div>
<h3 style={{ color: 'var(--semi-color-text-0)', fontWeight: 500 }}>{item.title}</h3>
<Descriptions
align="center"
size="small"
row
data={[
{ key: 'Rating', value: <Rating allowHalf size="small" value={item.rating} /> },
{ key: 'Feedbacks', value: item.feedbacks },
]}
/>
<div style={{ margin: '12px 0', display: 'flex', justifyContent: 'flex-end' }}>
<ButtonGroup theme="borderless" style={{ marginTop: 8 }}>
<Button>Edit</Button>
<Button>More</Button>
</ButtonGroup>
</div>
</div>
</List.Item>
)}
/>
</div>
);
}
}
render(Responsive);
You can use loadMore to achieve loading state for more incoming contents.
import React from 'react';
import { List, Skeleton, Button, Avatar } from '@douyinfe/semi-ui';
class LoadMoreList extends React.Component {
constructor() {
super();
const count = 3;
const data = [];
for (let i = 0; i < 40; i++) {
data.push({
color: 'grey',
title: `Semi Design Title ${i}`,
loading: false,
});
}
this.data = data;
this.count = 0;
this.fetchData = () => {
let placeholders = [0, 1, 2].map(key => ({ loading: true }));
this.setState({
loading: true,
list: [...this.state.dataSource, ...placeholders],
});
return new Promise((res, rej) => {
setTimeout(() => {
let dataSource = this.data.slice(this.count * count, this.count * count + count);
res(dataSource);
}, 1000);
}).then(dataSource => {
let newData = [...this.state.dataSource, ...dataSource];
this.setState({
loading: false,
dataSource: newData,
list: newData,
noMore: !dataSource.length,
});
});
};
this.state = {
loading: false,
dataSource: [],
list: [],
noMore: false,
};
}
componentDidMount() {
this.fetchData();
}
onLoadMore() {
this.count++;
this.fetchData();
}
render() {
const { loading, list, noMore } = this.state;
const loadMore =
!loading && !noMore ? (
<div
style={{
textAlign: 'center',
marginTop: 12,
height: 32,
lineHeight: '32px',
}}
>
<Button onClick={() => this.onLoadMore()}>Load More</Button>
</div>
) : null;
const placeholder = (
<div
style={{
display: 'flex',
alignItems: 'flex-start',
padding: 12,
borderBottom: '1px solid var(--semi-color-border)',
}}
>
<Skeleton.Avatar style={{ marginRight: 12 }} />
<div>
<Skeleton.Title style={{ width: 120, marginBottom: 12, marginTop: 12 }} />
<Skeleton.Paragraph style={{ width: 600 }} rows={2} />
</div>
</div>
);
return (
<List
loading={loading}
loadMore={loadMore}
dataSource={list}
renderItem={item => (
<Skeleton placeholder={placeholder} loading={item.loading}>
<List.Item
header={<Avatar color={item.color}>SE</Avatar>}
main={
<div>
<span style={{ color: 'var(--semi-color-text-0)', fontWeight: 500 }}>{item.title}</span>
<p style={{ color: 'var(--semi-color-text-2)', margin: '4px 0' }}>
Create a consistent, good-looking, easy-to-use, and efficient user experience
with a user-centric, content-first, and human-friendly design system
</p>
</div>
}
/>
</Skeleton>
)}
/>
);
}
}
render(LoadMoreList);
You can integrate react-infinite-scroller to implement scrolling load list. Recommended interaction could be reveal a loadmore button after three scrolling loads.
import React from 'react';
import { List, Avatar, Spin, Button } from '@douyinfe/semi-ui';
import InfiniteScroll from 'react-infinite-scroller';
class ScrollLoad extends React.Component {
constructor() {
super();
const count = 5;
const dataList = [];
for (let i = 0; i < 100; i++) {
dataList.push({
color: 'grey',
title: `Semi Design Title ${i}`,
loading: false,
});
}
this.data = dataList;
this.count = 0;
this.fetchData = () => {
this.setState({
loading: true,
});
return new Promise((res, rej) => {
setTimeout(() => {
let dataSource = this.data.slice(this.count * count, this.count * count + count);
res(dataSource);
}, 1000);
}).then(dataSource => {
let newData = [...this.state.dataSource, ...dataSource];
this.count++;
this.setState({
loading: false,
dataSource: newData,
noMore: !dataSource.length,
});
});
};
this.state = {
loading: false,
dataSource: [],
hasMore: true,
};
}
componentDidMount() {
this.fetchData();
}
render() {
const { loading, dataSource, hasMore } = this.state;
const showLoadMore = this.count % 4 === 0;
const loadMore =
!loading && hasMore && showLoadMore ? (
<div
style={{
textAlign: 'center',
marginTop: 12,
height: 32,
lineHeight: '32px',
}}
>
<Button onClick={this.fetchData}>show more</Button>
</div>
) : null;
return (
<div
className
Name="light-scrollbar"
style={{ height: 420, overflow: 'auto', border: '1px solid var(--semi-color-border)', padding: 10 }}
>
<InfiniteScroll
initialLoad={false}
pageStart={0}
threshold={20}
loadMore={this.fetchData}
hasMore={!this.state.loading && this.state.hasMore && !showLoadMore}
useWindow={false}
>
<List
loadMore={loadMore}
dataSource={dataSource}
renderItem={item => (
<List.Item
header={<Avatar color={item.color}>SE</Avatar>}
main={
<div>
<span style={{ color: 'var(--semi-color-text-0)', fontWeight: 500 }}>
{item.title}
</span>
<p style={{ color: 'var(--semi-color-text-2)', margin: '4px 0' }}>
Create a consistent, good-looking, easy-to-use, and efficient user
experience with a user-centric, content-first, and human-friendly design
system
</p>
</div>
}
/>
)}
/>
{this.state.loading && this.state.hasMore && (
<div style={{ textAlign: 'center' }}>
<Spin />
</div>
)}
</InfiniteScroll>
</div>
);
}
}
render(ScrollLoad);
You can integrate react-virtualized to implement infinite scrolling lists with virtualization to improve the performance for large amounts of data.
import React from 'react';
import { List, Avatar } from '@douyinfe/semi-ui';
import { InfiniteLoader, AutoSizer } from 'react-virtualized';
import VList from 'react-virtualized/dist/commonjs/List';
class VirtualizedScroll extends React.Component {
constructor() {
super();
const dataList = [];
for (let i = 0; i < 50; i++) {
dataList.push({
color: 'grey',
title: `Semi Design Title ${i}`,
});
}
this.data = dataList;
this.fetchData = (startIndex, stopIndex) => {
return new Promise((res, rej) => {
setTimeout(() => {
let dataSource = this.data.slice(startIndex, stopIndex + 1);
res(dataSource);
}, 1000);
}).then(dataSource => {
let newData = [...this.state.dataSource, ...dataSource];
const { loadedRowsMap, loadingRowCount } = this.state;
const increment = stopIndex - startIndex + 1;
for (let i = startIndex; i <= stopIndex; i++) {
loadedRowsMap[i] = this.statusLoaded;
}
this.setState({
dataSource: newData,
loadedRowsMap,
loadingRowCount: loadingRowCount - increment,
});
});
};
this.state = {
dataSource: [],
loadedRowsMap: {},
loadingRowCount: 0,
};
this.statusLoading = 0;
this.statusLoaded = 1;
this.loadLimit = this.data.length;
this.renderItem = this.renderItem.bind(this);
this.fetchData = this.fetchData.bind(this);
this.handleInfiniteOnLoad = this.handleInfiniteOnLoad.bind(this);
this.isRowLoaded = this.isRowLoaded.bind(this);
}
handleInfiniteOnLoad({ startIndex, stopIndex }) {
let { dataSource, loadedRowsMap, loadingRowCount } = this.state;
const increment = stopIndex - startIndex + 1;
if (stopIndex >= this.loadLimit || loadingRowCount > 0) {
return;
}
for (let i = startIndex; i <= stopIndex; i++) {
loadedRowsMap[i] = this.statusLoading;
}
this.setState({
loadingRowCount: loadingRowCount + increment,
});
return this.fetchData(startIndex, stopIndex);
}
isRowLoaded({ index }) {
const { loadedRowsMap } = this.state;
return !!loadedRowsMap[index];
}
renderItem({ index, key, style }) {
const { dataSource, loadedRowsMap } = this.state;
const item = dataSource[index];
if (!item) {
return;
}
const content = (
<List.Item
key={key}
style={style}
header={<Avatar color={item.color}>SE</Avatar>}
main={
<div>
<span style={{ color: 'var(--semi-color-text-0)', fontWeight: 500 }}>{item.title}</span>
<p style={{ color: 'var(--semi-color-text-2)', margin: '4px 0' }}>
Create a consistent, good-looking, easy-to-use, and efficient user experience with a
user-centric, content-first, and human-friendly design system
</p>
</div>
}
/>
);
return content;
}
render() {
const { dataSource } = this.state;
const height = 500;
return (
<List style={{ border: '1px solid var(--semi-color-border)', padding: 10 }}>
<InfiniteLoader
isRowLoaded={this.isRowLoaded}
loadMoreRows={this.handleInfiniteOnLoad}
rowCount={this.loadLimit}
>
{({ onRowsRendered, registerChild }) => (
<AutoSizer disableHeight>
{({ width }) => (
<VList
ref={registerChild}
height={height}
onRowsRendered={onRowsRendered}
rowCount={this.loadLimit}
rowHeight={118}
rowRenderer={this.renderItem}
width={width}
/>
)}
</AutoSizer>
)}
</InfiniteLoader>
</List>
);
}
}
render(VirtualizedScroll);
You can integrate react-dnd to implement drag and drop sort.
import React from 'react';
import { List, Avatar } from '@douyinfe/semi-ui';
import { DndProvider, DragSource, DropTarget } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import ReactDOM from 'react-dom';
class DraggableItem extends React.Component {
render() {
const { component, draggingItem, index, connectDragSource, connectDropTarget } = this.props;
const opacity = draggingItem && draggingItem.index === index ? 0.3 : 1;
const style = {
border: '1px solid var(--semi-color-border)',
marginBottom: 12,
backgroundColor: 'var(--semi-color-bg-2)',
cursor: 'move',
};
return connectDragSource(
connectDropTarget(
<div ref={node => (this.node = node)} style={{ ...style, opacity }}>
{component}
</div>
)
);
}
}
const cardSource = {
beginDrag(props) {
return {
id: props.id,
index: props.index,
};
},
};
const cardTarget = {
hover(props, monitor, component) {
const dragIndex = monitor.getItem().index;
const hoverIndex = props.index;
if (dragIndex === hoverIndex) {
return;
}
const hoverBoundingRect = ReactDOM.findDOMNode(component).getBoundingClientRect();
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
const clientOffset = monitor.getClientOffset();
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return;
}
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return;
}
monitor.getItem().index = hoverIndex;
props.moveItem(dragIndex, hoverIndex);
},
};
function collectDragSource(connect, monitor) {
return {
connectDragSource: connect.dragSource(),
draggingItem: monitor.getItem(),
};
}
function collectDropTarget(connect) {
return {
connectDropTarget: connect.dropTarget(),
};
}
DraggableItem = DragSource('item', cardSource, collectDragSource)(DraggableItem);
DraggableItem = DropTarget('item', cardTarget, collectDropTarget)(DraggableItem);
class DraggableList extends React.Component {
constructor() {
const listItems = [
{
title: 'Semi Design Title 1',
color: 'red',
},
{
title: 'Semi Design Title 2',
color: 'grey',
},
{
title: 'Semi Design Title 3',
color: 'light-green',
},
{
title: 'Semi Design Title 4',
color: 'light-blue',
},
{
title: 'Semi Design Title 5',
color: 'pink',
},
];
super();
this.state = {
data: listItems,
};
this.moveItem = this.moveItem.bind(this);
this.renderDraggable = this.renderDraggable.bind(this);
}
moveItem(dragIndex, hoverIndex) {
const { data } = this.state;
let temp = data[dragIndex];
data[dragIndex] = data[hoverIndex];
data[hoverIndex] = temp;
this.setState(
{
...this.state,
data
}
);
}
renderDraggable(item, id) {
const content = (
<List.Item
header={<Avatar color={item.color}>SE</Avatar>}
main={
<div>
<span style={{ color: 'var(--semi-color-text-0)', fontWeight: 500 }}>{item.title}</span>
<p style={{ color: 'var(--semi-color-text-2)', margin: '4px 0' }}>
Create a consistent, good-looking, easy-to-use, and efficient user experience with a
user-centric, content-first, and human-friendly design system
</p>
</div>
}
/>
);
return (
<DraggableItem key={item.title} index={id} id={item.title} component={content} moveItem={this.moveItem} />
);
}
render() {
const { data } = this.state;
return (
<div style={{ padding: 12, border: '1px solid var(--semi-color-border)', margin: 12 }}>
<DndProvider backend={HTML5Backend}>
<List dataSource={data} renderItem={this.renderDraggable} />
</DndProvider>
</div>
);
}
}
render(DraggableList);
If you use react-sortable-hoc, here is also an example
import React, { useState } from 'react';
import { List } from '@douyinfe/semi-ui';
import { IconHandle } from '@douyinfe/semi-icons';
import { SortableContainer, SortableElement, sortableHandle } from 'react-sortable-hoc';
() => {
const data = [
'Siege',
'The ordinary world',
'Three Body',
'Snow in the Snow',
'Saharan story',
'Those things',
'A little monk of Zen',
'Dune',
'The courage to be hated',
'Crime and Punishment',
'Moon and sixpence',
'The silent majority',
'First person singular',
];
const [list, setList] = useState(data.slice(0, 6));
const renderItem = (props) => {
const { item } = props;
const DragHandle = sortableHandle(() => <IconHandle className={`list-item-drag-handler`} style={{ marginRight: 4 }} />);
return (
<List.Item className='component-list-demo-drag-item list-item'>
<DragHandle />
{item}
</List.Item>
);
};
const arrayMove = (array, from, to) => {
let newArray = array.slice();
newArray.splice(to < 0 ? newArray.length + to : to, 0, newArray.splice(from, 1)[0]);
return newArray;
};
const onSortEnd = (callbackProps) => {
let { oldIndex, newIndex } = callbackProps;
let newList = arrayMove(list, oldIndex, newIndex);
setList(newList);
};
const SortableItem = SortableElement(props => renderItem(props));
const SortableList = SortableContainer(
({ items }) => {
return (
<div className="sortable-list-main">
{items.map((item, index) => (
<SortableItem key={item} index={index} item={item}></SortableItem>
))}
</div>
);
},
{ distance: 10 }
);
return (
<div>
<div style={{ marginRight: 16, width: 280, display: 'flex', flexWrap: 'wrap', border: '1px solid var(--semi-color-border)' }}>
<List style={{ width: '100%' }} className='component-list-demo-booklist'>
<SortableList useDragHandle onSortEnd={onSortEnd} items={list}></SortableList>
</List>
</div>
</div>
);
};
You can use Pagination in combination to achieve a paged List
import React, { useState } from 'react';
import { List, Pagination } from '@douyinfe/semi-ui';
() => {
const data = [
'Siege',
'The ordinary world',
'Three Body',
'Snow in the Snow',
'Saharan story',
'Those things in the Ming Dynasty',
'A little monk of Zen',
'Dune',
'The courage to be hated',
'Crime and Punishment',
'Moon and sixpence',
'The silent majority',
'First person singular',
];
const [page, onPageChange] = useState(1);
let pageSize = 4;
const getData = (page) => {
let start = (page - 1) * pageSize;
let end = page * pageSize;
return data.slice(start, end);
};
return (
<div>
<div style={{ marginRight: 16, width: 280, display: 'flex', flexWrap: 'wrap' }}>
<List
dataSource={getData(page)}
split={false}
size='small'
className='component-list-demo-booklist'
style={{ border: '1px solid var(--semi-color-border)', flexBasis: '100%', flexShrink: 0 }}
renderItem={item => <List.Item className='list-item'>{item}</List.Item>}
/>
<Pagination size='small' style={{ width: '100%', flexBasis: '100%', justifyContent: 'center' }} pageSize={pageSize} total={data.length} currentPage={page} onChange={cPage => onPageChange(cPage)} />
</div>
</div>
);
};
You can use it by assembling Input to filter the List
import React, { useState } from 'react';
import { List, Input } from '@douyinfe/semi-ui';
import { IconSearch } from '@douyinfe/semi-icons';
() => {
const data = [
'Siege',
'The ordinary world',
'Three Body',
'Snow in the Snow',
'Saharan story',
'Those things in the Ming Dynasty',
'A little monk of Zen',
'Dune',
'The courage to be hated',
'Crime and Punishment',
];
const [list, setList] = useState(data);
const onSearch = (string) => {
let newList;
if (string) {
newList = data.filter(item => item.includes(string));
} else {
newList = data;
}
setList(newList);
};
return (
<div>
<div style={{ marginRight: 16, width: 280, display: 'flex', flexWrap: 'wrap', border: '1px solid var(--semi-color-border)' }}>
<List
className='component-list-demo-booklist'
dataSource={list}
split={false}
header={<Input onCompositionEnd={(v) => onSearch(v.target.value)} onChange={(v) => !v ? onSearch() : null} placeholder='search' prefix={<IconSearch />} />}
size='small'
style={{ flexBasis: '100%', flexShrink: 0, borderBottom: '1px solid var(--semi-color-border)' }}
renderItem={item =>
<List.Item className='list-item'>{item}</List.Item>
}
/>
</div>
</div>
)
}
import React, { useState } from 'react';
import { List, Input, Button } from '@douyinfe/semi-ui';
import { IconMinusCircle, IconPlusCircle } from '@douyinfe/semi-icons';
() => {
const data = [
'Siege',
'The ordinary world',
'Three Body',
'Snow in the Snow',
'Saharan story',
'Those things in the Ming Dynasty',
'A little monk of Zen',
'Dune',
'The courage to be hated',
'Crime and Punishment',
'Moon and sixpence',
'The silent majority',
'First person singular',
];
const [list, setList] = useState(data.slice(0, 8));
const updateList = (item) => {
let newList;
if (item) {
newList = list.filter(i => item !== i);
} else {
newList = list.concat(data.slice(list.length, list.length + 1))
}
setList(newList);
};
return (
<div>
<div style={{ marginRight: 16, width: 280, display: 'flex', flexWrap: 'wrap', border: '1px solid var(--semi-color-border)' }}>
<List
className='component-list-demo-booklist'
dataSource={list}
split={false}
size='small'
style={{ flexBasis: '100%', flexShrink: 0, borderBottom: '1px solid var(--semi-color-border)' }}
renderItem={item =>
<div style={{ margin: 4 }} className='list-item'>
<Button type='danger' theme='borderless' icon={<IconMinusCircle />} onClick={() => updateList(item)} style={{ marginRight: 4 }} />
{item}
</div>
}
/>
<div style={{ margin: 4, fontSize: 14 }} onClick={() => updateList()}>
<Button theme='borderless' icon={<IconPlusCircle />} style={{ marginRight: 4, color: 'var(--semi-color-info)' }}>
</Button>
Add book
</div>
</div>
</div>
);
};
You can enhance the List into a list selector by combining Radio or Checkbox
import React, { useState } from 'react';
import { List, Input, Button, Checkbox, Radio, RadioGroup, CheckboxGroup } from '@douyinfe/semi-ui';
() => {
const data = [
'Siege',
'The ordinary world',
'Three Body',
'Snow in the Snow',
'Saharan story',
'Those things in the Ming Dynasty',
'A little monk of Zen',
'Dune',
'The courage to be hated',
'Crime and Punishment',
'Moon and sixpence',
'The silent majority',
'First person singular',
];
const [page, onPageChange] = useState(1);
const [checkboxVal, setCV] = useState([...data[0]]);
const [radioVal, setRV] = useState(data[0]);
let pageSize = 8;
const getData = (page) => {
let start = (page - 1) * pageSize;
let end = page * pageSize;
return data.slice(start, end);
};
return (
<div style={{ display: 'flex' }}>
<div style={{ marginRight: 16, width: 280, display: 'flex', flexWrap: 'wrap' }}>
<CheckboxGroup value={checkboxVal} onChange={(value) => setCV(value)}>
<List
dataSource={getData(page)}
className='component-list-demo-booklist'
split={false}
size='small'
style={{ border: '1px solid var(--semi-color-border)', flexBasis: '100%', flexShrink: 0 }}
renderItem={item => <List.Item className='list-item'><Checkbox value={item}>{item}</Checkbox></List.Item>}
/>
</CheckboxGroup>
</div>
<div style={{ marginRight: 16, width: 280, display: 'flex', flexWrap: 'wrap' }}>
<RadioGroup value={radioVal} onChange={(e) => setRV(e.target.value)}>
<List
className='component-list-demo-booklist'
dataSource={getData(page)}
split={false}
size='small'
style={{ border: '1px solid var(--semi-color-border)', flexBasis: '100%', flexShrink: 0 }}
renderItem={item => <List.Item className='list-item'><Radio value={item}>{item}</Radio></List.Item>}
/>
</RadioGroup>
</div>
</div>
);
};
You can monitor the keyboard events of the corresponding keys by yourself to realize the selection of different items. As in the following example, you can use the up and down arrow keys to select different items
import React, { useState, useRef } from 'react';
import { List, Input, Button } from '@douyinfe/semi-ui';
() => {
const data = [
'Siege',
'The ordinary world',
'Three Body',
'Snow in the Snow ',
'Saharan story',
'Those things in the Ming Dynasty',
'A little monk of Zen',
'Dune',
'The courage to be hated',
'Crime and Punishment',
'Moon and sixpence',
'The silent majority',
'First person singular',
];
const [list, setList] = useState(data.slice(0, 10));
const [hoverIndex, setHi] = useState(-1);
const i = useRef(-1);
let changeIndex = (offset) => {
let currentIndex = i.current;
let index = currentIndex + offset;
if (index < 0) {
index = list.length - 1;
}
if (index >= list.length) {
index = 0;
}
i.current = index;
setHi(index);
};
useEffect(() => {
let keydownHandler = (event) => {
let key = event.keyCode;
switch (key) {
case 38: // KeyCode.UP
event.preventDefault();
changeIndex(-1);
break;
case 40: // KeyCode.DOWN
event.preventDefault();
changeIndex(1);
break;
default:
break;
}
};
window.addEventListener('keydown', keydownHandler);
return () => {
window.removeEventListener('keydown', keydownHandler);
};
}, []);
return (
<div>
<div style={{ marginRight: 16, width: 280, display: 'flex', flexWrap: 'wrap', border: '1px solid var(--semi-color-border)' }}>
<List
className='component-list-demo-booklist'
dataSource={list}
split={false}
size='small'
style={{ flexBasis: '100%', flexShrink: 0, borderBottom: '1px solid var(--semi-color-border)' }}
renderItem={(item, index) =>
<List.Item className={index === hoverIndex ? 'component-list-demo-booklist-active-item' : ''}>{item}</List.Item>
}
/>
</div>
</div>
);
};
The custom styles involved in the Demo of the above book list example are as follows
.component-list-demo-booklist {
.list-item {
&:hover {
background-color: var(--semi-color-fill-0);
}
&:active {
background-color: var(--semi-color-fill-1);
}
}
}
body > .component-list-demo-drag-item {
font-size: 14px;
}
.component-list-demo-booklist-active-item {
background-color: var(--semi-color-fill-0);
}
| Properties | Instructions | type | Default |
|---|---|---|---|
| bordered | Toggle whether to display border | boolean | false |
| className | Class name | string | - |
| dataSource | List data source | any[] | - |
| emptyContent | Displayed content when empty | ReactNode | - |
| footer | Footer of list | ReactNode | - |
| grid | Grid configuration | Grid | - |
| header | Header of list | ReactNode | - |
| layout | Layout, one of vertical, vertical |
string | vertical |
| loadMore | Loadmore button | ReactNode | - |
| loading | Toggle whether to display Spin when loading |
boolean | false |
| renderItem | When using dataSource, you can customize rendering with renderItem | (item, ind) => ReactNode | - |
| size | Size, one of small, default, large |
string | default |
| split | Toggle whether to display split line | boolean | true |
| style | Inline style | CSSProperties | - |
| onClick | Callback function when click an item v>=1.0.0 | function | - |
| onRightClick | Callback function when right click an item v>=1.0.0 | function | - |
v>=1.7.0 Other grid properties are also supported. Refer to Grid.
| Properties | Instructions | type | Default |
|---|---|---|---|
| span | Number of grid spaces | number | - |
| gutter | Grid spacing | number | 0 |
| xs | < 576px responsive grid, a number or an object containing other attributes |
number|object | - |
| sm | ≥ 576px responsive grid, a number or an object containing other properties |
number|object | - |
| md | ≥ 768px responsive grid, a number or an object containing other properties |
number|object | - |
| lg | ≥ 992px responsive grid, a number or an object containing other properties |
number|object | - |
| xl | ≥ 1200px responsive grid, a number or an object containing other properties |
number|object | - |
| xxl | ≥ 1600px responsive grid, a number or an object containing other properties |
number|object | - |
| Properties | Instructions | type | Default |
|---|---|---|---|
| align | Vertical alignment of header and main, one of flex-start, flex-end, center, baseline, stretch |
string | flex-start |
| className | Class name | string | - |
| extra | Additional content | ReactNode | - |
| header | List item header content | ReactNode | - |
| main | List item body content | ReactNode | - |
| onClick | Callback function when click an item v>=1.0.0 | function | - |
| onRightClick | Callback function when right click an item v>=1.0.0 | function | - |
| style | Inline style | CSSProperties | - |