react-redux-toastr
Version:
react-redux-toastr is a React toastr message implemented with Redux
229 lines (199 loc) • 7.02 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import {onCSSTransitionEnd, _bind, keyCode, isBrowser} from './utils';
import Button from './Button';
const ENTER = 13;
const ESC = 27;
export default class ToastrConfirm extends React.Component {
static displayName = 'ToastrConfirm';
static propTypes = {
confirm: PropTypes.shape({
options: PropTypes.shape({
transitionIn: PropTypes.string,
transitionOut: PropTypes.string
})
})
};
constructor(props) {
super(props);
const {
confirmOptions,
confirm
} = this.props;
const {
okText,
cancelText,
transitionIn,
transitionOut,
disableCancel,
closeOnShadowClick
} = confirm.options;
this.okText = okText || confirmOptions.okText;
this.cancelText = cancelText || confirmOptions.cancelText;
this.transitionIn = transitionIn || confirmOptions.transitionIn || props.transitionIn;
this.transitionOut = transitionOut || confirmOptions.transitionOut || props.transitionOut;
this.disableCancel = disableCancel || confirmOptions.disableCancel;
this.closeOnShadowClick = closeOnShadowClick || confirmOptions.closeOnShadowClick;
_bind('setTransition removeConfirm handleOnKeyUp handleOnKeyDown handleCloseOnShadowClick', this);
this.isKeyDown = false;
// an identifier to facilitate aria labelling for a11y for multiple instances of the component family in the DOM
this.id = Math.floor(Math.random() * 9999);
}
componentDidMount() {
this.isHiding = false;
this.hasClicked = false;
this.confirmHolderElement.focus();
if (this.props.confirm.show) {
this.setTransition(true);
}
// when toast loads the toast close button automatically focuses on the toast control
if (this.closeButton !== undefined && this.closeButton.focus !== undefined) {
this.closeButton.focus();
}
}
componentWillUnmount() {
// when toast unloads the toast close button automatically focuses on the next toast control (if any)
// need to add a micro delay to allow the DOM to recycle
setTimeout(function() {
if (document.getElementsByClassName('toastr-control').length > 0) {
document.getElementsByClassName('toastr-control')[0].focus();
}
}, 50);
}
handleOnKeyDown(e) {
if (keyCode(e) == ENTER) {
e.preventDefault();
}
this.isKeyDown = true;
}
handleCloseOnShadowClick(e){
if(this.closeOnShadowClick){
this.handleCancelClick();
}
}
handleButtonClick(callback) {
if (this.hasClicked) return;
this.hasClicked = true;
const onAnimationEnd = () => {
this.removeConfirm();
if (callback) {
callback();
}
};
this.setTransition();
onCSSTransitionEnd(this.confirmElement, onAnimationEnd);
}
handleConfirmClick() {
const callback = this.props.confirm.options ? this.props.confirm.options.onOk : null;
this.handleButtonClick(callback);
}
handleCancelClick() {
const callback = this.props.confirm.options ? this.props.confirm.options.onCancel : null;
this.handleButtonClick(callback);
}
setTransition(add) {
if (add) {
this.isHiding = false;
this.confirmElement.classList.add(this.transitionIn);
if (isBrowser()) return document.querySelector('body').classList.add('toastr-confirm-active');
}
this.isHiding = true;
this.confirmElement.classList.remove(this.transitionIn);
this.confirmElement.classList.add(this.transitionOut);
}
removeConfirm() {
this.isHiding = false;
this.props.hideConfirm();
if (isBrowser()) return document.querySelector('body').classList.remove('toastr-confirm-active');
}
handleOnKeyUp(e) {
const code = keyCode(e);
if (code == ESC && !this.disableCancel) {
this.handleCancelClick();
} else if (code == ESC && this.disableCancel) {
this.handleConfirmClick();
} else if (code == ENTER && this.isKeyDown) {
this.isKeyDown = false;
this.handleConfirmClick();
}
}
containsOkButton(buttons) {
return buttons && buttons.filter(button => button.ok === true).length > 0;
}
containsCancelButton(buttons) {
return buttons && buttons.filter(button => button.cancel === true).length > 0;
}
getCustomButtonHandler(config) {
if (config.ok === true) {
return this.handleConfirmClick.bind(this);
}
if (config.cancel === true) {
return this.handleCancelClick.bind(this);
}
return () => this.handleButtonClick(config.handler);
}
getCustomButtonText(config) {
if (config.ok === true) {
return this.okText;
}
if (config.cancel === true) {
return this.cancelText;
}
return config.text;
}
getCustomButtonClassName(config) {
if (config.ok === true) {
return 'rrt-ok-btn';
}
if (config.cancel === true) {
return 'rrt-cancel-btn';
}
return config.className;
}
render() {
const {
options,
message
} = this.props.confirm;
const wrapperProps = {};
options.id && (wrapperProps.id = options.id);
return (
<div
className="rrt-confirm-holder"
tabIndex="-1"
ref={ref => this.confirmHolderElement = ref}
onKeyDown={this.handleOnKeyDown}
onKeyUp={this.handleOnKeyUp}
role="alert"
{...wrapperProps}
>
<div className="rrt-confirm animated" ref={ref => this.confirmElement = ref} role="alertdialog" aria-describedby={`dialogDesc-${this.id}`}>
{message && <div className="rrt-message" id={`dialogDesc-${this.id}`}>{message}</div>}
{options.component && <options.component/>}
<div className="rrt-buttons-holder">
{!this.containsOkButton(options.buttons) &&
<Button tabIndex="0" innerRef={ref => this.closeButton = ref} className="rrt-ok-btn toastr-control" onClick={() => this.handleConfirmClick()}>
{this.okText}
</Button>
}
{!this.disableCancel && !this.containsCancelButton(options.buttons) &&
<Button tabIndex="0" innerRef={ref => this.closeButton = ref} className="rrt-cancel-btn toastr-control" onClick={this.handleCancelClick.bind(this)}>
{this.cancelText}
</Button>
}
{options.buttons && options.buttons.map((button, index) => {
if (button.cancel === true && this.disableCancel) {
return null;
}
const handler = this.getCustomButtonHandler(button);
const text = this.getCustomButtonText(button);
const className = this.getCustomButtonClassName(button);
return <Button tabIndex="0" className={className} onClick={handler} key={index}>{text}</Button>;
})}
</div>
</div>
<div className="shadow" onClick={this.handleCloseOnShadowClick.bind(this)}></div>
</div>
);
}
}