From 5cca28a8d091a9306aa26707abf7015d203cad8d Mon Sep 17 00:00:00 2001 From: tjz <415800467@qq.com> Date: Sat, 16 Jun 2018 21:30:41 +0800 Subject: [PATCH] feat: add list component --- components/_util/props-util.js | 3 + components/index.js | 6 +- components/list/Item.jsx | 143 ++++++ .../__tests__/__snapshots__/demo.test.js.snap | 486 ++++++++++++++++++ components/list/__tests__/demo.test.js | 3 + components/list/demo/basic.md | 53 ++ components/list/demo/grid.md | 48 ++ components/list/demo/index.vue | 57 ++ components/list/demo/infinite-load.md | 95 ++++ .../list/demo/infinite-virtualized-load.md | 95 ++++ components/list/demo/loadmore.md | 86 ++++ components/list/demo/resposive.md | 55 ++ components/list/demo/simple.md | 69 +++ components/list/demo/vertical.md | 73 +++ components/list/index.en-US.md | 46 ++ components/list/index.jsx | 249 +++++++++ components/list/index.zh-CN.md | 46 ++ components/list/style/bordered.less | 41 ++ components/list/style/index.js | 7 + components/list/style/index.less | 207 ++++++++ components/list/style/responsive.less | 42 ++ components/style.js | 1 + site/components.js | 6 +- site/demo.js | 1 + tests/__snapshots__/index.test.js.snap | 1 + 25 files changed, 1916 insertions(+), 3 deletions(-) create mode 100644 components/list/Item.jsx create mode 100644 components/list/__tests__/__snapshots__/demo.test.js.snap create mode 100644 components/list/__tests__/demo.test.js create mode 100644 components/list/demo/basic.md create mode 100644 components/list/demo/grid.md create mode 100644 components/list/demo/index.vue create mode 100644 components/list/demo/infinite-load.md create mode 100644 components/list/demo/infinite-virtualized-load.md create mode 100644 components/list/demo/loadmore.md create mode 100644 components/list/demo/resposive.md create mode 100644 components/list/demo/simple.md create mode 100644 components/list/demo/vertical.md create mode 100644 components/list/index.en-US.md create mode 100644 components/list/index.jsx create mode 100644 components/list/index.zh-CN.md create mode 100644 components/list/style/bordered.less create mode 100644 components/list/style/index.js create mode 100644 components/list/style/index.less create mode 100644 components/list/style/responsive.less diff --git a/components/_util/props-util.js b/components/_util/props-util.js index a78030095..0a1cf7d6b 100644 --- a/components/_util/props-util.js +++ b/components/_util/props-util.js @@ -59,6 +59,9 @@ const getSlots = (ele) => { return slots } const getSlotOptions = (ele) => { + if (ele.fnOptions) { // 函数式组件 + return ele.fnOptions + } let componentOptions = ele.componentOptions if (ele.$vnode) { componentOptions = ele.$vnode.componentOptions diff --git a/components/index.js b/components/index.js index ee1b260f1..a9779713a 100644 --- a/components/index.js +++ b/components/index.js @@ -60,7 +60,7 @@ import { default as InputNumber } from './input-number' import { default as Layout } from './layout' -// import { default as List } from './list' +import { default as List } from './list' import { default as LocaleProvider } from './locale-provider' @@ -163,6 +163,9 @@ const components = [ Layout.Footer, Layout.Sider, Layout.Content, + List, + List.Item, + List.Item.Meta, LocaleProvider, Menu, Menu.Item, @@ -247,6 +250,7 @@ export { Input, InputNumber, Layout, + List, LocaleProvider, Menu, Modal, diff --git a/components/list/Item.jsx b/components/list/Item.jsx new file mode 100644 index 000000000..212047ff4 --- /dev/null +++ b/components/list/Item.jsx @@ -0,0 +1,143 @@ +import PropTypes from '../_util/vue-types' +import classNames from 'classnames' +import { getSlotOptions, getComponentFromProp, isEmptyElement } from '../_util/props-util' +import { Col } from '../grid' +import { ListGridType } from './index' + +export const ListItemProps = { + prefixCls: PropTypes.string, + extra: PropTypes.any, + actions: PropTypes.arrayOf(PropTypes.any), + grid: ListGridType, +} + +export const ListItemMetaProps = { + avatar: PropTypes.any, + description: PropTypes.any, + prefixCls: PropTypes.string, + title: PropTypes.any, +} + +export const Meta = { + functional: true, + name: 'AListItemMeta', + __ANT_LIST_ITEM_META: true, + render (h, context) { + const { props, slots, listeners } = context + const slotsMap = slots() + const { + prefixCls = 'ant-list', + } = props + const avatar = props.avatar || slotsMap.avatar + const title = props.title || slotsMap.title + const description = props.description || slotsMap.description + const content = ( + <div class={`${prefixCls}-item-meta-content`}> + {title && <h4 class={`${prefixCls}-item-meta-title`}>{title}</h4>} + {description && <div class={`${prefixCls}-item-meta-description`}>{description}</div>} + </div> + ) + return ( + <div {...{ on: listeners }} class={`${prefixCls}-item-meta`}> + {avatar && <div class={`${prefixCls}-item-meta-avatar`}>{avatar}</div>} + {(title || description) && content} + </div> + ) + }, + +} + +function getGrid (grid, t) { + return grid[t] && Math.floor(24 / grid[t]) +} + +export default { + name: 'AListItem', + Meta, + props: ListItemProps, + inject: { + listContext: { default: {}}, + }, + + render () { + const { grid } = this.listContext + const { prefixCls = 'ant-list', $slots, $listeners } = this + const classString = `${prefixCls}-item` + const extra = getComponentFromProp(this, 'extra') + const actions = getComponentFromProp(this, 'actions') + const metaContent = [] + const otherContent = [] + + ;($slots.default || []).forEach((element) => { + if (!isEmptyElement(element)) { + if (getSlotOptions(element).__ANT_LIST_ITEM_META) { + metaContent.push(element) + } else { + otherContent.push(element) + } + } + }) + + const contentClassString = classNames(`${prefixCls}-item-content`, { + [`${prefixCls}-item-content-single`]: (metaContent.length < 1), + }) + const content = otherContent.length > 0 ? ( + <div class={contentClassString}> + {otherContent} + </div>) : null + + let actionsContent + if (actions && actions.length > 0) { + const actionsContentItem = (action, i) => ( + <li key={`${prefixCls}-item-action-${i}`}> + {action} + {i !== (actions.length - 1) && <em class={`${prefixCls}-item-action-split`}/>} + </li> + ) + actionsContent = ( + <ul class={`${prefixCls}-item-action`}> + {actions.map((action, i) => actionsContentItem(action, i))} + </ul> + ) + } + + const extraContent = ( + <div class={`${prefixCls}-item-extra-wrap`}> + <div class={`${prefixCls}-item-main`}> + {metaContent} + {content} + {actionsContent} + </div> + <div class={`${prefixCls}-item-extra`}>{extra}</div> + </div> + ) + + const mainContent = grid ? ( + <Col + span={getGrid(grid, 'column')} + xs={getGrid(grid, 'xs')} + sm={getGrid(grid, 'sm')} + md={getGrid(grid, 'md')} + lg={getGrid(grid, 'lg')} + xl={getGrid(grid, 'xl')} + xxl={getGrid(grid, 'xxl')} + > + <div {...{ on: $listeners }} class={classString}> + {extra && extraContent} + {!extra && metaContent} + {!extra && content} + {!extra && actionsContent} + </div> + </Col> + ) : ( + <div {...{ on: $listeners }} class={classString}> + {extra && extraContent} + {!extra && metaContent} + {!extra && content} + {!extra && actionsContent} + </div> + ) + + return mainContent + }, +} diff --git a/components/list/__tests__/__snapshots__/demo.test.js.snap b/components/list/__tests__/__snapshots__/demo.test.js.snap new file mode 100644 index 000000000..a23c0b0f5 --- /dev/null +++ b/components/list/__tests__/__snapshots__/demo.test.js.snap @@ -0,0 +1,486 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders ./components/list/demo/basic.md correctly 1`] = ` +<div class="ant-list ant-list-split"> + <div class="ant-spin-nested-loading"> + <div class="ant-spin-container"> + <div class="ant-list-item"> + <div class="ant-list-item-meta"> + <div class="ant-list-item-meta-avatar"><span class="ant-avatar ant-avatar-image ant-avatar-circle"><img src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"></span></div> + <div class="ant-list-item-meta-content"> + <h4 class="ant-list-item-meta-title"> + <a href="https://vuecomponent.github.io/ant-design/">Ant Design Title 1</a> + </h4> + <div class="ant-list-item-meta-description">Ant Design, a design language for background applications, is refined by Ant UED Team</div> + </div> + </div> + </div> + <div class="ant-list-item"> + <div class="ant-list-item-meta"> + <div class="ant-list-item-meta-avatar"><span class="ant-avatar ant-avatar-image ant-avatar-circle"><img src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"></span></div> + <div class="ant-list-item-meta-content"> + <h4 class="ant-list-item-meta-title"> + <a href="https://vuecomponent.github.io/ant-design/">Ant Design Title 2</a> + </h4> + <div class="ant-list-item-meta-description">Ant Design, a design language for background applications, is refined by Ant UED Team</div> + </div> + </div> + </div> + <div class="ant-list-item"> + <div class="ant-list-item-meta"> + <div class="ant-list-item-meta-avatar"><span class="ant-avatar ant-avatar-image ant-avatar-circle"><img src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"></span></div> + <div class="ant-list-item-meta-content"> + <h4 class="ant-list-item-meta-title"> + <a href="https://vuecomponent.github.io/ant-design/">Ant Design Title 3</a> + </h4> + <div class="ant-list-item-meta-description">Ant Design, a design language for background applications, is refined by Ant UED Team</div> + </div> + </div> + </div> + <div class="ant-list-item"> + <div class="ant-list-item-meta"> + <div class="ant-list-item-meta-avatar"><span class="ant-avatar ant-avatar-image ant-avatar-circle"><img src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"></span></div> + <div class="ant-list-item-meta-content"> + <h4 class="ant-list-item-meta-title"> + <a href="https://vuecomponent.github.io/ant-design/">Ant Design Title 4</a> + </h4> + <div class="ant-list-item-meta-description">Ant Design, a design language for background applications, is refined by Ant UED Team</div> + </div> + </div> + </div> + </div> + </div> +</div> +`; + +exports[`renders ./components/list/demo/grid.md correctly 1`] = ` +<div class="ant-list ant-list-split ant-list-grid"> + <div class="ant-spin-nested-loading"> + <div class="ant-spin-container"> + <div class="ant-row" style="margin-left: -8px; margin-right: -8px;"> + <div class="ant-col-6" style="padding-left: 8px; padding-right: 8px;"> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single"> + <div class="ant-card ant-card-bordered"> + <div class="ant-card-head"> + <div class="ant-card-head-wrapper"> + <div class="ant-card-head-title">Title 1</div> + </div> + </div> + <div class="ant-card-body">Card content</div> + </div> + </div> + </div> + </div> + <div class="ant-col-6" style="padding-left: 8px; padding-right: 8px;"> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single"> + <div class="ant-card ant-card-bordered"> + <div class="ant-card-head"> + <div class="ant-card-head-wrapper"> + <div class="ant-card-head-title">Title 2</div> + </div> + </div> + <div class="ant-card-body">Card content</div> + </div> + </div> + </div> + </div> + <div class="ant-col-6" style="padding-left: 8px; padding-right: 8px;"> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single"> + <div class="ant-card ant-card-bordered"> + <div class="ant-card-head"> + <div class="ant-card-head-wrapper"> + <div class="ant-card-head-title">Title 3</div> + </div> + </div> + <div class="ant-card-body">Card content</div> + </div> + </div> + </div> + </div> + <div class="ant-col-6" style="padding-left: 8px; padding-right: 8px;"> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single"> + <div class="ant-card ant-card-bordered"> + <div class="ant-card-head"> + <div class="ant-card-head-wrapper"> + <div class="ant-card-head-title">Title 4</div> + </div> + </div> + <div class="ant-card-body">Card content</div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> +</div> +`; + +exports[`renders ./components/list/demo/infinite-load.md correctly 1`] = ` +<div infinite-scroll-distance="10" class="demo-infinite-container"> + <div class="ant-list ant-list-split"> + <div class="ant-spin ant-spin-show-text"><span class="ant-spin-dot"><i></i><i></i><i></i><i></i></span></div> + </div> +</div> +`; + +exports[`renders ./components/list/demo/infinite-virtualized-load.md correctly 1`] = ` +<div class="ant-list ant-list-split"> + <div class="ant-spin-nested-loading"> + <div class="ant-spin-container"> + <div data-v-caa2d078="" class="virtual-scroller" infinite-scroll-distance="10" style="height: 400px;"> + <div data-v-caa2d078="" class="item-container"> + <div data-v-caa2d078="" class="items"></div> + </div> + <div data-v-b329ee4c="" data-v-caa2d078="" tabindex="-1" class="resize-observer"> + <object style="display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; pointer-events: none; z-index: -1;" aria-hidden="true" tabindex="-1" type="text/html" data="about:blank"></object> + </div> + </div> + </div> + </div> +</div> +`; + +exports[`renders ./components/list/demo/loadmore.md correctly 1`] = ` +<div class="demo-loadmore-list ant-list ant-list-split ant-list-loading ant-list-something-after-last-item"> + <div class="ant-spin-nested-loading"> + <div> + <div class="ant-spin ant-spin-spinning ant-spin-show-text"><span class="ant-spin-dot"><i></i><i></i><i></i><i></i></span></div> + </div> + <div class="ant-spin-container ant-spin-blur"> + <div style="min-height: 53;"></div> + </div> + </div> + <div style="text-align: center; margin-top: 12px; height: 32px; line-height: 32px;"> + <button type="button" class="ant-btn ant-btn-default"><span>loading more</span></button> + </div> +</div> +`; + +exports[`renders ./components/list/demo/resposive.md correctly 1`] = ` +<div class="ant-list ant-list-split ant-list-grid"> + <div class="ant-spin-nested-loading"> + <div class="ant-spin-container"> + <div class="ant-row" style="margin-left: -8px; margin-right: -8px;"> + <div class="ant-col-xs-24 ant-col-sm-12 ant-col-md-6 ant-col-lg-6 ant-col-xl-4 ant-col-xxl-8" style="padding-left: 8px; padding-right: 8px;"> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single"> + <div class="ant-card ant-card-bordered"> + <div class="ant-card-head"> + <div class="ant-card-head-wrapper"> + <div class="ant-card-head-title">Title 1</div> + </div> + </div> + <div class="ant-card-body">Card content</div> + </div> + </div> + </div> + </div> + <div class="ant-col-xs-24 ant-col-sm-12 ant-col-md-6 ant-col-lg-6 ant-col-xl-4 ant-col-xxl-8" style="padding-left: 8px; padding-right: 8px;"> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single"> + <div class="ant-card ant-card-bordered"> + <div class="ant-card-head"> + <div class="ant-card-head-wrapper"> + <div class="ant-card-head-title">Title 2</div> + </div> + </div> + <div class="ant-card-body">Card content</div> + </div> + </div> + </div> + </div> + <div class="ant-col-xs-24 ant-col-sm-12 ant-col-md-6 ant-col-lg-6 ant-col-xl-4 ant-col-xxl-8" style="padding-left: 8px; padding-right: 8px;"> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single"> + <div class="ant-card ant-card-bordered"> + <div class="ant-card-head"> + <div class="ant-card-head-wrapper"> + <div class="ant-card-head-title">Title 3</div> + </div> + </div> + <div class="ant-card-body">Card content</div> + </div> + </div> + </div> + </div> + <div class="ant-col-xs-24 ant-col-sm-12 ant-col-md-6 ant-col-lg-6 ant-col-xl-4 ant-col-xxl-8" style="padding-left: 8px; padding-right: 8px;"> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single"> + <div class="ant-card ant-card-bordered"> + <div class="ant-card-head"> + <div class="ant-card-head-wrapper"> + <div class="ant-card-head-title">Title 4</div> + </div> + </div> + <div class="ant-card-body">Card content</div> + </div> + </div> + </div> + </div> + <div class="ant-col-xs-24 ant-col-sm-12 ant-col-md-6 ant-col-lg-6 ant-col-xl-4 ant-col-xxl-8" style="padding-left: 8px; padding-right: 8px;"> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single"> + <div class="ant-card ant-card-bordered"> + <div class="ant-card-head"> + <div class="ant-card-head-wrapper"> + <div class="ant-card-head-title">Title 5</div> + </div> + </div> + <div class="ant-card-body">Card content</div> + </div> + </div> + </div> + </div> + <div class="ant-col-xs-24 ant-col-sm-12 ant-col-md-6 ant-col-lg-6 ant-col-xl-4 ant-col-xxl-8" style="padding-left: 8px; padding-right: 8px;"> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single"> + <div class="ant-card ant-card-bordered"> + <div class="ant-card-head"> + <div class="ant-card-head-wrapper"> + <div class="ant-card-head-title">Title 6</div> + </div> + </div> + <div class="ant-card-body">Card content</div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> +</div> +`; + +exports[`renders ./components/list/demo/simple.md correctly 1`] = ` +<div> + <h3 style="margin-bottom: 16px;">Default Size</h3> + <div class="ant-list ant-list-split ant-list-bordered ant-list-something-after-last-item"> + <div class="ant-list-header"> + <div>Header</div> + </div> + <div class="ant-spin-nested-loading"> + <div class="ant-spin-container"> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single">Racing car sprays burning fuel into crowd.</div> + </div> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single">Japanese princess to wed commoner.</div> + </div> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single">Australian walks 100km after outback crash.</div> + </div> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single">Man charged over missing wedding girl.</div> + </div> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single">Los Angeles battles huge wildfires.</div> + </div> + </div> + </div> + <div class="ant-list-footer"> + <div>Footer</div> + </div> + </div> + <h3 style="margin: 16px 0px;">Small Size</h3> + <div class="ant-list ant-list-sm ant-list-split ant-list-bordered ant-list-something-after-last-item"> + <div class="ant-list-header"> + <div>Header</div> + </div> + <div class="ant-spin-nested-loading"> + <div class="ant-spin-container"> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single">Racing car sprays burning fuel into crowd.</div> + </div> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single">Japanese princess to wed commoner.</div> + </div> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single">Australian walks 100km after outback crash.</div> + </div> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single">Man charged over missing wedding girl.</div> + </div> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single">Los Angeles battles huge wildfires.</div> + </div> + </div> + </div> + <div class="ant-list-footer"> + <div>Footer</div> + </div> + </div> + <h3 style="margin: 16px 0px;">Large Size</h3> + <div class="ant-list ant-list-lg ant-list-split ant-list-bordered ant-list-something-after-last-item"> + <div class="ant-list-header"> + <div>Header</div> + </div> + <div class="ant-spin-nested-loading"> + <div class="ant-spin-container"> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single">Racing car sprays burning fuel into crowd.</div> + </div> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single">Japanese princess to wed commoner.</div> + </div> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single">Australian walks 100km after outback crash.</div> + </div> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single">Man charged over missing wedding girl.</div> + </div> + <div class="ant-list-item"> + <div class="ant-list-item-content ant-list-item-content-single">Los Angeles battles huge wildfires.</div> + </div> + </div> + </div> + <div class="ant-list-footer"> + <div>Footer</div> + </div> + </div> +</div> +`; + +exports[`renders ./components/list/demo/vertical.md correctly 1`] = ` +<div class="ant-list ant-list-vertical ant-list-lg ant-list-split ant-list-something-after-last-item"> + <div class="ant-spin-nested-loading"> + <div class="ant-spin-container"> + <div class="ant-list-item"> + <div class="ant-list-item-extra-wrap"> + <div class="ant-list-item-main"> + <div class="ant-list-item-meta"> + <div class="ant-list-item-meta-avatar"><span class="ant-avatar ant-avatar-image ant-avatar-circle"><img src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"></span></div> + <div class="ant-list-item-meta-content"> + <h4 class="ant-list-item-meta-title"> + <a href="https://vuecomponent.github.io/ant-design/">ant design part 0</a> + </h4> + <div class="ant-list-item-meta-description">Ant Design, a design language for background applications, is refined by Ant UED Team.</div> + </div> + </div> + <div class="ant-list-item-content"> + We supply a series of design principles, practical patterns and high quality design resources (Sketch and Axure), to help people create their product prototypes beautifully and efficiently. + </div> + <ul class="ant-list-item-action"> + <li><span><i class="anticon anticon-star-o" style="margin-right: 8px;"></i> + 156 + </span><em class="ant-list-item-action-split"></em></li> + <li><span><i class="anticon anticon-like-o" style="margin-right: 8px;"></i> + 156 + </span><em class="ant-list-item-action-split"></em></li> + <li><span><i class="anticon anticon-message-o" style="margin-right: 8px;"></i> + 2 + </span></li> + </ul> + </div> + <div class="ant-list-item-extra"> + <img width="272" alt="logo" src="https://gw.alipayobjects.com/zos/rmsportal/mqaQswcyDLcXyDKnZfES.png"> + </div> + </div> + </div> + <div class="ant-list-item"> + <div class="ant-list-item-extra-wrap"> + <div class="ant-list-item-main"> + <div class="ant-list-item-meta"> + <div class="ant-list-item-meta-avatar"><span class="ant-avatar ant-avatar-image ant-avatar-circle"><img src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"></span></div> + <div class="ant-list-item-meta-content"> + <h4 class="ant-list-item-meta-title"> + <a href="https://vuecomponent.github.io/ant-design/">ant design part 1</a> + </h4> + <div class="ant-list-item-meta-description">Ant Design, a design language for background applications, is refined by Ant UED Team.</div> + </div> + </div> + <div class="ant-list-item-content"> + We supply a series of design principles, practical patterns and high quality design resources (Sketch and Axure), to help people create their product prototypes beautifully and efficiently. + </div> + <ul class="ant-list-item-action"> + <li><span><i class="anticon anticon-star-o" style="margin-right: 8px;"></i> + 156 + </span><em class="ant-list-item-action-split"></em></li> + <li><span><i class="anticon anticon-like-o" style="margin-right: 8px;"></i> + 156 + </span><em class="ant-list-item-action-split"></em></li> + <li><span><i class="anticon anticon-message-o" style="margin-right: 8px;"></i> + 2 + </span></li> + </ul> + </div> + <div class="ant-list-item-extra"> + <img width="272" alt="logo" src="https://gw.alipayobjects.com/zos/rmsportal/mqaQswcyDLcXyDKnZfES.png"> + </div> + </div> + </div> + <div class="ant-list-item"> + <div class="ant-list-item-extra-wrap"> + <div class="ant-list-item-main"> + <div class="ant-list-item-meta"> + <div class="ant-list-item-meta-avatar"><span class="ant-avatar ant-avatar-image ant-avatar-circle"><img src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png"></span></div> + <div class="ant-list-item-meta-content"> + <h4 class="ant-list-item-meta-title"> + <a href="https://vuecomponent.github.io/ant-design/">ant design part 2</a> + </h4> + <div class="ant-list-item-meta-description">Ant Design, a design language for background applications, is refined by Ant UED Team.</div> + </div> + </div> + <div class="ant-list-item-content"> + We supply a series of design principles, practical patterns and high quality design resources (Sketch and Axure), to help people create their product prototypes beautifully and efficiently. + </div> + <ul class="ant-list-item-action"> + <li><span><i class="anticon anticon-star-o" style="margin-right: 8px;"></i> + 156 + </span><em class="ant-list-item-action-split"></em></li> + <li><span><i class="anticon anticon-like-o" style="margin-right: 8px;"></i> + 156 + </span><em class="ant-list-item-action-split"></em></li> + <li><span><i class="anticon anticon-message-o" style="margin-right: 8px;"></i> + 2 + </span></li> + </ul> + </div> + <div class="ant-list-item-extra"> + <img width="272" alt="logo" src="https://gw.alipayobjects.com/zos/rmsportal/mqaQswcyDLcXyDKnZfES.png"> + </div> + </div> + </div> + </div> + </div> + <div class="ant-list-pagination"> + <ul unselectable="unselectable" class="ant-pagination"> + <li title="Previous Page" aria-disabled="true" class="ant-pagination-disabled ant-pagination-prev"> + <a class="ant-pagination-item-link"></a> + </li> + <li title="1" tabindex="0" class="ant-pagination-item ant-pagination-item-1 ant-pagination-item-active"> + <a>1</a> + </li> + <li title="2" tabindex="0" class="ant-pagination-item ant-pagination-item-2"> + <a>2</a> + </li> + <li title="3" tabindex="0" class="ant-pagination-item ant-pagination-item-3"> + <a>3</a> + </li> + <li title="4" tabindex="0" class="ant-pagination-item ant-pagination-item-4"> + <a>4</a> + </li> + <li title="5" tabindex="0" class="ant-pagination-item ant-pagination-item-5"> + <a>5</a> + </li> + <li title="6" tabindex="0" class="ant-pagination-item ant-pagination-item-6"> + <a>6</a> + </li> + <li title="7" tabindex="0" class="ant-pagination-item ant-pagination-item-7"> + <a>7</a> + </li> + <li title="8" tabindex="0" class="ant-pagination-item ant-pagination-item-8"> + <a>8</a> + </li> + <li title="Next Page" tabindex="0" class=" ant-pagination-next"> + <a class="ant-pagination-item-link"></a> + </li> + <!----> + </ul> + </div> +</div> +`; diff --git a/components/list/__tests__/demo.test.js b/components/list/__tests__/demo.test.js new file mode 100644 index 000000000..fb75d12a3 --- /dev/null +++ b/components/list/__tests__/demo.test.js @@ -0,0 +1,3 @@ +import demoTest from '../../../tests/shared/demoTest' + +demoTest('list') diff --git a/components/list/demo/basic.md b/components/list/demo/basic.md new file mode 100644 index 000000000..82560b895 --- /dev/null +++ b/components/list/demo/basic.md @@ -0,0 +1,53 @@ +<cn> +#### 基础列表 +基础列表。 +</cn> + +<us> +#### Basic list +Basic list. +</us> + +```html +<template> + <a-list + itemLayout="horizontal" + :dataSource="data" + > + <a-list-item slot="renderItem" slot-scope="item, index"> + <a-list-item-meta + description="Ant Design, a design language for background applications, is refined by Ant UED Team" + > + <a slot="title" href="https://vuecomponent.github.io/ant-design/">{{item.title}}</a> + <a-avatar slot="avatar" src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" /> + </a-list-item-meta> + </a-list-item> + </a-list> +</template> +<script> +const data = [ + { + title: 'Ant Design Title 1', + }, + { + title: 'Ant Design Title 2', + }, + { + title: 'Ant Design Title 3', + }, + { + title: 'Ant Design Title 4', + }, +] +export default { + data () { + return { + data, + } + }, +} +</script> +<style> + +</style> +``` diff --git a/components/list/demo/grid.md b/components/list/demo/grid.md new file mode 100644 index 000000000..6904cfc51 --- /dev/null +++ b/components/list/demo/grid.md @@ -0,0 +1,48 @@ +<cn> +#### 栅格列表 +可以通过设置 `List` 的 `grid` 属性来实现栅格列表,`column` 可设置期望显示的列数。 +</cn> + +<us> +#### Grid +Creating a grid list by setting the `grid` property of List +</us> + +```html +<template> + <a-list + :grid="{ gutter: 16, column: 4 }" + :dataSource="data" + > + <a-list-item slot="renderItem" slot-scope="item, index"> + <a-card :title="item.title">Card content</a-card> + </a-list-item> + </a-list> +</template> +<script> +const data = [ + { + title: 'Title 1', + }, + { + title: 'Title 2', + }, + { + title: 'Title 3', + }, + { + title: 'Title 4', + }, +] +export default { + data () { + return { + data, + } + }, +} +</script> +<style> + +</style> +``` diff --git a/components/list/demo/index.vue b/components/list/demo/index.vue new file mode 100644 index 000000000..043c5e56f --- /dev/null +++ b/components/list/demo/index.vue @@ -0,0 +1,57 @@ +<script> +import Basic from './basic' +import Grid from './grid' +import InfiniteLoad from './infinite-load' +import InfiniteVirtualizedLoad from './infinite-virtualized-load' +import Loadmore from './loadmore' +import Resposive from './resposive' +import Simple from './simple' +import Vertical from './vertical' + +import CN from '../index.zh-CN.md' +import US from '../index.en-US.md' + +const md = { + cn: `# 列表 + 通用列表。 +## 何时使用 +最基础的列表展示,可承载文字、列表、图片、段落,常用于后台数据展示页面。 + ## 代码演示`, + us: `# List + Simple List. + +## When To Use + +A list can be used to display content related to a single subject. The content can consist of multiple elements of varying type and size. + ## Examples + `, +} +export default { + category: 'Components', + type: 'Data Display', + title: 'List', + subtitle: '列表', + cols: 1, + render () { + return ( + <div> + <md cn={md.cn} us={md.us}/> + <Basic /> + <Grid /> + <Loadmore /> + <Resposive /> + <Simple /> + <Vertical /> + <InfiniteLoad /> + <InfiniteVirtualizedLoad /> + <api> + <template slot='cn'> + <CN/> + </template> + <US/> + </api> + </div> + ) + }, +} +</script> diff --git a/components/list/demo/infinite-load.md b/components/list/demo/infinite-load.md new file mode 100644 index 000000000..c680a8ea2 --- /dev/null +++ b/components/list/demo/infinite-load.md @@ -0,0 +1,95 @@ +<cn> +#### 滚动加载 +结合 [vue-infinite-scroll](https://github.com/ElemeFE/vue-infinite-scroll) 实现滚动自动加载列表。 +</cn> + +<us> +#### Scrolling loaded +The example of infinite load with [vue-infinite-scroll](https://github.com/ElemeFE/vue-infinite-scroll). +</us> + +```html +<template> +<div + class="demo-infinite-container" + v-infinite-scroll="handleInfiniteOnLoad" + :infinite-scroll-disabled="busy" + :infinite-scroll-distance="10" +> + <a-list + :dataSource="data" + > + <a-list-item slot="renderItem" slot-scope="item, index"> + <a-list-item-meta :description="item.email"> + <a slot="title" :href="item.href">{{item.name.last}}</a> + <a-avatar slot="avatar" src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" /> + </a-list-item-meta> + <div>Content</div> + </a-list-item> + <a-spin v-if="loading && !busy" class="demo-loading" /> + </a-list> +</div> +</template> +<script> +import reqwest from 'reqwest' +import infiniteScroll from 'vue-infinite-scroll' +const fakeDataUrl = 'https://randomuser.me/api/?results=5&inc=name,gender,email,nat&noinfo' +export default { + directives: { infiniteScroll }, + data () { + return { + data: [], + loading: false, + busy: false, + } + }, + mounted () { + this.getData((res) => { + this.data = res.results + }) + }, + methods: { + getData (callback) { + reqwest({ + url: fakeDataUrl, + type: 'json', + method: 'get', + contentType: 'application/json', + success: (res) => { + callback(res) + }, + }) + }, + handleInfiniteOnLoad () { + const data = this.data + this.loading = true + if (data.length > 14) { + this.$message.warning('Infinite List loaded all') + this.busy = true + this.loading = false + return + } + this.getData((res) => { + this.data = data.concat(res.results) + this.loading = false + }) + }, + }, +} +</script> +<style> +.demo-infinite-container { + border: 1px solid #e8e8e8; + border-radius: 4px; + overflow: auto; + padding: 8px 24px; + height: 300px; +} +.demo-loading { + position: absolute; + bottom: 40px; + width: 100%; + text-align: center; +} +</style> +``` diff --git a/components/list/demo/infinite-virtualized-load.md b/components/list/demo/infinite-virtualized-load.md new file mode 100644 index 000000000..141761665 --- /dev/null +++ b/components/list/demo/infinite-virtualized-load.md @@ -0,0 +1,95 @@ +<cn> +#### 滚动加载无限长列表 +结合 [vue-virtual-scroller](https://github.com/Akryum/vue-virtual-scroller) 实现滚动加载无限长列表,带有虚拟化([virtualization](https://blog.jscrambler.com/optimizing-react-rendering-through-virtualization/))功能,能够提高数据量大时候长列表的性能。 +可以结合 [vue-infinite-scroll](https://github.com/ElemeFE/vue-infinite-scroll) 实现滚动自动加载无限长列表。 +`virtualized` 是在大数据列表中应用的一种技术,主要是为了减少不可见区域不必要的渲染从而提高性能,特别是数据量在成千上万条效果尤为明显。 +</cn> + +<us> +#### Infinite & virtualized +An example of infinite list & virtualized loading using [vue-virtual-scroller](https://github.com/Akryum/vue-virtual-scroller). +`Virtualized` rendering is a technique to mount big sets of data. It reduces the amount of rendered DOM nodes by tracking and hiding whatever isn't currently visible. +</us> + +```html +<template> + <a-list> + <virtual-scroller + style="height: 400px" + :items="data" + item-height="73" + v-infinite-scroll="handleInfiniteOnLoad" + :infinite-scroll-disabled="busy" + :infinite-scroll-distance="10" + > + <a-list-item slot-scope="{item}"> + <a-list-item-meta :description="item.email"> + <a slot="title" :href="item.href">{{item.name.last}}</a> + <a-avatar slot="avatar" src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" /> + </a-list-item-meta> + <div>Content {{item.index}}</div> + </a-list-item> + </virtual-scroller> + <a-spin v-if="loading" class="demo-loading" /> + </a-list> +</template> +<script> +import reqwest from 'reqwest' +import infiniteScroll from 'vue-infinite-scroll' +import { VirtualScroller } from 'vue-virtual-scroller' +const fakeDataUrl = 'https://randomuser.me/api/?results=10&inc=name,gender,email,nat&noinfo' +export default { + directives: { infiniteScroll }, + data () { + return { + data: [], + loading: false, + busy: false, + } + }, + mounted () { + this.getData((res) => { + this.data = res.results.map((item, index) => ({ ...item, index })) + }) + }, + methods: { + getData (callback) { + reqwest({ + url: fakeDataUrl, + type: 'json', + method: 'get', + contentType: 'application/json', + success: (res) => { + callback(res) + }, + }) + }, + handleInfiniteOnLoad () { + const data = this.data + this.loading = true + if (data.length > 100) { + this.$message.warning('Infinite List loaded all') + this.busy = true + this.loading = false + return + } + this.getData((res) => { + this.data = data.concat(res.results).map((item, index) => ({ ...item, index })) + this.loading = false + }) + }, + }, + components: { + 'virtual-scroller': VirtualScroller, + }, +} +</script> +<style> +.demo-loading { + position: absolute; + bottom: 40px; + width: 100%; + text-align: center; +} +</style> +``` diff --git a/components/list/demo/loadmore.md b/components/list/demo/loadmore.md new file mode 100644 index 000000000..672f8ca55 --- /dev/null +++ b/components/list/demo/loadmore.md @@ -0,0 +1,86 @@ +<cn> +#### 加载更多 +可通过 `loadMore` 属性实现加载更多功能。 +</cn> + +<us> +#### Load more +Load more list with `loadMore` property. +</us> + +```html +<template> + <a-list + class="demo-loadmore-list" + :loading="loading" + itemLayout="horizontal" + :dataSource="data" + > + <div v-if="showLoadingMore" slot="loadMore" :style="{ textAlign: 'center', marginTop: '12px', height: '32px', lineHeight: '32px' }"> + <a-spin v-if="loadingMore" /> + <a-button v-else @click="onLoadMore">loading more</a-button> + </div> + <a-list-item slot="renderItem" slot-scope="item, index"> + <a slot="actions">edit</a> + <a slot="actions">more</a> + <a-list-item-meta + description="Ant Design, a design language for background applications, is refined by Ant UED Team" + > + <a slot="title" href="https://vuecomponent.github.io/ant-design/">{{item.name.last}}</a> + <a-avatar slot="avatar" src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" /> + </a-list-item-meta> + <div>content</div> + </a-list-item> + </a-list> +</template> +<script> +import reqwest from 'reqwest' + +const fakeDataUrl = 'https://randomuser.me/api/?results=5&inc=name,gender,email,nat&noinfo' + +export default { + data () { + return { + loading: true, + loadingMore: false, + showLoadingMore: true, + data: [], + } + }, + mounted () { + this.getData((res) => { + this.loading = false + this.data = res.results + }) + }, + methods: { + getData (callback) { + reqwest({ + url: fakeDataUrl, + type: 'json', + method: 'get', + contentType: 'application/json', + success: (res) => { + callback(res) + }, + }) + }, + onLoadMore () { + this.loadingMore = true + this.getData((res) => { + this.data = this.data.concat(res.results) + this.loadingMore = false + this.$nextTick(() => { + window.dispatchEvent(new Event('resize')) + }) + }) + }, + }, +} +</script> +<style> +.demo-loadmore-list { + min-height: 350px; +} +</style> +``` diff --git a/components/list/demo/resposive.md b/components/list/demo/resposive.md new file mode 100644 index 000000000..00a5bdd70 --- /dev/null +++ b/components/list/demo/resposive.md @@ -0,0 +1,55 @@ +<cn> +#### 响应式的栅格列表 +响应式的栅格列表。尺寸与 [Layout Grid](https://vuecomponent.github.io/ant-design/components/grid-cn/#Col) 保持一致。 +</cn> + +<us> +#### Responsive grid list +Responsive grid list. The size property is as same as [Layout Grid](https://vuecomponent.github.io/ant-design/components/grid/#Col). +</us> + +```html +<template> + <a-list + :grid="{ gutter: 16, xs: 1, sm: 2, md: 4, lg: 4, xl: 6, xxl: 3 }" + :dataSource="data" + > + <a-list-item slot="renderItem" slot-scope="item, index"> + <a-card :title="item.title">Card content</a-card> + </a-list-item> + </a-list> +</template> +<script> +const data = [ + { + title: 'Title 1', + }, + { + title: 'Title 2', + }, + { + title: 'Title 3', + }, + { + title: 'Title 4', + }, + { + title: 'Title 5', + }, + { + title: 'Title 6', + }, +] + +export default { + data () { + return { + data, + } + }, +} +</script> +<style> + +</style> +``` diff --git a/components/list/demo/simple.md b/components/list/demo/simple.md new file mode 100644 index 000000000..76324fb9f --- /dev/null +++ b/components/list/demo/simple.md @@ -0,0 +1,69 @@ +<cn> +#### 简单列表 +列表拥有大、中、小三种尺寸。 +通过设置 `size` 为 `large` `small` 分别把按钮设为大、小尺寸。若不设置 `size`,则尺寸为中。 +可通过设置 `header` 和 `footer`,来自定义列表头部和尾部。 +</cn> + +<us> +#### Simple list +Ant Design supports a default list size as well as a large and small size. +If a large or small list is desired, set the size property to either large or small respectively. Omit the size property for a list with the default size. +Customizing the header and footer of list by setting `header` and `footer` property. +</us> + +```html +<template> +<div> + <h3 :style="{ marginBottom: '16px' }">Default Size</h3> + <a-list + bordered + :dataSource="data" + > + <a-list-item slot="renderItem" slot-scope="item, index">{{item}}</a-list-item> + <div slot="header">Header</div> + <div slot="footer">Footer</div> + </a-list> + <h3 :style="{ margin: '16px 0' }">Small Size</h3> + <a-list + size="small" + bordered + :dataSource="data" + > + <a-list-item slot="renderItem" slot-scope="item, index">{{item}}</a-list-item> + <div slot="header">Header</div> + <div slot="footer">Footer</div> + </a-list> + <h3 :style="{ margin: '16px 0' }">Large Size</h3> + <a-list + size="large" + bordered + :dataSource="data" + > + <a-list-item slot="renderItem" slot-scope="item, index">{{item}}</a-list-item> + <div slot="header">Header</div> + <div slot="footer">Footer</div> + </a-list> +</div> +</template> +<script> +const data = [ + 'Racing car sprays burning fuel into crowd.', + 'Japanese princess to wed commoner.', + 'Australian walks 100km after outback crash.', + 'Man charged over missing wedding girl.', + 'Los Angeles battles huge wildfires.', +] + +export default { + data () { + return { + data, + } + }, +} +</script> +<style> + +</style> +``` diff --git a/components/list/demo/vertical.md b/components/list/demo/vertical.md new file mode 100644 index 000000000..c631b2c10 --- /dev/null +++ b/components/list/demo/vertical.md @@ -0,0 +1,73 @@ +<cn> +#### 竖排列表样式 +通过设置 `itemLayout` 属性为 `vertical` 可实现竖排列表样式。 +</cn> + +<us> +#### Vertical +Setting `itemLayout` property with `vertical` to create a vertical list. +</us> + +```html +<template> + <a-list + itemLayout="vertical" + size="large" + :pagination="pagination" + :dataSource="listData" + > + <a-list-item slot="renderItem" slot-scope="item, index" key="item.title"> + <template slot="actions" v-for="{type, text} in actions"> + <span :key="type"> + <a-icon :type="type" style="margin-right: 8px" /> + {{text}} + </span> + </template> + <img slot="extra" width="272" alt="logo" src="https://gw.alipayobjects.com/zos/rmsportal/mqaQswcyDLcXyDKnZfES.png" /> + <a-list-item-meta + :description="item.description" + > + <a slot="title" :href="item.href">{{item.title}}</a> + <a-avatar slot="avatar" :src="item.avatar" /> + </a-list-item-meta> + {{item.content}} + </a-list-item> + </a-list> +</template> +<script> +const listData = [] +for (let i = 0; i < 23; i++) { + listData.push({ + href: 'https://vuecomponent.github.io/ant-design/', + title: `ant design part ${i}`, + avatar: 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png', + description: 'Ant Design, a design language for background applications, is refined by Ant UED Team.', + content: 'We supply a series of design principles, practical patterns and high quality design resources (Sketch and Axure), to help people create their product prototypes beautifully and efficiently.', + }) +} + +const pagination = { + onChange: (page) => { + console.log(page) + }, + pageSize: 3, +} + +export default { + data () { + return { + listData, + pagination, + actions: [ + { type: 'star-o', text: '156' }, + { type: 'like-o', text: '156' }, + { type: 'message-o', text: '2' }, + ], + } + }, +} +</script> +<style> + +</style> +``` diff --git a/components/list/index.en-US.md b/components/list/index.en-US.md new file mode 100644 index 000000000..76b468dbc --- /dev/null +++ b/components/list/index.en-US.md @@ -0,0 +1,46 @@ + +## API + +### List + +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| bordered | Toggles rendering of the border around the list | boolean | false | +| footer | List footer renderer | string\|slot | - | +| grid | The grid type of list. You can set grid to something like {gutter: 16, column: 4} | object | - | +| header | List header renderer | string\|slot | - | +| itemLayout | The layout of list, default is `horizontal`, If a vertical list is desired, set the itemLayout property to `vertical` | string | - | +| loading | Shows a loading indicator while the contents of the list are being fetched | boolean\|[object](https://vuecomponent.github.io/ant-design/components/spin/#API) | false | +| loadMore | Shows a load more content | string\|slot | - | +| pagination | Pagination [config](https://vuecomponent.github.io/ant-design/components/pagination/#API), hide it by setting it to false | boolean \| object | false | +| split | Toggles rendering of the split under the list item | boolean | true | +| renderItem | Custom item renderer, slot="renderItem" and slot-scope="item, index" | (item, index) => vNode | | - | + +### List grid props + +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| column | column of grid | number | - | +| gutter | spacing between grid | number | 0 | +| size | Size of list | `default` \| `middle` \| `small` | `default` | +| xs | `<576px` column of grid | number | - | +| sm | `≥576px` column of grid | number | - | +| md | `≥768px` column of grid | number | - | +| lg | `≥992px` column of grid | number | - | +| xl | `≥1200px` column of grid | number | - | +| xxl | `≥1600px` column of grid | number | - | + +### List.Item + +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| actions | The actions content of list item. If `itemLayout` is `vertical`, shows the content on bottom, otherwise shows content on the far right. | Array\<vNode>\|slot | - | +| extra | The extra content of list item. If `itemLayout` is `vertical`, shows the content on right, otherwise shows content on the far right. | string\|slot | - | + +### List.Item.Meta + +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| avatar | The avatar of list item | slot | - | +| description | The description of list item | string\|slot | - | +| title | The title of list item | string\|slot | - | diff --git a/components/list/index.jsx b/components/list/index.jsx new file mode 100644 index 000000000..7e0c04f14 --- /dev/null +++ b/components/list/index.jsx @@ -0,0 +1,249 @@ +import PropTypes from '../_util/vue-types' +import classNames from 'classnames' +import omit from 'omit.js' +import { SpinProps } from '../spin' +import LocaleReceiver from '../locale-provider/LocaleReceiver' +import defaultLocale from '../locale-provider/default' + +import Spin from '../spin' +import Pagination from '../pagination' +import { Row } from '../grid' + +import Item from './Item' +import { initDefaultProps, getComponentFromProp } from '../_util/props-util' +import { cloneElement } from '../_util/vnode' + +export { ListItemProps, ListItemMetaProps } from './Item' + +export const ColumnCount = ['', 1, 2, 3, 4, 6, 8, 12, 24] + +export const ColumnType = ['gutter', 'column', 'xs', 'sm', 'md', 'lg', 'xl', 'xxl'] + +export const ListGridType = { + gutter: PropTypes.number, + column: PropTypes.oneOf(ColumnCount), + xs: PropTypes.oneOf(ColumnCount), + sm: PropTypes.oneOf(ColumnCount), + md: PropTypes.oneOf(ColumnCount), + lg: PropTypes.oneOf(ColumnCount), + xl: PropTypes.oneOf(ColumnCount), + xxl: PropTypes.oneOf(ColumnCount), +} + +export const ListSize = ['small', 'default', 'large'] + +export const ListProps = () => ({ + bordered: PropTypes.bool, + dataSource: PropTypes.any, + extra: PropTypes.any, + grid: PropTypes.shape(ListGridType).loose, + itemLayout: PropTypes.string, + loading: PropTypes.oneOfType([PropTypes.bool, SpinProps()]), + loadMore: PropTypes.any, + pagination: PropTypes.any, + prefixCls: PropTypes.string, + rowKey: PropTypes.any, + renderItem: PropTypes.any, + size: PropTypes.oneOf(ListSize), + split: PropTypes.bool, + header: PropTypes.any, + footer: PropTypes.any, + locale: PropTypes.object, +}) + +export default { + Item, + name: 'AList', + + props: initDefaultProps(ListProps(), { + dataSource: [], + prefixCls: 'ant-list', + bordered: false, + split: true, + loading: false, + pagination: false, + }), + provide () { + return { + listContext: this, + } + }, + data () { + this.keys = [] + this.defaultPaginationProps = { + current: 1, + pageSize: 10, + onChange: (page, pageSize) => { + const { pagination } = this + this.paginationCurrent = page + if (pagination && pagination.onChange) { + pagination.onChange(page, pageSize) + } + }, + total: 0, + } + return { + paginationCurrent: 1, + } + }, + methods: { + renderItem2 (item, index) { + const { dataSource, $scopedSlots, rowKey } = this + let key + const renderItem = this.renderItem || $scopedSlots.renderItem + if (typeof rowKey === 'function') { + key = rowKey(dataSource[index]) + } else if (typeof rowKey === 'string') { + key = dataSource[rowKey] + } else { + key = dataSource.key + } + + if (!key) { + key = `list-item-${index}` + } + + this.keys[index] = key + + return renderItem(item, index) + }, + + isSomethingAfterLastTtem () { + const { pagination } = this + const loadMore = getComponentFromProp(this, 'loadMore') + const footer = getComponentFromProp(this, 'footer') + return !!(loadMore || pagination || footer) + }, + + renderEmpty (contextLocale) { + const locale = { ...contextLocale, ...this.locale } + return <div class={`${this.prefixCls}-empty-text`}>{locale.emptyText}</div> + }, + }, + + render () { + const { + bordered, + split, + itemLayout, + pagination, + prefixCls, + grid, + dataSource, + size, + loading, + $listeners, + $slots, + paginationCurrent, + } = this + const loadMore = getComponentFromProp(this, 'loadMore') + const footer = getComponentFromProp(this, 'footer') + const header = getComponentFromProp(this, 'header') + const children = $slots.default || [] + let loadingProp = loading + if (typeof loadingProp === 'boolean') { + loadingProp = { + spinning: loadingProp, + } + } + const isLoading = (loadingProp && loadingProp.spinning) + + // large => lg + // small => sm + let sizeCls = '' + switch (size) { + case 'large': + sizeCls = 'lg' + break + case 'small': + sizeCls = 'sm' + break + default: + break + } + const classString = classNames(prefixCls, { + [`${prefixCls}-vertical`]: itemLayout === 'vertical', + [`${prefixCls}-${sizeCls}`]: sizeCls, + [`${prefixCls}-split`]: split, + [`${prefixCls}-bordered`]: bordered, + [`${prefixCls}-loading`]: isLoading, + [`${prefixCls}-grid`]: grid, + [`${prefixCls}-something-after-last-item`]: this.isSomethingAfterLastTtem(), + }) + const paginationProps = { + ...this.defaultPaginationProps, + total: dataSource.length, + current: paginationCurrent, + ...pagination || {}, + } + const largestPage = Math.ceil( + paginationProps.total / paginationProps.pageSize, + ) + if (paginationProps.current > largestPage) { + paginationProps.current = largestPage + } + const { class: cls, style, onShowSizeChange = () => {}, ...restProps } = paginationProps + const paginationContent = pagination ? ( + <div class={`${prefixCls}-pagination`}> + <Pagination + {...{ + props: omit(restProps, ['onChange']), + class: cls, style, + on: { change: this.defaultPaginationProps.onChange, showSizeChange: onShowSizeChange }, + }} + /> + </div> + ) : null + + let splitDataSource = [...dataSource] + if (pagination) { + if ( + dataSource.length > + (paginationProps.current - 1) * paginationProps.pageSize + ) { + splitDataSource = [...dataSource].splice( + (paginationProps.current - 1) * paginationProps.pageSize, + paginationProps.pageSize, + ) + } + } + + let childrenContent + childrenContent = isLoading && (<div style={{ minHeight: 53 }} />) + if (splitDataSource.length > 0) { + const items = splitDataSource.map((item, index) => this.renderItem2(item, index)) + const childrenList = items.map((child, index) => cloneElement(child, { + key: this.keys[index], + }), + ) + + childrenContent = grid ? ( + <Row gutter={grid.gutter}>{childrenList}</Row> + ) : childrenList + } else if (!children && !isLoading) { + childrenContent = ( + <LocaleReceiver + componentName='Table' + defaultLocale={defaultLocale.Table} + scopedSlots={ + { default: this.renderEmpty } + } + /> + ) + } + const paginationPosition = paginationProps.position || 'bottom' + + return ( + <div class={classString} {...{ on: $listeners }}> + {(paginationPosition === 'top' || paginationPosition === 'both') && paginationContent} + {header && <div class={`${prefixCls}-header`}>{header}</div>} + <Spin {...{ props: loadingProp }}> + {childrenContent} + {children} + </Spin> + {footer && <div class={`${prefixCls}-footer`}>{footer}</div>} + {loadMore || (paginationPosition === 'bottom' || paginationPosition === 'both') && paginationContent} + </div> + ) + }, +} diff --git a/components/list/index.zh-CN.md b/components/list/index.zh-CN.md new file mode 100644 index 000000000..e79d29fa7 --- /dev/null +++ b/components/list/index.zh-CN.md @@ -0,0 +1,46 @@ + +## API + +### List + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| bordered | 是否展示边框 | boolean | false | +| footer | 列表底部 | string\|slot | - | +| grid | 列表栅格配置 | object | - | +| header | 列表头部 | string\|slot | - | +| itemLayout | 设置 `List.Item` 布局, 设置成 `vertical` 则竖直样式显示, 默认横排 | string | - | +| loading | 当卡片内容还在加载中时,可以用 `loading` 展示一个占位 | boolean\|[object](https://vuecomponent.github.io/ant-design/components/spin-cn/#API) | false | +| loadMore | 加载更多 | string\|slot | - | +| pagination | 对应的 `pagination` [配置]((https://vuecomponent.github.io/ant-design/components/pagination-cn/#API)), 设置 `false` 不显示 | boolean\|object | false | +| size | list 的尺寸 | `default` \| `middle` \| `small` | `default` | +| split | 是否展示分割线 | boolean | true | +| renderItem | 自定义`Item`函数,也可使用slot="renderItem" 和 slot-scope="item, index" | (item, index) => vNode | | - | + +### List grid props + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| column | 列数 | number | - | +| gutter | 栅格间隔 | number | 0 | +| xs | `<576px` 展示的列数 | number | - | +| sm | `≥576px` 展示的列数 | number | - | +| md | `≥768px` 展示的列数 | number | - | +| lg | `≥992px` 展示的列数 | number | - | +| xl | `≥1200px` 展示的列数 | number | - | +| xxl | `≥1600px` 展示的列数 | number | - | + +### List.Item + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| actions | 列表操作组,根据 `itemLayout` 的不同, 位置在卡片底部或者最右侧 | Array\<vNode>/|slot | - | +| extra | 额外内容, 通常用在 `itemLayout` 为 `vertical` 的情况下, 展示右侧内容; `horizontal` 展示在列表元素最右侧 | string\|slot | - | + +### List.Item.Meta + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| avatar | 列表元素的图标 | slot | - | +| description | 列表元素的描述内容 | string\|slot | - | +| title | 列表元素的标题 | string\|slot | - | diff --git a/components/list/style/bordered.less b/components/list/style/bordered.less new file mode 100644 index 000000000..e3175b8a6 --- /dev/null +++ b/components/list/style/bordered.less @@ -0,0 +1,41 @@ +.@{list-prefix-cls}-bordered { + border-radius: @border-radius-base; + border: 1px solid @border-color-base; + .@{list-prefix-cls}-header { + padding-left: 24px; + padding-right: 24px; + } + + .@{list-prefix-cls}-footer { + padding-left: 24px; + padding-right: 24px; + } + + .@{list-prefix-cls}-item { + border-bottom: 1px solid @border-color-split; + padding-left: 24px; + padding-right: 24px; + } + + .@{list-prefix-cls}-pagination { + margin: 16px 24px; + } + + &.@{list-prefix-cls}-sm { + .@{list-prefix-cls}-item { + padding-left: 16px; + padding-right: 16px; + } + .@{list-prefix-cls}-header, + .@{list-prefix-cls}-footer { + padding: 8px 16px; + } + } + + &.@{list-prefix-cls}-lg { + .@{list-prefix-cls}-header, + .@{list-prefix-cls}-footer { + padding: 16px 24px; + } + } +} diff --git a/components/list/style/index.js b/components/list/style/index.js new file mode 100644 index 000000000..c76d8c508 --- /dev/null +++ b/components/list/style/index.js @@ -0,0 +1,7 @@ +import '../../style/index.less' +import './index.less' + +// style dependencies +import '../../spin/style' +import '../../pagination/style' +import '../../grid/style' diff --git a/components/list/style/index.less b/components/list/style/index.less new file mode 100644 index 000000000..11e55b6c9 --- /dev/null +++ b/components/list/style/index.less @@ -0,0 +1,207 @@ +@import "../../style/themes/default"; +@import "../../style/mixins/index"; + +@list-prefix-cls: ~"@{ant-prefix}-list"; + +.@{list-prefix-cls} { + .reset-component; + position: relative; + * { + outline: none; + } + &-pagination { + margin-top: 24px; + text-align: right; + } + &-more { + margin-top: 12px; + text-align: center; + button { + padding-left: 32px; + padding-right: 32px; + } + } + &-spin { + text-align: center; + min-height: 40px; + } + &-empty-text { + color: @text-color-secondary; + font-size: @font-size-base; + padding: 16px; + text-align: center; + } + &-item { + align-items: center; + display: flex; + padding-top: 12px; + padding-bottom: 12px; + &-meta { + align-items: flex-start; + display: flex; + flex: 1; + font-size: 0; + &-avatar { + margin-right: 16px; + } + &-content { + flex: 1 0; + } + &-title { + color: @text-color; + margin-bottom: 4px; + font-size: @font-size-base; + line-height: 22px; + > a { + color: @text-color; + transition: all .3s; + &:hover { + color: @primary-color; + } + } + } + &-description { + color: @text-color-secondary; + font-size: @font-size-base; + line-height: 22px; + } + } + &-content { + display: flex; + flex: 1; + justify-content: flex-end; + } + &-content-single { + justify-content: flex-start; + } + &-action { + font-size: 0; + flex: 0 0 auto; + margin-left: 48px; + padding: 0; + list-style: none; + & > li { + display: inline-block; + color: @text-color-secondary; + cursor: pointer; + padding: 0 8px; + position: relative; + font-size: @font-size-base; + line-height: 22px; + text-align: center; + } + & > li:first-child { + padding-left: 0; + } + &-split { + background-color: @border-color-split; + margin-top: -7px; + position: absolute; + top: 50%; + right: 0; + width: 1px; + height: 14px; + } + } + &-main { + display: flex; + flex: 1; + } + } + + &-header, + &-footer { + padding-top: 12px; + padding-bottom: 12px; + } + + &-empty { + color: @text-color-secondary; + padding: 16px 0; + font-size: 12px; + text-align: center; + } + + &-split &-item { + border-bottom: 1px solid @border-color-split; + &:last-child { + border-bottom: none; + } + } + + &-split &-header { + border-bottom: 1px solid @border-color-split; + } + + &-loading &-spin-nested-loading { + min-height: 32px; + } + + &-something-after-last-item &-item:last-child { + border-bottom: 1px solid @border-color-split; + } + + &-lg &-item { + padding-top: 16px; + padding-bottom: 16px; + } + + &-sm &-item { + padding-top: 8px; + padding-bottom: 8px; + } + + &-vertical &-item { + display: block; + &-extra-wrap { + display: flex; + } + &-main { + display: block; + flex: 1; + } + &-extra { + margin-left: 58px; + } + &-meta { + margin-bottom: 16px; + &-avatar { + display: none; + } + &-title { + color: @heading-color; + margin-bottom: 12px; + font-size: @font-size-lg; + line-height: 24px; + } + } + &-content { + display: block; + color: @text-color; + font-size: @font-size-base; + margin-bottom: 16px; + } + &-action { + margin-left: auto; + > li { + padding: 0 16px; + &:first-child { + padding-left: 0; + } + } + } + } + + &-grid &-item { + border-bottom: none; + padding-top: 0; + padding-bottom: 0; + margin-bottom: 20px; + &-content { + display: block; + } + } +} + +@import './bordered'; +@import './responsive'; diff --git a/components/list/style/responsive.less b/components/list/style/responsive.less new file mode 100644 index 000000000..a1eba05ca --- /dev/null +++ b/components/list/style/responsive.less @@ -0,0 +1,42 @@ +@media screen and (max-width: @screen-md) { + .@{list-prefix-cls} { + &-item { + &-action { + margin-left: 24px; + } + } + } + + .@{list-prefix-cls}-vertical { + .@{list-prefix-cls}-item { + &-extra { + margin-left: 24px; + } + } + } +} + +@media screen and (max-width: @screen-xs) { + .@{list-prefix-cls} { + &-item { + flex-wrap: wrap; + &-action { + margin-left: 12px; + } + } + } + + .@{list-prefix-cls}-vertical { + .@{list-prefix-cls}-item { + &-extra-wrap { + flex-wrap: wrap-reverse; + } + &-main { + min-width: 220px; + } + &-extra { + margin-left: 0; + } + } + } +} diff --git a/components/style.js b/components/style.js index b2e783035..640ee4c18 100644 --- a/components/style.js +++ b/components/style.js @@ -45,3 +45,4 @@ import './upload/style' import './layout/style' import './form/style' import './anchor/style' +import './list/style' diff --git a/site/components.js b/site/components.js index 6422abf9b..8b262e351 100644 --- a/site/components.js +++ b/site/components.js @@ -24,7 +24,7 @@ import { Input, InputNumber, Layout, - // List, + List, LocaleProvider, message, Menu, @@ -100,7 +100,9 @@ Vue.component(Layout.Header.name, Layout.Header) Vue.component(Layout.Footer.name, Layout.Footer) Vue.component(Layout.Sider.name, Layout.Sider) Vue.component(Layout.Content.name, Layout.Content) -// Vue.component(List.name, List) +Vue.component(List.name, List) +Vue.component(List.Item.name, List.Item) +Vue.component(List.Item.Meta.name, List.Item.Meta) Vue.component(LocaleProvider.name, LocaleProvider) Vue.component(Menu.name, Menu) Vue.component(Menu.Item.name, Menu.Item) diff --git a/site/demo.js b/site/demo.js index 23694d32d..3e028ce0f 100644 --- a/site/demo.js +++ b/site/demo.js @@ -46,3 +46,4 @@ export { default as tree } from 'antd/tree/demo/index.vue' export { default as layout } from 'antd/layout/demo/index.vue' export { default as form } from 'antd/form/demo/index.vue' export { default as anchor } from 'antd/anchor/demo/index.vue' +export { default as list } from 'antd/list/demo/index.vue' diff --git a/tests/__snapshots__/index.test.js.snap b/tests/__snapshots__/index.test.js.snap index 9adcc0cc1..be8c0de07 100644 --- a/tests/__snapshots__/index.test.js.snap +++ b/tests/__snapshots__/index.test.js.snap @@ -29,6 +29,7 @@ Array [ "Input", "InputNumber", "Layout", + "List", "LocaleProvider", "Menu", "Modal",