refactor: list (#4242)

pull/4258/head
tangjinzhou 2021-06-23 14:44:21 +08:00 committed by GitHub
parent 31ed5c68d1
commit 22141e74de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 591 additions and 399 deletions

View File

@ -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<UnwrapRef<ConfigProviderProps>>(
'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,
};
};

View File

@ -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<Partial<
ExtractPropTypes<typeof ListItemMetaProps>
>> = (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 = (
<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 class={`${prefixCls}-item-meta`}>
{avatar && <div class={`${prefixCls}-item-meta-avatar`}>{avatar}</div>}
{(title || description) && content}
</div>
);
};
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>('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 && (
<ul class={`${prefixCls}-item-action`} key="actions">
{actions.map((action, i) => (
<li key={`${prefixCls}-item-action-${i}`}>
{action}
{i !== actions.length - 1 && <em class={`${prefixCls}-item-action-split`} />}
</li>
))}
</ul>
);
const children = getSlot(this);
const Tag = grid ? 'div' : 'li';
const itemChildren = (
<Tag
{...restAttrs}
class={classNames(`${prefixCls}-item`, _className, {
[`${prefixCls}-item-no-flex`]: !this.isFlexMode(),
})}
>
{itemLayout === 'vertical' && extra
? [
<div class={`${prefixCls}-item-main`} key="content">
{children}
{actionsContent}
</div>,
<div class={`${prefixCls}-item-extra`} key="extra">
{extra}
</div>,
]
: [children, actionsContent, cloneElement(extra, { key: 'extra' })]}
</Tag>
);
return !isItemContainsTextNodeAndNotSingular();
};
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')}
>
{itemChildren}
</Col>
) : (
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 && (
<ul class={`${pre}-item-action`} key="actions">
{actions.map((action, i) => (
<li key={`${pre}-item-action-${i}`}>
{action}
{i !== actions.length - 1 && <em class={`${pre}-item-action-split`} />}
</li>
))}
</ul>
);
const Element = grid.value ? 'div' : 'li';
const itemChildren = (
<Element
{...(restAttrs as any)} // `li` element `onCopy` prop args is not same as `div`
class={classNames(
`${pre}-item`,
{
[`${pre}-item-no-flex`]: !isFlexMode(),
},
className,
)}
>
{itemLayout.value === 'vertical' && extra
? [
<div class={`${pre}-item-main`} key="content">
{children}
{actionsContent}
</div>,
<div class={`${pre}-item-extra`} key="extra">
{extra}
</div>,
]
: [children, actionsContent, cloneElement(extra, { key: 'extra' })]}
</Element>
);
return grid.value ? (
<Col flex={1} style={props.colStyle}>
{itemChildren}
</Col>
) : (
itemChildren
);
};
},
});

View File

@ -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<ExtractPropTypes<typeof listItemMetaProps>>;
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 = (
<div class={`${prefixCls.value}-item-meta-content`}>
{title && <h4 class={`${prefixCls.value}-item-meta-title`}>{title}</h4>}
{description && (
<div class={`${prefixCls.value}-item-meta-description`}>{description}</div>
)}
</div>
);
return (
<div class={classString}>
{avatar && <div class={`${prefixCls.value}-item-meta-avatar`}>{avatar}</div>}
{(title || description) && content}
</div>
);
};
},
});

View File

@ -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<Partial<ExtractPropTypes<typeof paginationProps>>>(paginationProps).loose,
PropTypes.shape<PaginationConfig>(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<ListLocale>,
},
};
export interface ListLocale {
emptyText: any;
}
export type ListProps = Partial<ExtractPropTypes<typeof listProps>>;
export interface ListContext {
grid?: Ref<any>;
itemLayout?: Ref<string>;
}
export const ListContextKey: InjectionKey<ListContext> = 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<typeof pagination> =
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) => (
<div class={`${prefixCls.value}-empty-text`}>
{props.locale?.emptyText || renderEmptyHandler('List')}
</div>
);
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 ? (
<div class={`${prefixCls.value}-pagination`}>
<Pagination
{...paginationProps.value}
onChange={onPaginationChange}
onShowSizeChange={onPaginationShowSizeChange}
/>
</div>
) : null;
renderEmpty(prefixCls, renderEmpty) {
const { locale } = this;
let childrenContent = isLoading.value && <div style={{ minHeight: '53px' }} />;
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) => (
<div key={keys[index]} style={colStyle.value}>
{child}
</div>
));
childrenContent = props.grid ? (
<Row gutter={props.grid.gutter}>{childrenList}</Row>
) : (
<ul class={`${prefixCls.value}-items`}>{items}</ul>
);
} else if (!children.length && !isLoading.value) {
childrenContent = renderEmptyFunc(renderEmpty.value);
}
const paginationPosition = paginationProps.value.position || 'bottom';
return (
<div class={`${prefixCls}-empty-text`}>
{(locale && locale.emptyText) || renderEmpty('List')}
<div class={classString}>
{(paginationPosition === 'top' || paginationPosition === 'both') && paginationContent}
{header && <div class={`${prefixCls.value}-header`}>{header}</div>}
<Spin {...loadingProp.value}>
{childrenContent}
{children}
</Spin>
{footer && <div class={`${prefixCls.value}-footer`}>{footer}</div>}
{loadMore ||
((paginationPosition === 'bottom' || paginationPosition === 'both') &&
paginationContent)}
</div>
);
},
},
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 ? (
<div class={`${prefixCls}-pagination`}>
<Pagination
{...{
...omit(restProps, ['onChange']),
class: cls,
style,
onChange: this.onPaginationChange,
onShowSizeChange: this.onPaginationShowSizeChange,
}}
/>
</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.innerRenderItem(item, index));
const childrenList = items.map((child, index) =>
cloneElement(child, {
key: this.keys[index],
}),
);
childrenContent = grid ? (
<Row gutter={grid.gutter}>{childrenList}</Row>
) : (
<ul class={`${prefixCls}-items`}>{childrenList}</ul>
);
} else if (!children.length && !isLoading) {
const renderEmpty = this.configProvider.renderEmpty;
childrenContent = this.renderEmpty(prefixCls, renderEmpty);
}
const paginationPosition = paginationProps.position || 'bottom';
return (
<div class={classString} {...restAttrs}>
{(paginationPosition === 'top' || paginationPosition === 'both') && paginationContent}
{header && <div class={`${prefixCls}-header`}>{header}</div>}
<Spin {...loadingProp}>
{childrenContent}
{children}
</Spin>
{footer && <div class={`${prefixCls}-footer`}>{footer}</div>}
{loadMore ||
((paginationPosition === 'bottom' || paginationPosition === 'both') && paginationContent)}
</div>
);
},
});
@ -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;
};
};

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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';

View File

@ -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;
}
}
}
}
}

View File

@ -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<ExtractPropTypes<ReturnType<typeof paginationProps>>>;
export type PaginationConfig = Partial<ExtractPropTypes<ReturnType<typeof paginationConfig>>>;
export default defineComponent({
name: 'APagination',
inheritAttrs: false,
props: {
...PaginationProps(),
...paginationProps(),
},
emits: ['change', 'showSizeChange', 'update:current', 'update:pageSize'],
setup() {

View File

@ -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);

View File

@ -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
// ---

View File

@ -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,