feat: update card

pull/1790/head
tangjinzhou 2020-02-10 17:15:15 +08:00
parent 958f3c4592
commit 43c7c728fa
12 changed files with 142 additions and 146 deletions

View File

@ -1,5 +1,5 @@
module.exports = {
dev: {
componentName: 'calendar', // dev components
componentName: 'card', // dev components
},
};

View File

@ -3,14 +3,12 @@ import Tabs from '../tabs';
import Row from '../row';
import Col from '../col';
import PropTypes from '../_util/vue-types';
import addEventListener from '../vc-util/Dom/addEventListener';
import {
getComponentFromProp,
getSlotOptions,
filterEmpty,
getListeners,
} from '../_util/props-util';
import throttleByAnimationFrame from '../_util/throttleByAnimationFrame';
import BaseMixin from '../_util/BaseMixin';
import { ConfigConsumerProps } from '../config-provider';
@ -31,6 +29,7 @@ export default {
size: PropTypes.oneOf(['default', 'small']),
actions: PropTypes.any,
tabList: PropTypes.array,
tabBarExtraContent: PropTypes.any,
activeTabKey: PropTypes.string,
defaultActiveTabKey: PropTypes.string,
},
@ -38,44 +37,20 @@ export default {
configProvider: { default: () => ConfigConsumerProps },
},
data() {
this.updateWiderPaddingCalled = false;
return {
widerPadding: false,
};
},
beforeMount() {
this.updateWiderPadding = throttleByAnimationFrame(this.updateWiderPadding);
},
mounted() {
this.updateWiderPadding();
this.resizeEvent = addEventListener(window, 'resize', this.updateWiderPadding);
},
beforeDestroy() {
if (this.resizeEvent) {
this.resizeEvent.remove();
}
this.updateWiderPadding.cancel && this.updateWiderPadding.cancel();
},
methods: {
updateWiderPadding() {
const cardContainerRef = this.$refs.cardContainerRef;
if (!cardContainerRef) {
return;
}
// 936 is a magic card width pixel number indicated by designer
const WIDTH_BOUNDARY_PX = 936;
if (cardContainerRef.offsetWidth >= WIDTH_BOUNDARY_PX && !this.widerPadding) {
this.setState({ widerPadding: true }, () => {
this.updateWiderPaddingCalled = true; // first render without css transition
});
}
if (cardContainerRef.offsetWidth < WIDTH_BOUNDARY_PX && this.widerPadding) {
this.setState({ widerPadding: false }, () => {
this.updateWiderPaddingCalled = true; // first render without css transition
});
}
getAction(actions) {
const actionList = actions.map((action, index) => (
<li style={{ width: `${100 / actions.length}%` }} key={`action-${index}`}>
<span>{action}</span>
</li>
));
return actionList;
},
onHandleTabChange(key) {
onTabChange(key) {
this.$emit('tabChange', key);
},
isContainGrid(obj = []) {
@ -87,17 +62,6 @@ export default {
});
return containGrid;
},
getAction(actions) {
if (!actions || !actions.length) {
return null;
}
const actionList = actions.map((action, index) => (
<li style={{ width: `${100 / actions.length}%` }} key={`action-${index}`}>
<span>{action}</span>
</li>
));
return actionList;
},
},
render() {
const {
@ -118,14 +82,12 @@ export default {
const prefixCls = getPrefixCls('card', customizePrefixCls);
const { $slots, $scopedSlots } = this;
const tabBarExtraContent = getComponentFromProp(this, 'tabBarExtraContent');
const classString = {
[`${prefixCls}`]: true,
[`${prefixCls}-loading`]: loading,
[`${prefixCls}-bordered`]: bordered,
[`${prefixCls}-hoverable`]: !!hoverable,
[`${prefixCls}-wider-padding`]: this.widerPadding,
[`${prefixCls}-padding-transition`]: this.updateWiderPaddingCalled,
[`${prefixCls}-contain-grid`]: this.isContainGrid($slots.default),
[`${prefixCls}-contain-tabs`]: tabList && tabList.length,
[`${prefixCls}-${size}`]: size !== 'default',
@ -187,9 +149,10 @@ export default {
[hasActiveTabKey ? 'activeKey' : 'defaultActiveKey']: hasActiveTabKey
? activeTabKey
: defaultActiveTabKey,
tabBarExtraContent,
},
on: {
change: this.onHandleTabChange,
change: this.onTabChange,
},
class: `${prefixCls}-head-tabs`,
};

View File

@ -7,18 +7,20 @@ export default {
__ANT_CARD_GRID: true,
props: {
prefixCls: PropTypes.string,
hoverable: PropTypes.bool,
},
inject: {
configProvider: { default: () => ConfigConsumerProps },
},
render() {
const { prefixCls: customizePrefixCls } = this.$props;
const { prefixCls: customizePrefixCls, hoverable = true } = this.$props;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('card', customizePrefixCls);
const classString = {
[`${prefixCls}-grid`]: true,
[`${prefixCls}-grid-hoverable`]: hoverable,
};
return (
<div {...{ on: getListeners(this) }} class={classString}>

View File

@ -70,13 +70,14 @@ exports[`renders ./components/card/demo/grid-card.md correctly 1`] = `
</div>
</div>
<div class="ant-card-body">
<div class="ant-card-grid" style="width: 25%; text-align: 'center';">Content</div>
<div class="ant-card-grid" style="width: 25%; text-align: 'center';">Content</div>
<div class="ant-card-grid" style="width: 25%; text-align: 'center';">Content</div>
<div class="ant-card-grid" style="width: 25%; text-align: 'center';">Content</div>
<div class="ant-card-grid" style="width: 25%; text-align: 'center';">Content</div>
<div class="ant-card-grid" style="width: 25%; text-align: 'center';">Content</div>
<div class="ant-card-grid" style="width: 25%; text-align: 'center';">Content</div>
<div class="ant-card-grid ant-card-grid-hoverable" style="width: 25%; text-align: center;">Content</div>
<div class="ant-card-grid" style="width: 25%; text-align: center;">Content</div>
<div class="ant-card-grid ant-card-grid-hoverable" style="width: 25%; text-align: center;">Content</div>
<div class="ant-card-grid ant-card-grid-hoverable" style="width: 25%; text-align: center;">Content</div>
<div class="ant-card-grid ant-card-grid-hoverable" style="width: 25%; text-align: center;">Content</div>
<div class="ant-card-grid ant-card-grid-hoverable" style="width: 25%; text-align: center;">Content</div>
<div class="ant-card-grid ant-card-grid-hoverable" style="width: 25%; text-align: center;">Content</div>
<div class="ant-card-grid ant-card-grid-hoverable" style="width: 25%; text-align: center;">Content</div>
</div>
</div>
`;
@ -291,6 +292,7 @@ exports[`renders ./components/card/demo/tabs.md correctly 1`] = `
<div class="ant-card-head-wrapper"></div>
<div class="ant-tabs ant-tabs-top ant-tabs-large ant-tabs-line ant-card-head-tabs">
<div role="tablist" tabindex="0" class="ant-tabs-bar ant-tabs-top-bar ant-tabs-large-bar">
<div class="ant-tabs-extra-content" style="float: right;"><a href="#">More</a></div>
<div class="ant-tabs-nav-container"><span unselectable="unselectable" class="ant-tabs-tab-prev ant-tabs-tab-btn-disabled"><span class="ant-tabs-tab-prev-icon"><i aria-label="icon: left" class="ant-tabs-tab-prev-icon-target anticon anticon-left"><svg viewBox="64 64 896 896" data-icon="left" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8a31.86 31.86 0 0 0 0 50.3l450.8 352.1c5.3 4.1 12.9.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"></path></svg></i></span></span><span unselectable="unselectable" class="ant-tabs-tab-next ant-tabs-tab-btn-disabled"><span class="ant-tabs-tab-next-icon"><i aria-label="icon: right" class="ant-tabs-tab-next-icon-target anticon anticon-right"><svg viewBox="64 64 896 896" data-icon="right" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M765.7 486.8L314.9 134.7A7.97 7.97 0 0 0 302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 0 0 0-50.4z"></path></svg></i></span></span>
<div class="ant-tabs-nav-wrap">
<div class="ant-tabs-nav-scroll">

View File

@ -1,10 +1,12 @@
import { mount } from '@vue/test-utils';
import Card from '../index';
import Button from '../../button/index';
import mountTest from '../../../tests/shared/mountTest';
const testMethod = typeof window !== 'undefined' ? it : xit;
describe('Card', () => {
mountTest(Card);
beforeAll(() => {
jest.useFakeTimers();
});
@ -12,35 +14,6 @@ describe('Card', () => {
afterAll(() => {
jest.useRealTimers();
});
function fakeResizeWindowTo(wrapper, width) {
Object.defineProperties(wrapper.vm.$refs.cardContainerRef, {
offsetWidth: {
get() {
return width;
},
configurable: true,
},
});
window.resizeTo(width);
}
testMethod('resize card will trigger different padding', () => {
const wrapper = mount(Card, {
propsData: 'xxx',
slots: {
default: 'xxx',
},
});
fakeResizeWindowTo(wrapper, 1000);
jest.runAllTimers();
wrapper.vm.$forceUpdate();
expect(wrapper.findAll('.ant-card-wider-padding').length).toBe(1);
fakeResizeWindowTo(wrapper, 800);
jest.runAllTimers();
wrapper.vm.$forceUpdate();
expect(wrapper.findAll('.ant-card-wider-padding').length).toBe(0);
});
it('should still have padding when card which set padding to 0 is loading', () => {
const wrapper = mount({
render() {
@ -66,4 +39,50 @@ describe('Card', () => {
});
expect(wrapper.html()).toMatchSnapshot();
});
it('onTabChange should work', () => {
const tabList = [
{
key: 'tab1',
tab: 'tab1',
},
{
key: 'tab2',
tab: 'tab2',
},
];
const onTabChange = jest.fn();
const wrapper = mount(
{
render() {
return (
<Card onTabChange={onTabChange} tabList={tabList}>
xxx
</Card>
);
},
},
{
sync: false,
},
);
wrapper
.findAll('.ant-tabs-tab')
.at(1)
.trigger('click');
expect(onTabChange).toHaveBeenCalledWith('tab2');
});
it('should not render when actions is number', () => {
const wrapper = mount({
render() {
return (
<Card title="Card title" actions={11}>
<p>Card content</p>
</Card>
);
},
});
expect(wrapper.findAll('.ant-card-actions').length).toBe(0);
});
});

View File

@ -11,13 +11,14 @@ Grid style card content.
```tpl
<template>
<a-card title="Card Title">
<a-card-grid style="width:25%;textAlign:'center'">Content</a-card-grid>
<a-card-grid style="width:25%;textAlign:'center'">Content</a-card-grid>
<a-card-grid style="width:25%;textAlign:'center'">Content</a-card-grid>
<a-card-grid style="width:25%;textAlign:'center'">Content</a-card-grid>
<a-card-grid style="width:25%;textAlign:'center'">Content</a-card-grid>
<a-card-grid style="width:25%;textAlign:'center'">Content</a-card-grid>
<a-card-grid style="width:25%;textAlign:'center'">Content</a-card-grid>
<a-card-grid style="width:25%;text-align:center">Content</a-card-grid>
<a-card-grid style="width:25%;text-align:center" :hoverable="false">Content</a-card-grid>
<a-card-grid style="width:25%;text-align:center">Content</a-card-grid>
<a-card-grid style="width:25%;text-align:center">Content</a-card-grid>
<a-card-grid style="width:25%;text-align:center">Content</a-card-grid>
<a-card-grid style="width:25%;text-align:center">Content</a-card-grid>
<a-card-grid style="width:25%;text-align:center">Content</a-card-grid>
<a-card-grid style="width:25%;text-align:center">Content</a-card-grid>
</a-card>
</template>
```

View File

@ -17,9 +17,9 @@
slot="cover"
/>
<template class="ant-card-actions" slot="actions">
<a-icon type="setting" />
<a-icon type="edit" />
<a-icon type="ellipsis" />
<a-icon type="setting" key="setting" />
<a-icon type="edit" key="edit" />
<a-icon type="ellipsis" key="ellipsis" />
</template>
<a-card-meta title="Card title" description="This is the description">
<a-avatar

View File

@ -32,6 +32,7 @@
<p v-if="noTitleKey === 'article'">article content</p>
<p v-else-if="noTitleKey === 'app'">app content</p>
<p v-else="noTitleKey === 'project'">project content</p>
<a href="#" slot="tabBarExtraContent">More</a>
</a-card>
</div>
</template>

View File

@ -2,35 +2,36 @@
### Card
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| actions | The action list, shows at the bottom of the Card. | slots | - |
| activeTabKey | Current TabPane's key | string | - |
| headStyle | Inline style to apply to the card head | object | - |
| bodyStyle | Inline style to apply to the card content | object | - |
| bordered | Toggles rendering of the border around the card | boolean | `true` |
| cover | Card cover | slot | - |
| defaultActiveTabKey | Initial active TabPane's key, if `activeTabKey` is not set. | string | - |
| extra | Content to render in the top-right corner of the card | string\|slot | - |
| hoverable | Lift up when hovering card | boolean | false |
| loading | Shows a loading indicator while the contents of the card are being fetched | boolean | false |
| tabList | List of TabPane's head, Custom tabs can be created with the scopedSlots property | Array<{key: string, tab: any, scopedSlots: {tab: 'XXX'}}> | - |
| size | Size of card | `default` \| `small` | `default` |
| title | Card title | string\|slot | - |
| type | Card style type, can be set to `inner` or not set | string | - |
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| actions | The action list, shows at the bottom of the Card. | slots | - | |
| activeTabKey | Current TabPane's key | string | - | |
| headStyle | Inline style to apply to the card head | object | - | |
| bodyStyle | Inline style to apply to the card content | object | - | |
| bordered | Toggles rendering of the border around the card | boolean | `true` | |
| cover | Card cover | slot | - | |
| defaultActiveTabKey | Initial active TabPane's key, if `activeTabKey` is not set. | string | - | |
| extra | Content to render in the top-right corner of the card | string\|slot | - | |
| hoverable | Lift up when hovering card | boolean | false | |
| loading | Shows a loading indicator while the contents of the card are being fetched | boolean | false | |
| tabList | List of TabPane's head, Custom tabs can be created with the scopedSlots property | Array<{key: string, tab: any, scopedSlots: {tab: 'XXX'}}> | - | |
| tabBarExtraContent | Extra content in tab bar | slot | - | 1.5.0 |
| size | Size of card | `default` \| `small` | `default` | |
| title | Card title | string\|slot | - | |
| type | Card style type, can be set to `inner` or not set | string | - | |
### events
| Events Name | Description | Arguments |
| ----------- | ----------------------------- | ------------- |
| tabChange | Callback when tab is switched | (key) => void | - |
| Events Name | Description | Arguments | Version |
| ----------- | ----------------------------- | ------------- | ------- |
| tabChange | Callback when tab is switched | (key) => void | - | |
### Card.Grid
### Card.Meta
| Property | Description | Type | Default |
| ----------- | ------------------- | ------------ | ------- |
| avatar | avatar or icon | slot | - |
| description | description content | string\|slot | - |
| title | title content | string\|slot | - |
| Property | Description | Type | Default | Version |
| ----------- | ------------------- | ------------ | ------- | ------- |
| avatar | avatar or icon | slot | - | |
| description | description content | string\|slot | - | |
| title | title content | string\|slot | - | |

View File

@ -2,35 +2,36 @@
### Card
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| actions | 卡片操作组,位置在卡片底部 | slots | - |
| activeTabKey | 当前激活页签的 key | string | - |
| headStyle | 自定义标题区域样式 | object | - |
| bodyStyle | 内容区域自定义样式 | object | - |
| bordered | 是否有边框 | boolean | true |
| cover | 卡片封面 | slot | - |
| defaultActiveTabKey | 初始化选中页签的 key如果没有设置 activeTabKey | string | 第一个页签 |
| extra | 卡片右上角的操作区域 | string\|slot | - |
| hoverable | 鼠标移过时可浮起 | boolean | false |
| loading | 当卡片内容还在加载中时,可以用 loading 展示一个占位 | boolean | false |
| tabList | 页签标题列表, 可以通过 scopedSlots 属性自定义 tab | Array<{key: string, tab: any, scopedSlots: {tab: 'XXX'}}> | - |
| size | card 的尺寸 | `default` \| `small` | `default` |
| title | 卡片标题 | string\|slot | - |
| type | 卡片类型,可设置为 `inner` 或 不设置 | string | - |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| actions | 卡片操作组,位置在卡片底部 | slots | - | |
| activeTabKey | 当前激活页签的 key | string | - | |
| headStyle | 自定义标题区域样式 | object | - | |
| bodyStyle | 内容区域自定义样式 | object | - | |
| bordered | 是否有边框 | boolean | true | |
| cover | 卡片封面 | slot | - | |
| defaultActiveTabKey | 初始化选中页签的 key如果没有设置 activeTabKey | string | 第一个页签 | |
| extra | 卡片右上角的操作区域 | string\|slot | - | |
| hoverable | 鼠标移过时可浮起 | boolean | false | |
| loading | 当卡片内容还在加载中时,可以用 loading 展示一个占位 | boolean | false | |
| tabList | 页签标题列表, 可以通过 scopedSlots 属性自定义 tab | Array<{key: string, tab: any, scopedSlots: {tab: 'XXX'}}> | - | |
| tabBarExtraContent | tab bar 上额外的元素 | slot | 无 | 1.5.0 |
| size | card 的尺寸 | `default` \| `small` | `default` | |
| title | 卡片标题 | string\|slot | - | |
| type | 卡片类型,可设置为 `inner` 或 不设置 | string | - | |
### 事件
| 事件名称 | 说明 | 回调参数 |
| --------- | -------------- | ------------- |
| tabChange | 页签切换的回调 | (key) => void | - |
| 事件名称 | 说明 | 回调参数 | 版本 |
| --------- | -------------- | ------------- | ---- |
| tabChange | 页签切换的回调 | (key) => void | - | |
### Card.Grid
### Card.Meta
| Property | Description | Type | Default |
| ----------- | ----------- | ------------ | ------- |
| avatar | 头像/图标 | slot | - |
| description | 描述内容 | string\|slot | - |
| title | 标题内容 | string\|slot | - |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| ----------- | --------- | ------------ | ------ | ---- |
| avatar | 头像/图标 | slot | - | |
| description | 描述内容 | string\|slot | - | |
| title | 标题内容 | string\|slot | - | |

View File

@ -26,7 +26,12 @@ export default {
defaultActiveKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
hideAdd: PropTypes.bool.def(false),
tabBarStyle: PropTypes.object,
tabBarExtraContent: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.func]),
tabBarExtraContent: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.func,
PropTypes.array,
]),
destroyInactiveTabPane: PropTypes.bool.def(false),
type: PropTypes.oneOf(['line', 'card', 'editable-card']),
tabPosition: PropTypes.oneOf(['top', 'right', 'bottom', 'left']).def('top'),

1
types/card.d.ts vendored
View File

@ -11,6 +11,7 @@ export declare class Card extends AntdComponent {
static Grid: any;
static Meta: typeof Meta;
tabBarExtraContent: any;
/**
* The action list, shows at the bottom of the Card.
* @type any (slots)