Skip to content

Commit e43f690

Browse files
committed
feat: modal add closeIcon support
1 parent 078a3e4 commit e43f690

File tree

12 files changed

+113
-90
lines changed

12 files changed

+113
-90
lines changed

build/config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module.exports = {
22
dev: {
3-
componentName: 'message', // dev components
3+
componentName: 'modal', // dev components
44
},
55
};

components/modal/ActionButton.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ export default {
4848
// this.setState({ loading: false });
4949
closeModal(...args);
5050
},
51-
() => {
51+
e => {
52+
// Emit error when catch promise reject
53+
// eslint-disable-next-line no-console
54+
console.error(e);
5255
// See: https://github.com/ant-design/ant-design/issues/6183
5356
this.setState({ loading: false });
5457
},

components/modal/ConfirmDialog.jsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export default {
2727
} = props;
2828
warning(
2929
!('iconType' in props),
30+
'Modal',
3031
`The property 'iconType' is deprecated. Use the property 'icon' instead.`,
3132
);
3233
const icon = props.icon ? props.icon : iconType;
@@ -92,9 +93,9 @@ export default {
9293
<div class={`${contentPrefixCls}-body-wrapper`}>
9394
<div class={`${contentPrefixCls}-body`}>
9495
{iconNode}
95-
<span class={`${contentPrefixCls}-title`}>
96-
{typeof props.title === 'function' ? props.title(h) : props.title}
97-
</span>
96+
{props.title === undefined ? null : (
97+
<span class={`${contentPrefixCls}-title`}>{props.title}</span>
98+
)}
9899
<div class={`${contentPrefixCls}-content`}>
99100
{typeof props.content === 'function' ? props.content(h) : props.content}
100101
</div>

components/modal/Modal.jsx

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,23 @@ import {
1919
import { ConfigConsumerProps } from '../config-provider';
2020

2121
let mousePosition = null;
22-
let mousePositionEventBinded = false;
22+
// ref: https://github.com/ant-design/ant-design/issues/15795
23+
const getClickPosition = e => {
24+
mousePosition = {
25+
x: e.pageX,
26+
y: e.pageY,
27+
};
28+
// 100ms 内发生过点击事件,则从点击位置动画展示
29+
// 否则直接 zoom 展示
30+
// 这样可以兼容非点击方式展开
31+
setTimeout(() => (mousePosition = null), 100);
32+
};
33+
34+
// 只有点击事件支持从鼠标位置动画展开
35+
if (typeof window !== 'undefined' && window.document && window.document.documentElement) {
36+
addEventListener(document.documentElement, 'click', getClickPosition);
37+
}
38+
2339
function noop() {}
2440
const modalProps = (defaultProps = {}) => {
2541
const props = {
@@ -32,6 +48,7 @@ const modalProps = (defaultProps = {}) => {
3248
title: PropTypes.any,
3349
/** 是否显示右上角的关闭按钮*/
3450
closable: PropTypes.bool,
51+
closeIcon: PropTypes.any,
3552
/** 点击确定回调*/
3653
// onOk: (e: React.MouseEvent<any>) => void,
3754
/** 点击模态框右上角叉、取消按钮、Props.maskClosable 值为 true 时的遮罩层或键盘按下 Esc 时的回调*/
@@ -88,30 +105,22 @@ export default {
88105
confirmLoading: false,
89106
visible: false,
90107
okType: 'primary',
91-
// okButtonDisabled: false,
92-
// cancelButtonDisabled: false,
93108
}),
94-
inject: {
95-
configProvider: { default: () => ConfigConsumerProps },
109+
data() {
110+
return {
111+
sVisible: !!this.visible,
112+
};
96113
},
97-
mounted() {
98-
if (mousePositionEventBinded) {
99-
return;
100-
}
101-
// 只有点击事件支持从鼠标位置动画展开
102-
addEventListener(document.documentElement, 'click', e => {
103-
mousePosition = {
104-
x: e.pageX,
105-
y: e.pageY,
106-
};
107-
// 100ms 内发生过点击事件,则从点击位置动画展示
108-
// 否则直接 zoom 展示
109-
// 这样可以兼容非点击方式展开
114+
watch: {
115+
visible(val) {
116+
// 点击位置动画 getClickPosition 在 render 后执行,需要延迟触发render
110117
setTimeout(() => {
111-
mousePosition = null;
112-
}, 100);
113-
});
114-
mousePositionEventBinded = true;
118+
this.sVisible = val;
119+
});
120+
},
121+
},
122+
inject: {
123+
configProvider: { default: () => ConfigConsumerProps },
115124
},
116125
// static info: ModalFunc;
117126
// static success: ModalFunc;
@@ -158,14 +167,16 @@ export default {
158167
render() {
159168
const {
160169
prefixCls: customizePrefixCls,
161-
visible,
170+
sVisible: visible,
162171
wrapClassName,
163172
centered,
173+
getContainer,
164174
$slots,
175+
$scopedSlots,
165176
$attrs,
166177
} = this;
167-
168-
const getPrefixCls = this.configProvider.getPrefixCls;
178+
const children = $scopedSlots.default ? $scopedSlots.default() : $slots.default;
179+
const { getPrefixCls, getPopupContainer: getContextPopupContainer } = this.configProvider;
169180
const prefixCls = getPrefixCls('modal', customizePrefixCls);
170181

171182
const defaultFooter = (
@@ -175,23 +186,25 @@ export default {
175186
scopedSlots={{ default: this.renderFooter }}
176187
/>
177188
);
178-
const closeIcon = (
189+
const closeIcon = getComponentFromProp(this, 'closeIcon');
190+
const closeIconToRender = (
179191
<span class={`${prefixCls}-close-x`}>
180-
<Icon class={`${prefixCls}-close-icon`} type={'close'} />
192+
{closeIcon || <Icon class={`${prefixCls}-close-icon`} type={'close'} />}
181193
</span>
182194
);
183195
const footer = getComponentFromProp(this, 'footer');
184196
const title = getComponentFromProp(this, 'title');
185197
const dialogProps = {
186198
props: {
187199
...this.$props,
200+
getContainer: getContainer === undefined ? getContextPopupContainer : getContainer,
188201
prefixCls,
189202
wrapClassName: classNames({ [`${prefixCls}-centered`]: !!centered }, wrapClassName),
190203
title,
191204
footer: footer === undefined ? defaultFooter : footer,
192205
visible: visible,
193206
mousePosition,
194-
closeIcon,
207+
closeIcon: closeIconToRender,
195208
},
196209
on: {
197210
...getListeners(this),
@@ -201,6 +214,6 @@ export default {
201214
style: getStyle(this),
202215
attrs: $attrs,
203216
};
204-
return <Dialog {...dialogProps}>{$slots.default}</Dialog>;
217+
return <Dialog {...dialogProps}>{children}</Dialog>;
205218
},
206219
};

components/modal/__tests__/Modal.test.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { mount } from '@vue/test-utils';
22
import Vue from 'vue';
33
import Modal from '..';
4+
import mountTest from '../../../tests/shared/mountTest';
45

56
const ModalTester = {
67
props: ['footer', 'visible'],
@@ -26,6 +27,7 @@ const ModalTester = {
2627
};
2728

2829
describe('Modal', () => {
30+
mountTest(Modal);
2931
it('render correctly', done => {
3032
const wrapper = mount({
3133
render() {
@@ -38,10 +40,10 @@ describe('Modal', () => {
3840
sync: false,
3941
});
4042
wrapper1.setProps({ visible: true });
41-
Vue.nextTick(() => {
43+
setTimeout(() => {
4244
expect(wrapper1.html()).toMatchSnapshot();
4345
done();
44-
});
46+
}, 10);
4547
});
4648

4749
it('render without footer', () => {

components/modal/demo/confirm.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<us>
77
#### Confirmation modal dialog
8-
To use `confirm()` to popup a confirmation modal dialog.
8+
To use `confirm()` to show a confirmation modal dialog.
99
</us>
1010

1111
```tpl

components/modal/demo/manual.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<us>
77
#### Manual to update destroy
8-
Manually updateing and destroying a modal from `Modal.method`.
8+
Manually updating and destroying a modal from `Modal.method`.
99
</us>
1010

1111
```tpl

components/modal/index.en-US.md

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,30 @@
11
## API
22

3-
| Property | Description | Type | Default |
4-
| --- | --- | --- | --- |
5-
| afterClose | Specify a function that will be called when modal is closed completely. | function | - |
6-
| bodyStyle | Body style for modal body element. Such as height, padding etc. | object | {} |
7-
| cancelText | Text of the Cancel button | string\|slot | `Cancel` |
8-
| centered | Centered Modal | Boolean | `false` |
9-
| closable | Whether a close (x) button is visible on top right of the modal dialog or not | boolean | true |
10-
| confirmLoading | Whether to apply loading visual effect for OK button or not | boolean | false |
11-
| destroyOnClose | Whether to unmount child components on onClose | boolean | false |
12-
| footer | Footer content, set as `:footer="null"` when you don't need default buttons | string\|slot | OK and Cancel buttons |
13-
| forceRender | Force render Modal | boolean | false |
14-
| getContainer | Return the mount node for Modal | (instance): HTMLElement | () => document.body |
15-
| mask | Whether show mask or not. | Boolean | true |
16-
| maskClosable | Whether to close the modal dialog when the mask (area outside the modal) is clicked | boolean | true |
17-
| maskStyle | Style for modal's mask element. | object | {} |
18-
| okText | Text of the OK button | string\|slot | `OK` |
19-
| okType | Button `type` of the OK button | string | `primary` |
20-
| okButtonProps | The ok button props, follow jsx [rules](https://github.com/vuejs/babel-plugin-transform-vue-jsx#difference-from-react-jsx) | {props: [ButtonProps](/components/button/#API), on: {}} | - |
21-
| cancelButtonProps | The cancel button props, follow jsx [rules](https://github.com/vuejs/babel-plugin-transform-vue-jsx#difference-from-react-jsx) | {props: [ButtonProps](/components/button/#API), on: {}} | - |
22-
| title | The modal dialog's title | string\|slot | - |
23-
| visible | Whether the modal dialog is visible or not | boolean | false |
24-
| width | Width of the modal dialog | string\|number | 520 |
25-
| wrapClassName | The class name of the container of the modal dialog | string | - |
26-
| zIndex | The `z-index` of the Modal | Number | 1000 |
3+
| Property | Description | Type | Default | Version |
4+
| --- | --- | --- | --- | --- |
5+
| afterClose | Specify a function that will be called when modal is closed completely. | function | - | |
6+
| bodyStyle | Body style for modal body element. Such as height, padding etc. | object | {} | |
7+
| cancelText | Text of the Cancel button | string\|slot | `Cancel` | |
8+
| centered | Centered Modal | Boolean | `false` | |
9+
| closable | Whether a close (x) button is visible on top right of the modal dialog or not | boolean | true | |
10+
| closeIcon | custom close icon | VNode \| slot | - | 1.5.0 |
11+
| confirmLoading | Whether to apply loading visual effect for OK button or not | boolean | false | |
12+
| destroyOnClose | Whether to unmount child components on onClose | boolean | false | |
13+
| footer | Footer content, set as `:footer="null"` when you don't need default buttons | string\|slot | OK and Cancel buttons | |
14+
| forceRender | Force render Modal | boolean | false | |
15+
| getContainer | Return the mount node for Modal | (instance): HTMLElement | () => document.body | |
16+
| mask | Whether show mask or not. | Boolean | true | |
17+
| maskClosable | Whether to close the modal dialog when the mask (area outside the modal) is clicked | boolean | true | |
18+
| maskStyle | Style for modal's mask element. | object | {} | |
19+
| okText | Text of the OK button | string\|slot | `OK` | |
20+
| okType | Button `type` of the OK button | string | `primary` | |
21+
| okButtonProps | The ok button props, follow jsx [rules](https://github.com/vuejs/babel-plugin-transform-vue-jsx#difference-from-react-jsx) | {props: [ButtonProps](/components/button/#API), on: {}} | - | |
22+
| cancelButtonProps | The cancel button props, follow jsx [rules](https://github.com/vuejs/babel-plugin-transform-vue-jsx#difference-from-react-jsx) | {props: [ButtonProps](/components/button/#API), on: {}} | - | |
23+
| title | The modal dialog's title | string\|slot | - | |
24+
| visible | Whether the modal dialog is visible or not | boolean | false | |
25+
| width | Width of the modal dialog | string\|number | 520 | |
26+
| wrapClassName | The class name of the container of the modal dialog | string | - | |
27+
| zIndex | The `z-index` of the Modal | Number | 1000 | |
2728

2829
### events
2930

components/modal/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const warning = function(props) {
5555
};
5656
const warn = warning;
5757

58-
const confirm = function(props) {
58+
const confirm = function confirmFn(props) {
5959
const config = {
6060
type: 'confirm',
6161
okCancel: true,
@@ -70,7 +70,7 @@ Modal.warning = warning;
7070
Modal.warn = warn;
7171
Modal.confirm = confirm;
7272

73-
Modal.destroyAll = function() {
73+
Modal.destroyAll = function destroyAllFn() {
7474
while (destroyFns.length) {
7575
const close = destroyFns.pop();
7676
if (close) {

components/modal/index.zh-CN.md

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
11
## API
22

3-
| 参数 | 说明 | 类型 | 默认值 |
4-
| --- | --- | --- | --- |
5-
| afterClose | Modal 完全关闭后的回调 | function ||
6-
| bodyStyle | Modal body 样式 | object | {} |
7-
| cancelText | 取消按钮文字 | string\| slot | 取消 |
8-
| centered | 垂直居中展示 Modal | Boolean | `false` |
9-
| closable | 是否显示右上角的关闭按钮 | boolean | true |
10-
| confirmLoading | 确定按钮 loading | boolean ||
11-
| destroyOnClose | 关闭时销毁 Modal 里的子元素 | boolean | false |
12-
| footer | 底部内容,当不需要默认底部按钮时,可以设为 `:footer="null"` | string\|slot | 确定取消按钮 |
13-
| forceRender | 强制渲染 Modal | boolean | false |
14-
| getContainer | 指定 Modal 挂载的 HTML 节点 | (instance): HTMLElement | () => document.body |
15-
| keyboard | 是否支持键盘 esc 关闭 | boolean | true |
16-
| mask | 是否展示遮罩 | Boolean | true |
17-
| maskClosable | 点击蒙层是否允许关闭 | boolean | true |
18-
| maskStyle | 遮罩样式 | object | {} |
19-
| okText | 确认按钮文字 | string\|slot | 确定 |
20-
| okType | 确认按钮类型 | string | primary |
21-
| okButtonProps | ok 按钮 props, 遵循 jsx[规范](https://github.com/vuejs/babel-plugin-transform-vue-jsx#difference-from-react-jsx) | {props: [ButtonProps](/components/button/#API), on: {}} | - |
22-
| cancelButtonProps | cancel 按钮 props, 遵循 jsx[规范](https://github.com/vuejs/babel-plugin-transform-vue-jsx#difference-from-react-jsx) | {props: [ButtonProps](/components/button/#API), on: {}} | - |
23-
| title | 标题 | string\|slot ||
24-
| visible(v-model) | 对话框是否可见 | boolean ||
25-
| width | 宽度 | string\|number | 520 |
26-
| wrapClassName | 对话框外层容器的类名 | string | - |
27-
| zIndex | 设置 Modal 的 `z-index` | Number | 1000 |
3+
| 参数 | 说明 | 类型 | 默认值 | 版本 |
4+
| --- | --- | --- | --- | --- |
5+
| afterClose | Modal 完全关闭后的回调 | function || |
6+
| bodyStyle | Modal body 样式 | object | {} | |
7+
| cancelText | 取消按钮文字 | string\| slot | 取消 | |
8+
| centered | 垂直居中展示 Modal | Boolean | `false` | |
9+
| closable | 是否显示右上角的关闭按钮 | boolean | true | |
10+
| closeIcon | 自定义关闭图标 | VNode \| slot | - | 1.5.0 |
11+
| confirmLoading | 确定按钮 loading | boolean || |
12+
| destroyOnClose | 关闭时销毁 Modal 里的子元素 | boolean | false | |
13+
| footer | 底部内容,当不需要默认底部按钮时,可以设为 `:footer="null"` | string\|slot | 确定取消按钮 | |
14+
| forceRender | 强制渲染 Modal | boolean | false | |
15+
| getContainer | 指定 Modal 挂载的 HTML 节点 | (instance): HTMLElement | () => document.body | |
16+
| keyboard | 是否支持键盘 esc 关闭 | boolean | true | |
17+
| mask | 是否展示遮罩 | Boolean | true | |
18+
| maskClosable | 点击蒙层是否允许关闭 | boolean | true | |
19+
| maskStyle | 遮罩样式 | object | {} | |
20+
| okText | 确认按钮文字 | string\|slot | 确定 | |
21+
| okType | 确认按钮类型 | string | primary | |
22+
| okButtonProps | ok 按钮 props, 遵循 jsx[规范](https://github.com/vuejs/babel-plugin-transform-vue-jsx#difference-from-react-jsx) | {props: [ButtonProps](/components/button/#API), on: {}} | - | |
23+
| cancelButtonProps | cancel 按钮 props, 遵循 jsx[规范](https://github.com/vuejs/babel-plugin-transform-vue-jsx#difference-from-react-jsx) | {props: [ButtonProps](/components/button/#API), on: {}} | - | |
24+
| title | 标题 | string\|slot || |
25+
| visible(v-model) | 对话框是否可见 | boolean || |
26+
| width | 宽度 | string\|number | 520 | |
27+
| wrapClassName | 对话框外层容器的类名 | string | - | |
28+
| zIndex | 设置 Modal 的 `z-index` | Number | 1000 | |
2829

2930
### 事件
3031

0 commit comments

Comments
 (0)