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 = ( +
+ {title &&

{title}

} + {description &&
{description}
} +
+ ) + return ( +
+ {avatar &&
{avatar}
} + {(title || description) && content} +
+ ) + }, + +} + +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 ? ( +
+ {otherContent} +
) : null + + let actionsContent + if (actions && actions.length > 0) { + const actionsContentItem = (action, i) => ( +
  • + {action} + {i !== (actions.length - 1) && } +
  • + ) + actionsContent = ( + + ) + } + + const extraContent = ( +
    +
    + {metaContent} + {content} + {actionsContent} +
    +
    {extra}
    +
    + ) + + const mainContent = grid ? ( + +
    + {extra && extraContent} + {!extra && metaContent} + {!extra && content} + {!extra && actionsContent} +
    + + ) : ( +
    + {extra && extraContent} + {!extra && metaContent} + {!extra && content} + {!extra && actionsContent} +
    + ) + + 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`] = ` +
    +
    +
    +
    +
    +
    +
    +

    + Ant Design Title 1 +

    +
    Ant Design, a design language for background applications, is refined by Ant UED Team
    +
    +
    +
    +
    +
    +
    +
    +

    + Ant Design Title 2 +

    +
    Ant Design, a design language for background applications, is refined by Ant UED Team
    +
    +
    +
    +
    +
    +
    +
    +

    + Ant Design Title 3 +

    +
    Ant Design, a design language for background applications, is refined by Ant UED Team
    +
    +
    +
    +
    +
    +
    +
    +

    + Ant Design Title 4 +

    +
    Ant Design, a design language for background applications, is refined by Ant UED Team
    +
    +
    +
    +
    +
    +
    +`; + +exports[`renders ./components/list/demo/grid.md correctly 1`] = ` +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    Title 1
    +
    +
    +
    Card content
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    Title 2
    +
    +
    +
    Card content
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    Title 3
    +
    +
    +
    Card content
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    Title 4
    +
    +
    +
    Card content
    +
    +
    +
    +
    +
    +
    +
    +
    +`; + +exports[`renders ./components/list/demo/infinite-load.md correctly 1`] = ` +
    +
    +
    +
    +
    +`; + +exports[`renders ./components/list/demo/infinite-virtualized-load.md correctly 1`] = ` +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +`; + +exports[`renders ./components/list/demo/loadmore.md correctly 1`] = ` +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +`; + +exports[`renders ./components/list/demo/resposive.md correctly 1`] = ` +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    Title 1
    +
    +
    +
    Card content
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    Title 2
    +
    +
    +
    Card content
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    Title 3
    +
    +
    +
    Card content
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    Title 4
    +
    +
    +
    Card content
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    Title 5
    +
    +
    +
    Card content
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    Title 6
    +
    +
    +
    Card content
    +
    +
    +
    +
    +
    +
    +
    +
    +`; + +exports[`renders ./components/list/demo/simple.md correctly 1`] = ` +
    +

    Default Size

    +
    +
    +
    Header
    +
    +
    +
    +
    +
    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.
    +
    +
    +
    + +
    +

    Small Size

    +
    +
    +
    Header
    +
    +
    +
    +
    +
    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.
    +
    +
    +
    + +
    +

    Large Size

    +
    +
    +
    Header
    +
    +
    +
    +
    +
    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.
    +
    +
    +
    + +
    +
    +`; + +exports[`renders ./components/list/demo/vertical.md correctly 1`] = ` +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + ant design part 0 +

    +
    Ant Design, a design language for background applications, is refined by Ant UED Team.
    +
    +
    +
    + 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. +
    +
      +
    • + 156 +
    • +
    • + 156 +
    • +
    • + 2 +
    • +
    +
    +
    + logo +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + ant design part 1 +

    +
    Ant Design, a design language for background applications, is refined by Ant UED Team.
    +
    +
    +
    + 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. +
    +
      +
    • + 156 +
    • +
    • + 156 +
    • +
    • + 2 +
    • +
    +
    +
    + logo +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + ant design part 2 +

    +
    Ant Design, a design language for background applications, is refined by Ant UED Team.
    +
    +
    +
    + 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. +
    +
      +
    • + 156 +
    • +
    • + 156 +
    • +
    • + 2 +
    • +
    +
    +
    + logo +
    +
    +
    +
    +
    +
    +
      +
    • + +
    • +
    • + 1 +
    • +
    • + 2 +
    • +
    • + 3 +
    • +
    • + 4 +
    • +
    • + 5 +
    • +
    • + 6 +
    • +
    • + 7 +
    • +
    • + 8 +
    • +
    • + +
    • + +
    +
    +
    +`; 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 @@ + +#### 基础列表 +基础列表。 + + + +#### Basic list +Basic list. + + +```html + + + +``` 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 @@ + +#### 栅格列表 +可以通过设置 `List` 的 `grid` 属性来实现栅格列表,`column` 可设置期望显示的列数。 + + + +#### Grid +Creating a grid list by setting the `grid` property of List + + +```html + + + +``` 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 @@ + 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 @@ + +#### 滚动加载 +结合 [vue-infinite-scroll](https://github.com/ElemeFE/vue-infinite-scroll) 实现滚动自动加载列表。 + + + +#### Scrolling loaded +The example of infinite load with [vue-infinite-scroll](https://github.com/ElemeFE/vue-infinite-scroll). + + +```html + + + +``` 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 @@ + +#### 滚动加载无限长列表 +结合 [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` 是在大数据列表中应用的一种技术,主要是为了减少不可见区域不必要的渲染从而提高性能,特别是数据量在成千上万条效果尤为明显。 + + + +#### 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. + + +```html + + + +``` 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 @@ + +#### 加载更多 +可通过 `loadMore` 属性实现加载更多功能。 + + + +#### Load more +Load more list with `loadMore` property. + + +```html + + + +``` 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 @@ + +#### 响应式的栅格列表 +响应式的栅格列表。尺寸与 [Layout Grid](https://vuecomponent.github.io/ant-design/components/grid-cn/#Col) 保持一致。 + + + +#### Responsive grid list +Responsive grid list. The size property is as same as [Layout Grid](https://vuecomponent.github.io/ant-design/components/grid/#Col). + + +```html + + + +``` 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 @@ + +#### 简单列表 +列表拥有大、中、小三种尺寸。 +通过设置 `size` 为 `large` `small` 分别把按钮设为大、小尺寸。若不设置 `size`,则尺寸为中。 +可通过设置 `header` 和 `footer`,来自定义列表头部和尾部。 + + + +#### 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. + + +```html + + + +``` 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 @@ + +#### 竖排列表样式 +通过设置 `itemLayout` 属性为 `vertical` 可实现竖排列表样式。 + + + +#### Vertical +Setting `itemLayout` property with `vertical` to create a vertical list. + + +```html + + + +``` 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\\|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
    {locale.emptyText}
    + }, + }, + + 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 ? ( +
    + +
    + ) : 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 && (
    ) + 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 ? ( + {childrenList} + ) : childrenList + } else if (!children && !isLoading) { + childrenContent = ( + + ) + } + const paginationPosition = paginationProps.position || 'bottom' + + return ( +
    + {(paginationPosition === 'top' || paginationPosition === 'both') && paginationContent} + {header &&
    {header}
    } + + {childrenContent} + {children} + + {footer &&
    {footer}
    } + {loadMore || (paginationPosition === 'bottom' || paginationPosition === 'both') && paginationContent} +
    + ) + }, +} 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\/|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",