From e43f6905bef4b6d4c6397d0dae8f7fa29bad7cf5 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Sat, 22 Feb 2020 13:22:24 +0800 Subject: [PATCH] feat: modal add closeIcon support --- build/config.js | 2 +- components/modal/ActionButton.jsx | 5 +- components/modal/ConfirmDialog.jsx | 7 ++- components/modal/Modal.jsx | 71 ++++++++++++++---------- components/modal/__tests__/Modal.test.js | 6 +- components/modal/demo/confirm.md | 2 +- components/modal/demo/manual.md | 2 +- components/modal/index.en-US.md | 49 ++++++++-------- components/modal/index.js | 4 +- components/modal/index.zh-CN.md | 51 ++++++++--------- components/modal/locale.js | 2 +- types/modal.d.ts | 2 + 12 files changed, 113 insertions(+), 90 deletions(-) diff --git a/build/config.js b/build/config.js index 7498a5ca0..6ad118ad5 100644 --- a/build/config.js +++ b/build/config.js @@ -1,5 +1,5 @@ module.exports = { dev: { - componentName: 'message', // dev components + componentName: 'modal', // dev components }, }; diff --git a/components/modal/ActionButton.jsx b/components/modal/ActionButton.jsx index 264214aa8..882a4a0fa 100644 --- a/components/modal/ActionButton.jsx +++ b/components/modal/ActionButton.jsx @@ -48,7 +48,10 @@ export default { // this.setState({ loading: false }); closeModal(...args); }, - () => { + e => { + // Emit error when catch promise reject + // eslint-disable-next-line no-console + console.error(e); // See: https://github.com/ant-design/ant-design/issues/6183 this.setState({ loading: false }); }, diff --git a/components/modal/ConfirmDialog.jsx b/components/modal/ConfirmDialog.jsx index 154a0b3b1..4fd38e848 100644 --- a/components/modal/ConfirmDialog.jsx +++ b/components/modal/ConfirmDialog.jsx @@ -27,6 +27,7 @@ export default { } = props; warning( !('iconType' in props), + 'Modal', `The property 'iconType' is deprecated. Use the property 'icon' instead.`, ); const icon = props.icon ? props.icon : iconType; @@ -92,9 +93,9 @@ export default {
{iconNode} - - {typeof props.title === 'function' ? props.title(h) : props.title} - + {props.title === undefined ? null : ( + {props.title} + )}
{typeof props.content === 'function' ? props.content(h) : props.content}
diff --git a/components/modal/Modal.jsx b/components/modal/Modal.jsx index 6d9a42da0..09b8e3194 100644 --- a/components/modal/Modal.jsx +++ b/components/modal/Modal.jsx @@ -19,7 +19,23 @@ import { import { ConfigConsumerProps } from '../config-provider'; let mousePosition = null; -let mousePositionEventBinded = false; +// ref: https://github.com/ant-design/ant-design/issues/15795 +const getClickPosition = e => { + mousePosition = { + x: e.pageX, + y: e.pageY, + }; + // 100ms 内发生过点击事件,则从点击位置动画展示 + // 否则直接 zoom 展示 + // 这样可以兼容非点击方式展开 + setTimeout(() => (mousePosition = null), 100); +}; + +// 只有点击事件支持从鼠标位置动画展开 +if (typeof window !== 'undefined' && window.document && window.document.documentElement) { + addEventListener(document.documentElement, 'click', getClickPosition); +} + function noop() {} const modalProps = (defaultProps = {}) => { const props = { @@ -32,6 +48,7 @@ const modalProps = (defaultProps = {}) => { title: PropTypes.any, /** 是否显示右上角的关闭按钮*/ closable: PropTypes.bool, + closeIcon: PropTypes.any, /** 点击确定回调*/ // onOk: (e: React.MouseEvent) => void, /** 点击模态框右上角叉、取消按钮、Props.maskClosable 值为 true 时的遮罩层或键盘按下 Esc 时的回调*/ @@ -88,31 +105,23 @@ export default { confirmLoading: false, visible: false, okType: 'primary', - // okButtonDisabled: false, - // cancelButtonDisabled: false, }), + data() { + return { + sVisible: !!this.visible, + }; + }, + watch: { + visible(val) { + // 点击位置动画 getClickPosition 在 render 后执行,需要延迟触发render + setTimeout(() => { + this.sVisible = val; + }); + }, + }, inject: { configProvider: { default: () => ConfigConsumerProps }, }, - mounted() { - if (mousePositionEventBinded) { - return; - } - // 只有点击事件支持从鼠标位置动画展开 - addEventListener(document.documentElement, 'click', e => { - mousePosition = { - x: e.pageX, - y: e.pageY, - }; - // 100ms 内发生过点击事件,则从点击位置动画展示 - // 否则直接 zoom 展示 - // 这样可以兼容非点击方式展开 - setTimeout(() => { - mousePosition = null; - }, 100); - }); - mousePositionEventBinded = true; - }, // static info: ModalFunc; // static success: ModalFunc; // static error: ModalFunc; @@ -158,14 +167,16 @@ export default { render() { const { prefixCls: customizePrefixCls, - visible, + sVisible: visible, wrapClassName, centered, + getContainer, $slots, + $scopedSlots, $attrs, } = this; - - const getPrefixCls = this.configProvider.getPrefixCls; + const children = $scopedSlots.default ? $scopedSlots.default() : $slots.default; + const { getPrefixCls, getPopupContainer: getContextPopupContainer } = this.configProvider; const prefixCls = getPrefixCls('modal', customizePrefixCls); const defaultFooter = ( @@ -175,9 +186,10 @@ export default { scopedSlots={{ default: this.renderFooter }} /> ); - const closeIcon = ( + const closeIcon = getComponentFromProp(this, 'closeIcon'); + const closeIconToRender = ( - + {closeIcon || } ); const footer = getComponentFromProp(this, 'footer'); @@ -185,13 +197,14 @@ export default { const dialogProps = { props: { ...this.$props, + getContainer: getContainer === undefined ? getContextPopupContainer : getContainer, prefixCls, wrapClassName: classNames({ [`${prefixCls}-centered`]: !!centered }, wrapClassName), title, footer: footer === undefined ? defaultFooter : footer, visible: visible, mousePosition, - closeIcon, + closeIcon: closeIconToRender, }, on: { ...getListeners(this), @@ -201,6 +214,6 @@ export default { style: getStyle(this), attrs: $attrs, }; - return {$slots.default}; + return {children}; }, }; diff --git a/components/modal/__tests__/Modal.test.js b/components/modal/__tests__/Modal.test.js index b1d0474c0..50e133f1e 100644 --- a/components/modal/__tests__/Modal.test.js +++ b/components/modal/__tests__/Modal.test.js @@ -1,6 +1,7 @@ import { mount } from '@vue/test-utils'; import Vue from 'vue'; import Modal from '..'; +import mountTest from '../../../tests/shared/mountTest'; const ModalTester = { props: ['footer', 'visible'], @@ -26,6 +27,7 @@ const ModalTester = { }; describe('Modal', () => { + mountTest(Modal); it('render correctly', done => { const wrapper = mount({ render() { @@ -38,10 +40,10 @@ describe('Modal', () => { sync: false, }); wrapper1.setProps({ visible: true }); - Vue.nextTick(() => { + setTimeout(() => { expect(wrapper1.html()).toMatchSnapshot(); done(); - }); + }, 10); }); it('render without footer', () => { diff --git a/components/modal/demo/confirm.md b/components/modal/demo/confirm.md index b515d010d..ed6e578e3 100644 --- a/components/modal/demo/confirm.md +++ b/components/modal/demo/confirm.md @@ -5,7 +5,7 @@ #### Confirmation modal dialog -To use `confirm()` to popup a confirmation modal dialog. +To use `confirm()` to show a confirmation modal dialog. ```tpl diff --git a/components/modal/demo/manual.md b/components/modal/demo/manual.md index 5fab51243..8633dbac7 100644 --- a/components/modal/demo/manual.md +++ b/components/modal/demo/manual.md @@ -5,7 +5,7 @@ #### Manual to update destroy -Manually updateing and destroying a modal from `Modal.method`. +Manually updating and destroying a modal from `Modal.method`. ```tpl diff --git a/components/modal/index.en-US.md b/components/modal/index.en-US.md index a5484beec..03d1bd3bc 100644 --- a/components/modal/index.en-US.md +++ b/components/modal/index.en-US.md @@ -1,29 +1,30 @@ ## API -| Property | Description | Type | Default | -| --- | --- | --- | --- | -| afterClose | Specify a function that will be called when modal is closed completely. | function | - | -| bodyStyle | Body style for modal body element. Such as height, padding etc. | object | {} | -| cancelText | Text of the Cancel button | string\|slot | `Cancel` | -| centered | Centered Modal | Boolean | `false` | -| closable | Whether a close (x) button is visible on top right of the modal dialog or not | boolean | true | -| confirmLoading | Whether to apply loading visual effect for OK button or not | boolean | false | -| destroyOnClose | Whether to unmount child components on onClose | boolean | false | -| footer | Footer content, set as `:footer="null"` when you don't need default buttons | string\|slot | OK and Cancel buttons | -| forceRender | Force render Modal | boolean | false | -| getContainer | Return the mount node for Modal | (instance): HTMLElement | () => document.body | -| mask | Whether show mask or not. | Boolean | true | -| maskClosable | Whether to close the modal dialog when the mask (area outside the modal) is clicked | boolean | true | -| maskStyle | Style for modal's mask element. | object | {} | -| okText | Text of the OK button | string\|slot | `OK` | -| okType | Button `type` of the OK button | string | `primary` | -| 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: {}} | - | -| 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: {}} | - | -| title | The modal dialog's title | string\|slot | - | -| visible | Whether the modal dialog is visible or not | boolean | false | -| width | Width of the modal dialog | string\|number | 520 | -| wrapClassName | The class name of the container of the modal dialog | string | - | -| zIndex | The `z-index` of the Modal | Number | 1000 | +| Property | Description | Type | Default | Version | +| --- | --- | --- | --- | --- | +| afterClose | Specify a function that will be called when modal is closed completely. | function | - | | +| bodyStyle | Body style for modal body element. Such as height, padding etc. | object | {} | | +| cancelText | Text of the Cancel button | string\|slot | `Cancel` | | +| centered | Centered Modal | Boolean | `false` | | +| closable | Whether a close (x) button is visible on top right of the modal dialog or not | boolean | true | | +| closeIcon | custom close icon | VNode \| slot | - | 1.5.0 | +| confirmLoading | Whether to apply loading visual effect for OK button or not | boolean | false | | +| destroyOnClose | Whether to unmount child components on onClose | boolean | false | | +| footer | Footer content, set as `:footer="null"` when you don't need default buttons | string\|slot | OK and Cancel buttons | | +| forceRender | Force render Modal | boolean | false | | +| getContainer | Return the mount node for Modal | (instance): HTMLElement | () => document.body | | +| mask | Whether show mask or not. | Boolean | true | | +| maskClosable | Whether to close the modal dialog when the mask (area outside the modal) is clicked | boolean | true | | +| maskStyle | Style for modal's mask element. | object | {} | | +| okText | Text of the OK button | string\|slot | `OK` | | +| okType | Button `type` of the OK button | string | `primary` | | +| 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: {}} | - | | +| 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: {}} | - | | +| title | The modal dialog's title | string\|slot | - | | +| visible | Whether the modal dialog is visible or not | boolean | false | | +| width | Width of the modal dialog | string\|number | 520 | | +| wrapClassName | The class name of the container of the modal dialog | string | - | | +| zIndex | The `z-index` of the Modal | Number | 1000 | | ### events diff --git a/components/modal/index.js b/components/modal/index.js index c853249f2..bbf989ca6 100644 --- a/components/modal/index.js +++ b/components/modal/index.js @@ -55,7 +55,7 @@ const warning = function(props) { }; const warn = warning; -const confirm = function(props) { +const confirm = function confirmFn(props) { const config = { type: 'confirm', okCancel: true, @@ -70,7 +70,7 @@ Modal.warning = warning; Modal.warn = warn; Modal.confirm = confirm; -Modal.destroyAll = function() { +Modal.destroyAll = function destroyAllFn() { while (destroyFns.length) { const close = destroyFns.pop(); if (close) { diff --git a/components/modal/index.zh-CN.md b/components/modal/index.zh-CN.md index fe62bb5ef..6902de757 100644 --- a/components/modal/index.zh-CN.md +++ b/components/modal/index.zh-CN.md @@ -1,30 +1,31 @@ ## API -| 参数 | 说明 | 类型 | 默认值 | -| --- | --- | --- | --- | -| afterClose | Modal 完全关闭后的回调 | function | 无 | -| bodyStyle | Modal body 样式 | object | {} | -| cancelText | 取消按钮文字 | string\| slot | 取消 | -| centered | 垂直居中展示 Modal | Boolean | `false` | -| closable | 是否显示右上角的关闭按钮 | boolean | true | -| confirmLoading | 确定按钮 loading | boolean | 无 | -| destroyOnClose | 关闭时销毁 Modal 里的子元素 | boolean | false | -| footer | 底部内容,当不需要默认底部按钮时,可以设为 `:footer="null"` | string\|slot | 确定取消按钮 | -| forceRender | 强制渲染 Modal | boolean | false | -| getContainer | 指定 Modal 挂载的 HTML 节点 | (instance): HTMLElement | () => document.body | -| keyboard | 是否支持键盘 esc 关闭 | boolean | true | -| mask | 是否展示遮罩 | Boolean | true | -| maskClosable | 点击蒙层是否允许关闭 | boolean | true | -| maskStyle | 遮罩样式 | object | {} | -| okText | 确认按钮文字 | string\|slot | 确定 | -| okType | 确认按钮类型 | string | primary | -| okButtonProps | ok 按钮 props, 遵循 jsx[规范](https://github.com/vuejs/babel-plugin-transform-vue-jsx#difference-from-react-jsx) | {props: [ButtonProps](/components/button/#API), on: {}} | - | -| cancelButtonProps | cancel 按钮 props, 遵循 jsx[规范](https://github.com/vuejs/babel-plugin-transform-vue-jsx#difference-from-react-jsx) | {props: [ButtonProps](/components/button/#API), on: {}} | - | -| title | 标题 | string\|slot | 无 | -| visible(v-model) | 对话框是否可见 | boolean | 无 | -| width | 宽度 | string\|number | 520 | -| wrapClassName | 对话框外层容器的类名 | string | - | -| zIndex | 设置 Modal 的 `z-index` | Number | 1000 | +| 参数 | 说明 | 类型 | 默认值 | 版本 | +| --- | --- | --- | --- | --- | +| afterClose | Modal 完全关闭后的回调 | function | 无 | | +| bodyStyle | Modal body 样式 | object | {} | | +| cancelText | 取消按钮文字 | string\| slot | 取消 | | +| centered | 垂直居中展示 Modal | Boolean | `false` | | +| closable | 是否显示右上角的关闭按钮 | boolean | true | | +| closeIcon | 自定义关闭图标 | VNode \| slot | - | 1.5.0 | +| confirmLoading | 确定按钮 loading | boolean | 无 | | +| destroyOnClose | 关闭时销毁 Modal 里的子元素 | boolean | false | | +| footer | 底部内容,当不需要默认底部按钮时,可以设为 `:footer="null"` | string\|slot | 确定取消按钮 | | +| forceRender | 强制渲染 Modal | boolean | false | | +| getContainer | 指定 Modal 挂载的 HTML 节点 | (instance): HTMLElement | () => document.body | | +| keyboard | 是否支持键盘 esc 关闭 | boolean | true | | +| mask | 是否展示遮罩 | Boolean | true | | +| maskClosable | 点击蒙层是否允许关闭 | boolean | true | | +| maskStyle | 遮罩样式 | object | {} | | +| okText | 确认按钮文字 | string\|slot | 确定 | | +| okType | 确认按钮类型 | string | primary | | +| okButtonProps | ok 按钮 props, 遵循 jsx[规范](https://github.com/vuejs/babel-plugin-transform-vue-jsx#difference-from-react-jsx) | {props: [ButtonProps](/components/button/#API), on: {}} | - | | +| cancelButtonProps | cancel 按钮 props, 遵循 jsx[规范](https://github.com/vuejs/babel-plugin-transform-vue-jsx#difference-from-react-jsx) | {props: [ButtonProps](/components/button/#API), on: {}} | - | | +| title | 标题 | string\|slot | 无 | | +| visible(v-model) | 对话框是否可见 | boolean | 无 | | +| width | 宽度 | string\|number | 520 | | +| wrapClassName | 对话框外层容器的类名 | string | - | | +| zIndex | 设置 Modal 的 `z-index` | Number | 1000 | | ### 事件 diff --git a/components/modal/locale.js b/components/modal/locale.js index 5b1f0d42f..8eb9d0e90 100644 --- a/components/modal/locale.js +++ b/components/modal/locale.js @@ -1,4 +1,4 @@ -import defaultLocale from '../locale-provider/default'; +import defaultLocale from '../locale/default'; // export interface ModalLocale { // okText: string; diff --git a/types/modal.d.ts b/types/modal.d.ts index b49ccd77b..6deba79ff 100644 --- a/types/modal.d.ts +++ b/types/modal.d.ts @@ -179,6 +179,8 @@ export declare class Modal extends AntdComponent { */ closable: boolean; + closeIcon: any; + /** * Whether to apply loading visual effect for OK button or not * @default false