zent
Version:
一套前端设计语言和基于React的实现
253 lines (212 loc) • 7.01 kB
JavaScript
import React, { Component, PropTypes } from 'react';
import Popover from 'zent-popover';
import Button from 'zent-button';
import cx from 'zent-utils/classnames';
import noop from 'zent-utils/lodash/noop';
import isFunction from 'zent-utils/lodash/isFunction';
import isPromise from 'zent-utils/isPromise';
import NoneTrigger from './NoneTrigger';
import getPosition from './position';
const { Trigger, withPopover } = Popover;
const stateMap = {
onConfirm: 'confirmPending',
onCancel: 'cancelPending'
};
class PopAction extends Component {
// 支持异步的回调函数
// onConfirm/onCancel异步等待的时候要禁用用户关闭
handleClick(callbackName) {
const callback = this.props[callbackName];
const { popover } = this.props;
if (!isFunction(callback)) {
return popover.close();
}
const { changePending } = this.props;
const stateKey = stateMap[callbackName];
const startClose = () => {
changePending(stateKey, true);
};
const finishClose = () => {
changePending(stateKey, false, popover.close);
};
if (callback.length >= 1) {
startClose();
return callback(finishClose);
}
const maybePromise = callback();
if (isPromise(maybePromise)) {
startClose();
maybePromise
.then(finishClose)
.catch(() => changePending(stateKey, false, popover.close));
} else {
popover.close();
}
}
handleConfirm = () => {
this.handleClick('onConfirm');
};
handleCancel = () => {
this.handleClick('onCancel');
};
render() {
const { prefix, type, onConfirm, onCancel, confirmText, cancelText, confirmPending, cancelPending } = this.props;
if (!onConfirm && !onCancel) {
return null;
}
return (
<div className={`${prefix}-pop-buttons`}>
<Button loading={confirmPending} disabled={cancelPending} size="small" type={type} onClick={this.handleConfirm}>{confirmText}</Button>
<Button loading={cancelPending} disabled={confirmPending} size="small" onClick={this.handleCancel}>{cancelText}</Button>
</div>
);
}
}
const BoundPopAction = withPopover(PopAction);
export default class Pop extends Component {
static propTypes = {
trigger: PropTypes.oneOf([
'click', 'hover', 'focus', 'none'
]),
position: PropTypes.oneOf([
'left-top', 'left-center', 'left-bottom',
'right-top', 'right-center', 'right-bottom',
'top-left', 'top-center', 'top-right',
'bottom-left', 'bottom-center', 'bottom-right'
]),
// 是否按小箭头居中对齐trigger来定位
centerArrow: PropTypes.bool,
// trigger是否块级显示
block: PropTypes.bool,
content: PropTypes.node,
header: PropTypes.node,
// confirm形式相关
onConfirm: PropTypes.func,
onCancel: PropTypes.func,
confirmText: PropTypes.string,
cancelText: PropTypes.string,
type: PropTypes.oneOf([
'primary', 'default', 'danger', 'success'
]),
// 打开之后的回调函数
onShow: PropTypes.func,
// 关闭之后的回调函数
onClose: PropTypes.func,
// 打开/关闭前的回调函数,只有用户触发的操作才会调用;通过外部改变`visible`不会触发
onBeforeShow: PropTypes.func,
onBeforeClose: PropTypes.func,
visible: PropTypes.bool,
onVisibleChange: PropTypes.func,
// 只有trigger为hover时才有效
mouseLeaveDelay: PropTypes.number,
mouseEnterDelay: PropTypes.number,
// 只有trigger为click时才有效
closeOnClickOutside: PropTypes.bool,
isClickOutside: PropTypes.func,
prefix: PropTypes.string,
className: PropTypes.string,
wrapperClassName: PropTypes.string
};
static defaultProps = {
trigger: 'none',
position: 'top-center',
centerArrow: false,
block: false,
confirmText: '确定',
cancelText: '取消',
type: 'primary',
closeOnClickOutside: true,
mouseLeaveDelay: 200,
mouseEnterDelay: 200,
className: '',
wrapperClassName: '',
prefix: 'zent',
};
state = {
confirmPending: false,
cancelPending: false
};
changePending = (key, pending, callback) => {
if (this.isUnmounted) {
return;
}
this.setState({
[key]: pending
}, callback);
}
renderContent() {
const { prefix, content, header, onConfirm, onCancel, confirmText, cancelText, type } = this.props;
const { confirmPending, cancelPending } = this.state;
return (
<Popover.Content>
{header && <div className={`${prefix}-pop-header`}>{header}</div>}
<div className={`${prefix}-pop-inner`}>
{content}
<BoundPopAction
prefix={prefix}
onConfirm={onConfirm}
onCancel={onCancel}
confirmText={confirmText}
cancelText={cancelText}
confirmPending={confirmPending}
cancelPending={cancelPending}
changePending={this.changePending}
type={type}
/>
</div>
<i className={`${prefix}-pop-arrow`} />
</Popover.Content>
);
}
renderTrigger() {
const { trigger, visible, onVisibleChange, closeOnClickOutside, isOutside, mouseLeaveDelay, mouseEnterDelay, children } = this.props;
if (trigger === 'click') {
return <Trigger.Click autoClose={closeOnClickOutside} isOutside={isOutside}>{children}</Trigger.Click>;
}
if (trigger === 'hover') {
return <Trigger.Hover showDelay={mouseEnterDelay} hideDelay={mouseLeaveDelay} isOutside={isOutside}>{children}</Trigger.Hover>;
}
if (trigger === 'focus') {
return <Trigger.Focus>{children}</Trigger.Focus>;
}
if (trigger === 'none') {
return <NoneTrigger visible={visible} onVisibleChange={onVisibleChange}>{children}</NoneTrigger>;
}
return null;
}
componentWillUnmount() {
this.isUnmounted = true;
}
render() {
const {
className, wrapperClassName, trigger, visible,
prefix, block, onShow, onClose, position, centerArrow,
onBeforeClose, onBeforeShow
} = this.props;
let { onVisibleChange } = this.props;
if (trigger === 'none') {
onVisibleChange = onVisibleChange || noop;
}
const { confirmPending, cancelPending } = this.state;
const closePending = confirmPending || cancelPending;
return (
<Popover
visible={closePending ? true : visible}
onVisibleChange={closePending ? noop : onVisibleChange}
prefix={prefix}
wrapperClassName={cx(`${prefix}-pop-wrapper`, wrapperClassName)}
className={cx(`${prefix}-pop`, className)}
cushion={10}
position={getPosition(position, centerArrow)}
display={block ? 'block' : 'inline-block'}
onShow={onShow}
onClose={onClose}
onBeforeClose={onBeforeClose}
onBeforeShow={onBeforeShow}
>
{this.renderTrigger()}
{this.renderContent()}
</Popover>
);
}
}