From 22141e74de8b6a6d431ba51e23282355cca762fb Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Wed, 23 Jun 2021 14:44:21 +0800 Subject: [PATCH] refactor: list (#4242) --- components/_util/hooks/useConfigInject.ts | 5 +- components/list/Item.tsx | 200 ++++----- components/list/ItemMeta.tsx | 43 ++ components/list/index.tsx | 483 ++++++++++++---------- components/list/style/bordered.less | 27 +- components/list/style/customize.less | 12 + components/list/style/index.less | 58 +-- components/list/style/rtl.less | 139 +++++++ components/pagination/Pagination.tsx | 13 +- components/pagination/index.ts | 2 +- components/style/themes/default.less | 4 + components/table/interface.ts | 4 +- 12 files changed, 591 insertions(+), 399 deletions(-) create mode 100644 components/list/ItemMeta.tsx create mode 100644 components/list/style/customize.less create mode 100644 components/list/style/rtl.less diff --git a/components/_util/hooks/useConfigInject.ts b/components/_util/hooks/useConfigInject.ts index 78c1e3b4f..7099b5b56 100644 --- a/components/_util/hooks/useConfigInject.ts +++ b/components/_util/hooks/useConfigInject.ts @@ -1,5 +1,5 @@ import { RequiredMark } from '../../form/Form'; -import { computed, ComputedRef, inject, UnwrapRef } from 'vue'; +import { computed, ComputedRef, inject, UnwrapRef, VNodeChild } from 'vue'; import { ConfigProviderProps, defaultConfigProvider, @@ -21,6 +21,7 @@ export default ( form?: ComputedRef<{ requiredMark?: RequiredMark; }>; + renderEmpty?: ComputedRef<(componentName?: string) => VNodeChild | JSX.Element>; } => { const configProvider = inject>( 'configProvider', @@ -28,6 +29,7 @@ export default ( ); const prefixCls = computed(() => configProvider.getPrefixCls(name, props.prefixCls)); const direction = computed(() => configProvider.direction); + const renderEmpty = computed(() => configProvider.renderEmpty); const space = computed(() => configProvider.space); const pageHeader = computed(() => configProvider.pageHeader); const form = computed(() => configProvider.form); @@ -42,5 +44,6 @@ export default ( space, pageHeader, form, + renderEmpty, }; }; diff --git a/components/list/Item.tsx b/components/list/Item.tsx index a446a32c5..d6092df08 100644 --- a/components/list/Item.tsx +++ b/components/list/Item.tsx @@ -1,80 +1,36 @@ import PropTypes from '../_util/vue-types'; import classNames from '../_util/classNames'; -import { getComponent, isStringElement, isEmptyElement, getSlot } from '../_util/props-util'; +import { isStringElement, isEmptyElement, flattenChildren } from '../_util/props-util'; import { Col } from '../grid'; -import { defaultConfigProvider } from '../config-provider'; import { cloneElement } from '../_util/vnode'; -import { defineComponent, ExtractPropTypes, FunctionalComponent, inject } from 'vue'; +import { defineComponent, inject, ref } from 'vue'; +import ItemMeta from './ItemMeta'; +import useConfigInject from '../_util/hooks/useConfigInject'; +import { ListContextKey } from '.'; export const ListItemProps = { prefixCls: PropTypes.string, extra: PropTypes.any, actions: PropTypes.array, grid: PropTypes.any, + colStyle: PropTypes.style, }; -export const ListItemMetaProps = { - avatar: PropTypes.any, - description: PropTypes.any, - prefixCls: PropTypes.string, - title: PropTypes.any, -}; - -export const ListItemMeta: FunctionalComponent ->> = (props, { slots }) => { - const configProvider = inject('configProvider', defaultConfigProvider); - const { getPrefixCls } = configProvider; - const { prefixCls: customizePrefixCls } = props; - const prefixCls = getPrefixCls('list', customizePrefixCls); - const avatar = props.avatar || slots.avatar?.(); - const title = props.title || slots.title?.(); - const description = props.description || slots.description?.(); - const content = ( -
- {title &&

{title}

} - {description &&
{description}
} -
- ); - return ( -
- {avatar &&
{avatar}
} - {(title || description) && content} -
- ); -}; - -Object.assign(ListItemMeta, { - props: ListItemMetaProps, - __ANT_LIST_ITEM_META: true, - displayName: 'AListItemMeta', -}); - -function getGrid(grid, t) { - return grid[t] && Math.floor(24 / grid[t]); -} - -export interface ListContext { - grid?: any; - itemLayout?: string; -} - export default defineComponent({ name: 'AListItem', inheritAttrs: false, - Meta: ListItemMeta, + Meta: ItemMeta, props: ListItemProps, - setup() { - const listContext = inject('listContext', {}); - const configProvider = inject('configProvider', defaultConfigProvider); - return { - listContext, - configProvider, - }; - }, - methods: { - isItemContainsTextNodeAndNotSingular() { - const children = getSlot(this) || []; + slots: ['actions', 'extra'], + setup(props, { slots, attrs }) { + const { itemLayout, grid } = inject(ListContextKey, { + grid: ref(), + itemLayout: ref(), + }); + const { prefixCls } = useConfigInject('list', props); + + const isItemContainsTextNodeAndNotSingular = () => { + const children = slots.default?.() || []; let result; children.forEach(element => { if (isStringElement(element) && !isEmptyElement(element)) { @@ -82,75 +38,65 @@ export default defineComponent({ } }); return result && children.length > 1; - }, + }; - isFlexMode() { - const extra = getComponent(this, 'extra'); - const { itemLayout } = this.listContext; - if (itemLayout === 'vertical') { + const isFlexMode = () => { + const extra = props.extra ?? slots.extra?.(); + if (itemLayout.value === 'vertical') { return !!extra; } - return !this.isItemContainsTextNodeAndNotSingular(); - }, - }, - render() { - const { grid, itemLayout } = this.listContext; - const { prefixCls: customizePrefixCls, $attrs } = this; - const { class: _className, ...restAttrs } = $attrs; - const getPrefixCls = this.configProvider.getPrefixCls; - const prefixCls = getPrefixCls('list', customizePrefixCls); - const extra = getComponent(this, 'extra'); - let actions = getComponent(this, 'actions'); - actions = actions && !Array.isArray(actions) ? [actions] : actions; - const actionsContent = actions && actions.length > 0 && ( -
    - {actions.map((action, i) => ( -
  • - {action} - {i !== actions.length - 1 && } -
  • - ))} -
- ); - const children = getSlot(this); - const Tag = grid ? 'div' : 'li'; - const itemChildren = ( - - {itemLayout === 'vertical' && extra - ? [ -
- {children} - {actionsContent} -
, -
- {extra} -
, - ] - : [children, actionsContent, cloneElement(extra, { key: 'extra' })]} -
- ); + return !isItemContainsTextNodeAndNotSingular(); + }; - const mainContent = grid ? ( - - {itemChildren} - - ) : ( - itemChildren - ); - - return mainContent; + return () => { + const { class: className, ...restAttrs } = attrs; + const pre = prefixCls.value; + const extra = props.extra ?? slots.extra?.(); + const children = slots.default?.(); + let actions = props.actions ?? flattenChildren(slots.actions?.()); + actions = actions && !Array.isArray(actions) ? [actions] : actions; + const actionsContent = actions && actions.length > 0 && ( +
    + {actions.map((action, i) => ( +
  • + {action} + {i !== actions.length - 1 && } +
  • + ))} +
+ ); + const Element = grid.value ? 'div' : 'li'; + const itemChildren = ( + + {itemLayout.value === 'vertical' && extra + ? [ +
+ {children} + {actionsContent} +
, +
+ {extra} +
, + ] + : [children, actionsContent, cloneElement(extra, { key: 'extra' })]} +
+ ); + return grid.value ? ( + + {itemChildren} + + ) : ( + itemChildren + ); + }; }, }); diff --git a/components/list/ItemMeta.tsx b/components/list/ItemMeta.tsx new file mode 100644 index 000000000..5c013adb7 --- /dev/null +++ b/components/list/ItemMeta.tsx @@ -0,0 +1,43 @@ +import { defineComponent, ExtractPropTypes } from 'vue'; +import useConfigInject from '../_util/hooks/useConfigInject'; +import PropTypes from '../_util/vue-types'; + +export const listItemMetaProps = { + avatar: PropTypes.any, + description: PropTypes.any, + prefixCls: PropTypes.string, + title: PropTypes.any, +}; + +export type ListItemMetaProps = Partial>; + +export default defineComponent({ + name: 'AListItemMeta', + props: listItemMetaProps, + displayName: 'AListItemMeta', // 兼容历史函数式组件 + __ANT_LIST_ITEM_META: true, + slots: ['avatar', 'description', 'title'], + setup(props, { slots }) { + const { prefixCls } = useConfigInject('list', props); + return () => { + const classString = `${prefixCls.value}-item-meta`; + const title = props.title ?? slots.title?.(); + const description = props.description ?? slots.description?.(); + const avatar = props.avatar ?? slots.avatar?.(); + const content = ( +
+ {title &&

{title}

} + {description && ( +
{description}
+ )} +
+ ); + return ( +
+ {avatar &&
{avatar}
} + {(title || description) && content} +
+ ); + }; + }, +}); diff --git a/components/list/index.tsx b/components/list/index.tsx index cc597a43c..00eaed2e9 100644 --- a/components/list/index.tsx +++ b/components/list/index.tsx @@ -1,51 +1,65 @@ -import { provide, inject, defineComponent, App, Plugin, ExtractPropTypes } from 'vue'; -import omit from 'omit.js'; +import { + provide, + defineComponent, + App, + Plugin, + ExtractPropTypes, + PropType, + ref, + watch, + computed, + InjectionKey, + toRef, + Ref, +} from 'vue'; import PropTypes, { withUndefined } from '../_util/vue-types'; -import classNames from '../_util/classNames'; -import { defaultConfigProvider } from '../config-provider'; +import { RenderEmptyHandler } from '../config-provider'; import Spin from '../spin'; -import Pagination, { PaginationConfig } from '../pagination'; +import Pagination, { PaginationConfig, paginationConfig } from '../pagination'; import { Row } from '../grid'; -import Item, { ListItemMeta } from './Item'; -import { getComponent, getSlot } from '../_util/props-util'; +import Item from './Item'; +import { flattenChildren } from '../_util/props-util'; import initDefaultProps from '../_util/props-util/initDefaultProps'; -import { cloneElement } from '../_util/vnode'; import { tuple } from '../_util/type'; +import ItemMeta from './ItemMeta'; +import useConfigInject from '../_util/hooks/useConfigInject'; +import useBreakpoint from '../_util/hooks/useBreakpoint'; +import { Breakpoint, responsiveArray } from '../_util/responsiveObserve'; -export { ListItemProps, ListItemMetaProps, ListItemMeta } from './Item'; +export { ListItemProps } from './Item'; +export { ListItemMetaProps } from './ItemMeta'; +export const ListItemMeta = ItemMeta; -export const ColumnCount = ['', 1, 2, 3, 4, 6, 8, 12, 24]; - -export const ColumnType = ['gutter', 'column', 'xs', 'sm', 'md', 'lg', 'xl', 'xxl']; +export type 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), + column: PropTypes.number, + xs: PropTypes.number, + sm: PropTypes.number, + md: PropTypes.number, + lg: PropTypes.number, + xl: PropTypes.number, + xxl: PropTypes.number, }; export const ListSize = tuple('small', 'default', 'large'); -const paginationProps = PaginationConfig(); +export type ListItemLayout = 'horizontal' | 'vertical'; -export const ListProps = () => ({ +export const listProps = { bordered: PropTypes.looseBool, dataSource: PropTypes.array, extra: PropTypes.any, grid: PropTypes.shape(ListGridType).loose, - itemLayout: PropTypes.string, + itemLayout: PropTypes.oneOf(tuple('horizontal', 'vertical')), loading: withUndefined(PropTypes.oneOfType([PropTypes.looseBool, PropTypes.object])), loadMore: PropTypes.any, pagination: withUndefined( PropTypes.oneOfType([ - PropTypes.shape>>(paginationProps).loose, + PropTypes.shape(paginationConfig()).loose, PropTypes.looseBool, ]), ), @@ -56,79 +70,186 @@ export const ListProps = () => ({ split: PropTypes.looseBool, header: PropTypes.any, footer: PropTypes.any, - locale: PropTypes.object, -}); + locale: { + type: Object as PropType, + }, +}; + +export interface ListLocale { + emptyText: any; +} + +export type ListProps = Partial>; + +export interface ListContext { + grid?: Ref; + itemLayout?: Ref; +} + +export const ListContextKey: InjectionKey = Symbol('ListContextKey'); const List = defineComponent({ name: 'AList', - inheritAttrs: false, Item, - props: initDefaultProps(ListProps(), { + props: initDefaultProps(listProps, { dataSource: [], bordered: false, split: true, loading: false, pagination: false, }), - setup() { - return { - keys: [], - defaultPaginationProps: {}, - onPaginationChange: null, - onPaginationShowSizeChange: null, - configProvider: inject('configProvider', defaultConfigProvider), - }; - }, - - data() { - const { pagination } = this.$props; - const paginationObj: Partial = - pagination && typeof pagination === 'object' ? pagination : {}; - return { - paginationCurrent: paginationObj.defaultCurrent || 1, - paginationSize: paginationObj.defaultPageSize || 10, - }; - }, - created() { - provide('listContext', this); - this.defaultPaginationProps = { + slots: ['extra', 'loadMore', 'renderItem', 'header', 'footer'], + setup(props, { slots }) { + provide(ListContextKey, { + grid: toRef(props, 'grid'), + itemLayout: toRef(props, 'itemLayout'), + }); + const defaultPaginationProps = { current: 1, - pageSize: 10, - onChange: (page: number, pageSize: number) => { - const { pagination } = this; - this.paginationCurrent = page; - if (pagination && (pagination as any).onChange) { - (pagination as any).onChange(page, pageSize); - } - }, total: 0, }; - this.onPaginationChange = this.triggerPaginationEvent('onChange'); - this.onPaginationShowSizeChange = this.triggerPaginationEvent('onShowSizeChange'); - }, - methods: { - triggerPaginationEvent(eventName) { - return (page, pageSize) => { - const { pagination } = this.$props; - this.paginationCurrent = page; - this.paginationSize = pageSize; - if (pagination && pagination[eventName]) { - pagination[eventName](page, pageSize); - } + const { prefixCls, direction, renderEmpty } = useConfigInject('list', props); + const paginationObj = computed(() => + props.pagination && typeof props.pagination === 'object' ? props.pagination : {}, + ); + const paginationCurrent = ref(paginationObj.value.defaultCurrent ?? 1); + const paginationSize = ref(paginationObj.value.defaultPageSize ?? 10); + watch(paginationObj, () => { + if ('current' in paginationObj.value) { + paginationCurrent.value = paginationObj.value.current; + } + if ('pageSize' in paginationObj.value) { + paginationSize.value = paginationObj.value.pageSize; + } + }); + + const triggerPaginationEvent = (eventName: string) => (page: number, pageSize: number) => { + paginationCurrent.value = page; + paginationSize.value = pageSize; + if (paginationObj.value[eventName]) { + paginationObj.value[eventName](page, pageSize); + } + }; + + const onPaginationChange = triggerPaginationEvent('onChange'); + + const onPaginationShowSizeChange = triggerPaginationEvent('onShowSizeChange'); + + const renderEmptyFunc = (renderEmptyHandler: RenderEmptyHandler) => ( +
+ {props.locale?.emptyText || renderEmptyHandler('List')} +
+ ); + + const loadingProp = computed(() => { + if (typeof props.loading === 'boolean') { + return { + spinning: props.loading, + }; + } else { + return props.loading; + } + }); + + const isLoading = computed(() => loadingProp.value && loadingProp.value.spinning); + + const sizeCls = computed(() => { + let size = ''; + switch (props.size) { + case 'large': + size = 'lg'; + break; + case 'small': + size = 'sm'; + break; + default: + break; + } + return size; + }); + + const classObj = computed(() => ({ + [`${prefixCls.value}`]: true, + [`${prefixCls.value}-vertical`]: props.itemLayout === 'vertical', + [`${prefixCls.value}-${sizeCls.value}`]: sizeCls.value, + [`${prefixCls.value}-split`]: props.split, + [`${prefixCls.value}-bordered`]: props.bordered, + [`${prefixCls.value}-loading`]: isLoading.value, + [`${prefixCls.value}-grid`]: !!props.grid, + [`${prefixCls.value}-rtl`]: direction.value === 'rtl', + })); + + const paginationProps = computed(() => { + const pp = { + ...defaultPaginationProps, + total: props.dataSource.length, + current: paginationCurrent.value, + pageSize: paginationSize.value, + ...((props.pagination as PaginationConfig) || {}), }; - }, - innerRenderItem(item, index) { - const { - $slots: { renderItem }, - rowKey, - } = this; - const renderer = this.renderItem || renderItem; - if (!renderer) return null; + + const largestPage = Math.ceil(pp.total / pp.pageSize); + if (pp.current > largestPage) { + pp.current = largestPage; + } + return pp; + }); + + const splitDataSource = computed(() => { + let dd = [...props.dataSource]; + if (props.pagination) { + if ( + props.dataSource.length > + (paginationProps.value.current - 1) * paginationProps.value.pageSize + ) { + dd = [...props.dataSource].splice( + (paginationProps.value.current - 1) * paginationProps.value.pageSize, + paginationProps.value.pageSize, + ); + } + } + return dd; + }); + + const screens = useBreakpoint(); + + const currentBreakpoint = computed(() => { + for (let i = 0; i < responsiveArray.length; i += 1) { + const breakpoint: Breakpoint = responsiveArray[i]; + if (screens.value[breakpoint]) { + return breakpoint; + } + } + return undefined; + }); + + const colStyle = computed(() => { + if (!props.grid) { + return undefined; + } + const columnCount = + currentBreakpoint.value && props.grid[currentBreakpoint.value] + ? props.grid[currentBreakpoint.value] + : props.grid.column; + if (columnCount) { + return { + width: `${100 / columnCount}%`, + maxWidth: `${100 / columnCount}%`, + }; + } + return undefined; + }); + + const renderInnerItem = (keys: number[], item: any, index: number) => { + const renderItem = props.renderItem ?? slots.renderItem; + if (!renderItem) return null; + let key; - if (typeof rowKey === 'function') { - key = rowKey(item); - } else if (typeof rowKey === 'string') { - key = item[rowKey]; + + if (typeof props.rowKey === 'function') { + key = props.rowKey(item); + } else if (typeof props.rowKey === 'string') { + key = item[props.rowKey]; } else { key = item.key; } @@ -137,155 +258,67 @@ const List = defineComponent({ key = `list-item-${index}`; } - this.keys[index] = key; + keys[index] = key; - return renderer({ item, index }); - }, + return renderItem({ item, index }); + }; - isSomethingAfterLastItem() { - const { pagination } = this; - const loadMore = getComponent(this, 'loadMore'); - const footer = getComponent(this, 'footer'); - return !!(loadMore || pagination || footer); - }, + return () => { + const loadMore = props.loadMore ?? slots.loadMore?.(); + const footer = props.footer ?? slots.footer?.(); + const header = props.header ?? slots.header?.(); + const children = flattenChildren(slots.default?.()); + const keys = []; + const isSomethingAfterLastItem = !!(loadMore || props.pagination || footer); + const classString = { + ...classObj.value, + [`${prefixCls.value}-something-after-last-item`]: isSomethingAfterLastItem, + }; + const paginationContent = props.pagination ? ( +
+ +
+ ) : null; - renderEmpty(prefixCls, renderEmpty) { - const { locale } = this; + let childrenContent = isLoading.value &&
; + if (splitDataSource.value.length > 0) { + const items = splitDataSource.value.map((item: any, index: number) => + renderInnerItem(keys, item, index), + ); + const childrenList = items.map((child: any, index) => ( +
+ {child} +
+ )); + childrenContent = props.grid ? ( + {childrenList} + ) : ( +
    {items}
+ ); + } else if (!children.length && !isLoading.value) { + childrenContent = renderEmptyFunc(renderEmpty.value); + } + + const paginationPosition = paginationProps.value.position || 'bottom'; return ( -
- {(locale && locale.emptyText) || renderEmpty('List')} +
+ {(paginationPosition === 'top' || paginationPosition === 'both') && paginationContent} + {header &&
{header}
} + + {childrenContent} + {children} + + {footer &&
{footer}
} + {loadMore || + ((paginationPosition === 'bottom' || paginationPosition === 'both') && + paginationContent)}
); - }, - }, - - render() { - const { - prefixCls: customizePrefixCls, - bordered, - split, - itemLayout, - pagination, - grid, - dataSource = [], - size, - loading, - paginationCurrent, - paginationSize, - $attrs, - } = this; - const { getPrefixCls } = this.configProvider; - const prefixCls = getPrefixCls('list', customizePrefixCls); - const { class: className, ...restAttrs } = $attrs; - const loadMore = getComponent(this, 'loadMore'); - const footer = getComponent(this, 'footer'); - const header = getComponent(this, 'header'); - const children = getSlot(this); - 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.isSomethingAfterLastItem(), - }, - className, - ); - const paginationProps = { - ...this.defaultPaginationProps, - total: dataSource.length, - current: paginationCurrent, - pageSize: paginationSize, - ...((pagination as any) || {}), }; - classString; - const largestPage = Math.ceil(paginationProps.total / paginationProps.pageSize); - if (paginationProps.current > largestPage) { - paginationProps.current = largestPage; - } - const { class: cls, style, ...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.innerRenderItem(item, index)); - const childrenList = items.map((child, index) => - cloneElement(child, { - key: this.keys[index], - }), - ); - - childrenContent = grid ? ( - {childrenList} - ) : ( -
    {childrenList}
- ); - } else if (!children.length && !isLoading) { - const renderEmpty = this.configProvider.renderEmpty; - childrenContent = this.renderEmpty(prefixCls, renderEmpty); - } - const paginationPosition = paginationProps.position || 'bottom'; - - return ( -
- {(paginationPosition === 'top' || paginationPosition === 'both') && paginationContent} - {header &&
{header}
} - - {childrenContent} - {children} - - {footer &&
{footer}
} - {loadMore || - ((paginationPosition === 'bottom' || paginationPosition === 'both') && paginationContent)} -
- ); }, }); @@ -293,13 +326,13 @@ const List = defineComponent({ List.install = function(app: App) { app.component(List.name, List); app.component(List.Item.name, List.Item); - app.component(List.Item.Meta.displayName, List.Item.Meta); + app.component(List.Item.Meta.name, List.Item.Meta); return app; }; export default List as typeof List & Plugin & { readonly Item: typeof Item & { - readonly Meta: typeof ListItemMeta; + readonly Meta: typeof ItemMeta; }; }; diff --git a/components/list/style/bordered.less b/components/list/style/bordered.less index a881524eb..63859a3eb 100644 --- a/components/list/style/bordered.less +++ b/components/list/style/bordered.less @@ -1,41 +1,44 @@ +@import '../../style/themes/index'; + .@{list-prefix-cls}-bordered { border: 1px solid @border-color-base; border-radius: @border-radius-base; .@{list-prefix-cls}-header { - padding-right: 24px; - padding-left: 24px; + padding-right: @padding-lg; + padding-left: @padding-lg; } .@{list-prefix-cls}-footer { - padding-right: 24px; - padding-left: 24px; + padding-right: @padding-lg; + padding-left: @padding-lg; } .@{list-prefix-cls}-item { - padding-right: 24px; - padding-left: 24px; - border-bottom: 1px solid @border-color-split; + padding-right: @padding-lg; + padding-left: @padding-lg; } .@{list-prefix-cls}-pagination { - margin: 16px 24px; + margin: @margin-md @margin-lg; } &.@{list-prefix-cls}-sm { .@{list-prefix-cls}-item { - padding-right: 16px; - padding-left: 16px; + padding: @list-item-padding-sm; } .@{list-prefix-cls}-header, .@{list-prefix-cls}-footer { - padding: 8px 16px; + padding: @list-item-padding-sm; } } &.@{list-prefix-cls}-lg { + .@{list-prefix-cls}-item { + padding: @list-item-padding-lg; + } .@{list-prefix-cls}-header, .@{list-prefix-cls}-footer { - padding: 16px 24px; + padding: @list-item-padding-lg; } } } diff --git a/components/list/style/customize.less b/components/list/style/customize.less new file mode 100644 index 000000000..bbad35b7b --- /dev/null +++ b/components/list/style/customize.less @@ -0,0 +1,12 @@ +@import './index.less'; + +@card-prefix-cls: ~'@{ant-prefix}-card'; + +.@{list-prefix-cls} { + // =================== Dard Hook Components =================== + .@{card-prefix-cls} { + & when (@theme = dark) { + background: @list-customize-card-bg; + } + } +} diff --git a/components/list/style/index.less b/components/list/style/index.less index 5c0c5b2bc..72e75eec4 100644 --- a/components/list/style/index.less +++ b/components/list/style/index.less @@ -1,5 +1,6 @@ @import '../../style/themes/index'; @import '../../style/mixins/index'; +@import './customize.less'; @list-prefix-cls: ~'@{ant-prefix}-list'; @@ -13,7 +14,7 @@ } &-pagination { - margin-top: 24px; + margin-top: @margin-lg; text-align: right; // https://github.com/ant-design/ant-design/issues/20037 @@ -23,7 +24,7 @@ } &-more { - margin-top: 12px; + margin-top: @margin-sm; text-align: center; button { padding-right: 32px; @@ -54,27 +55,27 @@ align-items: center; justify-content: space-between; padding: @list-item-padding; - - &-content { - color: @text-color; - } + color: @text-color; &-meta { display: flex; flex: 1; align-items: flex-start; - font-size: 0; + max-width: 100%; + &-avatar { margin-right: @list-item-meta-avatar-margin-right; } &-content { flex: 1 0; + width: 0; + color: @text-color; } &-title { margin-bottom: 4px; color: @text-color; font-size: @font-size-base; - line-height: 22px; + line-height: @line-height-base; > a { color: @text-color; transition: all 0.3s; @@ -85,8 +86,8 @@ } &-description { color: @text-color-secondary; - font-size: @font-size-base; - line-height: 22px; + font-size: @list-item-meta-description-font-size; + line-height: @line-height-base; } } &-action { @@ -95,19 +96,21 @@ padding: 0; font-size: 0; list-style: none; + & > li { position: relative; display: inline-block; - padding: 0 8px; + padding: 0 @padding-xs; color: @text-color-secondary; font-size: @font-size-base; - line-height: 22px; + line-height: @line-height-base; text-align: center; - cursor: pointer; - } - & > li:first-child { - padding-left: 0; + + &:first-child { + padding-left: 0; + } } + &-split { position: absolute; top: 50%; @@ -130,12 +133,12 @@ &-header, &-footer { - padding-top: 12px; - padding-bottom: 12px; + padding-top: @padding-sm; + padding-bottom: @padding-sm; } &-empty { - padding: 16px 0; + padding: @padding-md 0; color: @text-color-secondary; font-size: 12px; text-align: center; @@ -152,22 +155,24 @@ border-bottom: 1px solid @border-color-split; } + &-split&-empty &-footer { + border-top: 1px solid @border-color-split; + } + &-loading &-spin-nested-loading { min-height: 32px; } - &-something-after-last-item .@{ant-prefix}-spin-container > &-items > &-item:last-child { + &-split&-something-after-last-item .@{ant-prefix}-spin-container > &-items > &-item:last-child { border-bottom: 1px solid @border-color-split; } &-lg &-item { - padding-top: 16px; - padding-bottom: 16px; + padding: @list-item-padding-lg; } &-sm &-item { - padding-top: 8px; - padding-bottom: 8px; + padding: @list-item-padding-sm; } &-vertical &-item { @@ -198,7 +203,7 @@ margin-left: auto; > li { - padding: 0 16px; + padding: 0 @padding-md; &:first-child { padding-left: 0; } @@ -209,7 +214,7 @@ &-grid .@{ant-prefix}-col > &-item { display: block; max-width: 100%; - margin-bottom: 16px; + margin-bottom: @margin-md; padding-top: 0; padding-bottom: 0; border-bottom: none; @@ -232,3 +237,4 @@ @import './bordered'; @import './responsive'; +@import './rtl'; diff --git a/components/list/style/rtl.less b/components/list/style/rtl.less new file mode 100644 index 000000000..429969c68 --- /dev/null +++ b/components/list/style/rtl.less @@ -0,0 +1,139 @@ +@import '../../style/themes/index'; +@import '../../style/mixins/index'; +@import './customize.less'; + +@list-prefix-cls: ~'@{ant-prefix}-list'; + +.@{list-prefix-cls} { + &-rtl { + direction: rtl; + text-align: right; + + // fix for virtual scroll style attribute > (direction:ltr) + .ReactVirtualized__List .@{list-prefix-cls}-item { + direction: rtl; + } + } + + &-pagination { + .@{list-prefix-cls}-rtl & { + text-align: left; + } + } + + &-item { + &-meta { + &-avatar { + .@{list-prefix-cls}-rtl & { + margin-right: 0; + margin-left: @list-item-meta-avatar-margin-right; + } + } + } + + &-action { + .@{list-prefix-cls}-rtl & { + margin-right: 48px; + margin-left: 0; + } + + & > li:first-child { + .@{list-prefix-cls}.@{list-prefix-cls}-rtl & { + padding-right: 0; + padding-left: @padding-md; + } + } + + &-split { + .@{list-prefix-cls}-rtl & { + right: auto; + left: 0; + } + } + } + } + + &-vertical &-item { + &-extra { + .@{list-prefix-cls}-rtl& { + margin-right: 40px; + margin-left: 0; + } + } + + &-action { + .@{list-prefix-cls}-rtl& { + margin-right: auto; + } + + > li { + &:first-child { + .@{list-prefix-cls}-rtl & { + padding-right: 0; + padding-left: @padding-md; + } + } + } + } + } + + // Horizontal + &:not(.@{list-prefix-cls}-vertical) { + .@{list-prefix-cls}-item-no-flex { + .@{list-prefix-cls}-item-action { + .@{list-prefix-cls}-rtl & { + float: left; + } + } + } + } +} + +// responsive +@media screen and (max-width: @screen-md) { + .@{list-prefix-cls} { + &-item { + &-action { + .@{list-prefix-cls}-rtl & { + margin-right: 24px; + margin-left: 0; + } + } + } + } + + .@{list-prefix-cls}-vertical { + .@{list-prefix-cls}-item { + &-extra { + .@{list-prefix-cls}-rtl & { + margin-right: 24px; + margin-left: 0; + } + } + } + } +} + +@media screen and (max-width: @screen-sm) { + .@{list-prefix-cls} { + &-item { + &-action { + .@{list-prefix-cls}-rtl & { + margin-right: 22px; + margin-left: 0; + } + } + } + } + + .@{list-prefix-cls}-vertical { + .@{list-prefix-cls}-item { + &-extra { + // to override margins on rtl view + .@{list-prefix-cls}-rtl& { + margin: auto auto 16px; + } + } + } + } +} diff --git a/components/pagination/Pagination.tsx b/components/pagination/Pagination.tsx index 6b3db662c..df1a2dd6d 100644 --- a/components/pagination/Pagination.tsx +++ b/components/pagination/Pagination.tsx @@ -1,4 +1,4 @@ -import { defineComponent, inject } from 'vue'; +import { defineComponent, ExtractPropTypes, inject } from 'vue'; import LeftOutlined from '@ant-design/icons-vue/LeftOutlined'; import RightOutlined from '@ant-design/icons-vue/RightOutlined'; import DoubleLeftOutlined from '@ant-design/icons-vue/DoubleLeftOutlined'; @@ -14,7 +14,7 @@ import enUS from '../vc-pagination/locale/en_US'; import { defaultConfigProvider } from '../config-provider'; import classNames from '../_util/classNames'; -export const PaginationProps = () => ({ +export const paginationProps = () => ({ total: PropTypes.number, defaultCurrent: PropTypes.number, disabled: PropTypes.looseBool, @@ -42,16 +42,19 @@ export const PaginationProps = () => ({ 'onUpdate:pageSize': PropTypes.func, }); -export const PaginationConfig = () => ({ - ...PaginationProps(), +export const paginationConfig = () => ({ + ...paginationProps(), position: PropTypes.oneOf(tuple('top', 'bottom', 'both')), }); +export type PaginationProps = Partial>>; +export type PaginationConfig = Partial>>; + export default defineComponent({ name: 'APagination', inheritAttrs: false, props: { - ...PaginationProps(), + ...paginationProps(), }, emits: ['change', 'showSizeChange', 'update:current', 'update:pageSize'], setup() { diff --git a/components/pagination/index.ts b/components/pagination/index.ts index a77529a7d..86626c0c7 100644 --- a/components/pagination/index.ts +++ b/components/pagination/index.ts @@ -1,6 +1,6 @@ import Pagination from './Pagination'; import { withInstall } from '../_util/type'; -export { PaginationProps, PaginationConfig } from './Pagination'; +export { paginationProps, PaginationProps, PaginationConfig, paginationConfig } from './Pagination'; export default withInstall(Pagination); diff --git a/components/style/themes/default.less b/components/style/themes/default.less index 76d4c9cf8..3bc40f36e 100644 --- a/components/style/themes/default.less +++ b/components/style/themes/default.less @@ -803,9 +803,13 @@ @list-footer-background: transparent; @list-empty-text-padding: @padding-md; @list-item-padding: @padding-sm 0; +@list-item-padding-sm: @padding-xs @padding-md; +@list-item-padding-lg: 16px 24px; @list-item-meta-margin-bottom: @padding-md; @list-item-meta-avatar-margin-right: @padding-md; @list-item-meta-title-margin-bottom: @padding-sm; +@list-customize-card-bg: @component-background; +@list-item-meta-description-font-size: @font-size-base; // Statistic // --- diff --git a/components/table/interface.ts b/components/table/interface.ts index c2fa7ac63..ab3e409bb 100644 --- a/components/table/interface.ts +++ b/components/table/interface.ts @@ -1,6 +1,6 @@ import { ExtractPropTypes, PropType, UnwrapRef } from 'vue'; import PropTypes, { withUndefined } from '../_util/vue-types'; -import { PaginationProps as getPaginationProps, PaginationConfig } from '../pagination'; +import { paginationProps as getPaginationProps, paginationConfig } from '../pagination'; import { getSpinProps } from '../spin'; import { tuple } from '../_util/type'; @@ -103,7 +103,7 @@ export const tableRowSelection = { export type SortOrder = 'descend' | 'ascend'; -const paginationProps = PaginationConfig(); +const paginationProps = paginationConfig(); export const tableProps = { prefixCls: PropTypes.string,