diff --git a/docs/modal/api.md b/docs/modal/api.md
index 8d7dde4..46a6eab 100644
--- a/docs/modal/api.md
+++ b/docs/modal/api.md
@@ -17,6 +17,12 @@ Modal 组件,用于弹出内容,实现 alert、confirm、prompt、loading、
Modal 形式,不设置时为普通的模态窗口。
+##### `isOpen`
+
+> PropType: `bool`
+
+Modal 是否为打开状态。
+
##### `title`
> PropType: `node`
@@ -53,6 +59,8 @@ Modal 标题。
用户点击「确定」或「取消」按钮时的处理函数。
+`role` 为 `prompt` 时,通过该函数的返回值可以控制按钮点击时是否关闭 Modal。
+
##### `onOpen`
> PropType: `func`
@@ -65,10 +73,18 @@ Modal 打开时的回调函数。
Modal 关闭以后的回调函数。
+##### `onRequestClose`
+
+> PropType: `func`
+
+用户请求关闭操作时的处理函数。
+
#### 方法
-调用方式见示例代码。
+~~调用方式见示例代码。~~
+
+**Modal 实现方式调整,请勿直接调用其方法。**
##### `.open()`
diff --git a/kitchen-sink/pages/ModalExample.js b/kitchen-sink/pages/ModalExample.js
index b2017b3..fd930cb 100644
--- a/kitchen-sink/pages/ModalExample.js
+++ b/kitchen-sink/pages/ModalExample.js
@@ -1,4 +1,5 @@
import React from 'react';
+import ReactDOM from 'react-dom';
import {
Container,
Group,
@@ -9,40 +10,81 @@ import {
} from 'amazeui-touch';
const ModalExample = React.createClass({
- open() {
- this.refs.modal.open();
+ getInitialState() {
+ return {
+ isModalOpen: false,
+ };
},
- close() {
- this.refs.modal.close();
+ openModal() {
+ this.setState({
+ isModalOpen: true,
+ })
+ },
+
+ closeModal() {
+ this.setState({
+ isModalOpen: false,
+ });
+ },
+
+ onOpen() {
+ console.log('modal open....');
+ },
+
+ onClosed() {
+ console.log('modal closed....');
},
handleAction(data) {
- let role = this.refs.modal.props.role;
+ let role = this.getModalRole();
// 确定和取消放在一起处理
// data 为 true 时为 `确定`
if (role === 'confirm') {
- console.log('你的选择是:「' + (data ? '确定' : '取消') + '」')
+ console.log('你的选择是:「' + (data ? '确定' : '取消') + '」')
} else if (role === 'prompt') {
+ // `prompt` 类型支持通过返回值控制是否关闭 Modal
+
+ // 点击取消时 data 的值为 null
+
+ // 简单的验证逻辑
+ // 仅适用于一个输入框的场景,多个输入框的 data 值为 `['', '', ...]`
+ if (data === '') {
+ console.error('赶紧交出来啊,不然...你懂的...');
+ return false; // 点击确定时不关闭 Modal
+ }
+
console.log('输入的数据是:', data);
+ return true; // 点击确定时关闭 Modal
}
},
+ getModalRole() {
+ return this.props.modalProps.role;
+ },
+
render() {
return (
-
- {React.cloneElement(React.Children.only(this.props.children), {
- ref: 'modal',
- onAction: this.handleAction
- })}
+
+ {this.getModalRole() !== 'loading' && this.props.children}
+
);
}
@@ -55,12 +97,13 @@ const ModalExamples = React.createClass({
-
-
- Modal 内容
-
+
+ Hello, Modal 内容
@@ -69,28 +112,25 @@ const ModalExamples = React.createClass({
>
-
- 这一个 Alert 窗口。
-
+ 这一个 Alert 窗口。
-
-
- 这一个 Confirm 窗口。
-
+ 这一个 Confirm 窗口。
@@ -99,14 +139,13 @@ const ModalExamples = React.createClass({
>
-
- 输入你的 IQ 卡密码:
-
-
+ 输入你的 IQ 卡密码:
+
@@ -115,16 +154,15 @@ const ModalExamples = React.createClass({
>
-
-
-
-
-
-
+
+
+
+
@@ -133,12 +171,11 @@ const ModalExamples = React.createClass({
>
-
-
+ modalProps={{
+ title: '使劲加载中...',
+ role: 'loading'
+ }}
+ />
-
-
-
- 分享到
- 微信
- Twitter
-
-
-
+
+
+ 分享到
+ 微信
+
+ Twitter
+
+
-
-
- 为你封了国境
为你赦了罪
为你撤了历史记载
为你涂了装扮
为你喝了醉
为你建了城池围墙
一颗热的心穿着冰冷外衣
一张白的脸漆上多少褪色的情节
在我的空虚身体里面
爱上哪个肤浅的王位
在你的空虚宝座里面
爱过什麽女爵的滋味
为你封了国境
为你赦了罪
为你撤了历史记载
一颗热的心
穿着冰冷外衣
一张白的脸
漆上多少褪色的情节
在我的空虚身体里面
爱上哪个肤浅的王位
在你的空虚宝座里面
爱过什麽女爵的滋味
在我的空虚身体里面
爱上哪个肤浅的王位
在你的空虚宝座里面
爱过什麽女爵的滋味
-
+
+ 为你封了国境
为你赦了罪
为你撤了历史记载
为你涂了装扮
为你喝了醉
为你建了城池围墙
一颗热的心穿着冰冷外衣
一张白的脸漆上多少褪色的情节
在我的空虚身体里面
爱上哪个肤浅的王位
在你的空虚宝座里面
爱过什麽女爵的滋味
为你封了国境
+
为你赦了罪
为你撤了历史记载
一颗热的心
穿着冰冷外衣
一张白的脸
漆上多少褪色的情节
在我的空虚身体里面
爱上哪个肤浅的王位
在你的空虚宝座里面
爱过什麽女爵的滋味
在我的空虚身体里面
爱上哪个肤浅的王位
在你的空虚宝座里面
爱过什麽女爵的滋味
+
diff --git a/src/js/index.js b/src/js/index.js
index 7ff15b2..8b287ec 100644
--- a/src/js/index.js
+++ b/src/js/index.js
@@ -21,7 +21,7 @@ export {default as Icon} from './Icon';
export {default as Field} from './Field';
export {default as List} from './List';
export {default as Loader} from './Loader';
-export {default as Modal} from './Modal';
+export {default as Modal} from './modal';
export {default as NavBar} from './NavBar';
export {default as Notification} from './Notification';
export {default as OffCanvas} from './OffCanvas';
diff --git a/src/js/Modal.js b/src/js/modal/Modal.js
similarity index 80%
rename from src/js/Modal.js
rename to src/js/modal/Modal.js
index deb1fa0..a83710b 100644
--- a/src/js/Modal.js
+++ b/src/js/modal/Modal.js
@@ -2,36 +2,41 @@
* @see https://github.com/yuanyan/boron
*/
-import React from 'react';
+import React, {
+ PropTypes,
+ createClass,
+} from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
import CSSTransitionGroup from 'react-addons-css-transition-group';
-import ClassNameMixin from './mixins/ClassNameMixin';
-import CSSCore from './utils/CSSCore';
-import Events from './utils/Events';
-import TransitionEvents from './utils/TransitionEvents';
-import Button from './Button';
-import Icon from './Icon';
-import Loader from './Loader';
+import ClassNameMixin from '../mixins/ClassNameMixin';
+import TransitionEvents from '../utils/TransitionEvents';
+import Button from '../Button';
+import Icon from '../Icon';
+import Loader from '../Loader';
// MUST be equal to $modal-duration in _modal.scss
const TRANSITION_TIMEOUT = 300;
-const Modal = React.createClass({
+function noop() {
+}
+
+const Modal = createClass({
mixins: [ClassNameMixin],
propTypes: {
- classPrefix: React.PropTypes.string,
- role: React.PropTypes.oneOf(['alert', 'confirm', 'prompt', 'loading',
+ classPrefix: PropTypes.string,
+ role: PropTypes.oneOf(['alert', 'confirm', 'prompt', 'loading',
'actions', 'popup']),
- title: React.PropTypes.node,
- confirmText: React.PropTypes.string,
- cancelText: React.PropTypes.string,
- closeBtn: React.PropTypes.bool,
- closeViaBackdrop: React.PropTypes.bool,
- onAction: React.PropTypes.func,
- onOpen: React.PropTypes.func,
- onClosed: React.PropTypes.func,
+ title: PropTypes.node,
+ confirmText: PropTypes.string,
+ cancelText: PropTypes.string,
+ closeBtn: PropTypes.bool,
+ closeViaBackdrop: PropTypes.bool,
+ onAction: PropTypes.func,
+ onOpen: PropTypes.func,
+ onClosed: PropTypes.func,
+ onRequestClose: PropTypes.func,
},
getDefaultProps() {
@@ -40,12 +45,10 @@ const Modal = React.createClass({
confirmText: '确定',
cancelText: '取消',
closeBtn: true,
- onAction: () => {
- },
- onOpen: () => {
- },
- onClosed: () => {
- },
+ onAction: noop,
+ onOpen: noop,
+ onClosed: noop,
+ onRequestClose: noop,
};
},
@@ -56,16 +59,24 @@ const Modal = React.createClass({
};
},
- isClosed() {
- return this.state.closed;
+ componentDidMount() {
+ if (this.props.isOpen) {
+ this.open();
+ }
},
- handleBackdropClick(e) {
- if (e.target !== e.currentTarget || !this.props.closeViaBackdrop) {
- return;
+ componentWillReceiveProps(nextProps) {
+ let isOpen = this.props.isOpen;
+
+ if (!isOpen && nextProps.isOpen) {
+ this.open();
+ } else if (isOpen && !nextProps.isOpen) {
+ this.close();
}
+ },
- this.close();
+ isClosed() {
+ return this.state.closed;
},
isPopup() {
@@ -91,13 +102,33 @@ const Modal = React.createClass({
return (data.length === 0) ? null : ((data.length === 1) ? data[0] : data);
},
- handleSelect(data, e) {
- if (this.props.role === 'prompt' && data) {
+ // data === null: prompt -> canceled
+ // data === true: confirm -> confirmed
+ // data === false: confirm -> canceled
+ handleAction(data, e) {
+ let {
+ role,
+ onAction,
+ } = this.props;
+ let willClose = true;
+
+ if (role === 'prompt' && data) {
data = this.getFieldData();
+
+ willClose = onAction.call(this, data, e);
+ } else {
+ onAction.call(this, data, e);
}
- this.close();
- this.props.onAction.call(this, data, e);
+ willClose && this.requestClose(e);
+ },
+
+ handleBackdropClick(e) {
+ if (e.target !== e.currentTarget || !this.props.closeViaBackdrop) {
+ return;
+ }
+
+ this.requestClose(e);
},
open() {
@@ -111,6 +142,7 @@ const Modal = React.createClass({
}
},
+ // Only for instance self calling
close() {
if (this.isClosed() || this.state.isClosing) {
return;
@@ -121,6 +153,11 @@ const Modal = React.createClass({
});
},
+ // for user actions
+ requestClose(e) {
+ this.props.onRequestClose(e);
+ },
+
handleClosed() {
this.setState({
closed: true,
@@ -141,7 +178,7 @@ const Modal = React.createClass({
{this.props.children}
@@ -200,7 +237,7 @@ const Modal = React.createClass({
) : null;
@@ -234,7 +271,7 @@ const Modal = React.createClass({
buttons = (
{confirmText}
@@ -247,7 +284,7 @@ const Modal = React.createClass({
return (
{text}
diff --git a/src/js/modal/ModalPortal.js b/src/js/modal/ModalPortal.js
new file mode 100644
index 0000000..80ec84d
--- /dev/null
+++ b/src/js/modal/ModalPortal.js
@@ -0,0 +1,55 @@
+import React, {createClass} from 'react';
+import ReactDOM, {
+ unmountComponentAtNode,
+ unstable_renderSubtreeIntoContainer as renderSubtreeIntoContainer
+} from 'react-dom';
+import CSSCore from '../utils/CSSCore';
+import Modal from './Modal';
+
+// const bodyElement = canUseDOM ? document.body : {appendChild: () => {}};
+const body = document.body;
+const bodyClassName = 'has-modal-open';
+
+const ModalPortal = createClass({
+ propTypes: {
+ isOpen: React.PropTypes.bool.isRequired,
+ },
+
+ getDefaultProps() {
+ return {
+ isOpen: false,
+ };
+ },
+
+ componentDidMount() {
+ this.node = document.createElement('div');
+ this.node.className = '__modal-portal';
+ body.appendChild(this.node);
+ this.renderModal(this.props);
+ },
+
+ componentWillReceiveProps(nextProps) {
+ this.renderModal(nextProps);
+ },
+
+ componentWillUnmount() {
+ unmountComponentAtNode(this.node);
+ body.removeChild(this.node);
+ CSSCore.removeClass(body, bodyClassName);
+ },
+
+ renderModal(props) {
+ CSSCore[(props.isOpen ? 'add' : 'remove') + 'Class'](body, bodyClassName);
+ this.portal = renderSubtreeIntoContainer(
+ this,
+ ,
+ this.node
+ );
+ },
+
+ render() {
+ return null;
+ }
+});
+
+export default ModalPortal;
diff --git a/src/js/modal/index.js b/src/js/modal/index.js
new file mode 100644
index 0000000..0cbdbc0
--- /dev/null
+++ b/src/js/modal/index.js
@@ -0,0 +1 @@
+export default from './ModalPortal';