react-notification-system
Version:
A React Notification System fully customized
385 lines (329 loc) • 9.69 kB
JSX
var React = require('react');
var PropTypes = require('prop-types');
var ReactDOM = require('react-dom');
var Constants = require('./constants');
var Helpers = require('./helpers');
var merge = require('object-assign');
/* From Modernizr */
var whichTransitionEvent = function() {
var el = document.createElement('fakeelement');
var transition;
var transitions = {
transition: 'transitionend',
OTransition: 'oTransitionEnd',
MozTransition: 'transitionend',
WebkitTransition: 'webkitTransitionEnd'
};
Object.keys(transitions).forEach(function(transitionKey) {
if (el.style[transitionKey] !== undefined) {
transition = transitions[transitionKey];
}
});
return transition;
};
function _allowHTML(string) {
return { __html: string };
}
class NotificationItem extends React.Component {
constructor(props) {
super(props);
this._notificationTimer = null;
this._height = 0;
this._noAnimation = null;
this._isMounted = false;
this._removeCount = 0;
this.state = {
visible: undefined,
removed: false
};
const getStyles = props.getStyles;
const level = props.notification.level;
const dismissible = props.notification.dismissible;
this._noAnimation = props.noAnimation;
this._styles = {
notification: getStyles.byElement('notification')(level),
title: getStyles.byElement('title')(level),
dismiss: getStyles.byElement('dismiss')(level),
messageWrapper: getStyles.byElement('messageWrapper')(level),
actionWrapper: getStyles.byElement('actionWrapper')(level),
action: getStyles.byElement('action')(level)
};
if (!dismissible || dismissible === 'none' || dismissible === 'button') {
this._styles.notification.cursor = 'default';
}
this._getCssPropertyByPosition = this._getCssPropertyByPosition.bind(this);
this._defaultAction = this._defaultAction.bind(this);
this._hideNotification = this._hideNotification.bind(this);
this._removeNotification = this._removeNotification.bind(this);
this._dismiss = this._dismiss.bind(this);
this._showNotification = this._showNotification.bind(this);
this._onTransitionEnd = this._onTransitionEnd.bind(this);
this._handleMouseEnter = this._handleMouseEnter.bind(this);
this._handleMouseLeave = this._handleMouseLeave.bind(this);
this._handleNotificationClick = this._handleNotificationClick.bind(this);
}
_getCssPropertyByPosition() {
var position = this.props.notification.position;
var css = {};
switch (position) {
case Constants.positions.tl:
case Constants.positions.bl:
css = {
property: 'left',
value: -200
};
break;
case Constants.positions.tr:
case Constants.positions.br:
css = {
property: 'right',
value: -200
};
break;
case Constants.positions.tc:
css = {
property: 'top',
value: -100
};
break;
case Constants.positions.bc:
css = {
property: 'bottom',
value: -100
};
break;
default:
}
return css;
}
_defaultAction(event) {
var notification = this.props.notification;
event.preventDefault();
this._hideNotification();
if (typeof notification.action.callback === 'function') {
notification.action.callback();
}
}
_hideNotification() {
if (this._notificationTimer) {
this._notificationTimer.clear();
}
if (this._isMounted) {
this.setState({
visible: false,
removed: true
});
}
if (this._noAnimation) {
this._removeNotification();
}
}
_removeNotification() {
this.props.onRemove(this.props.notification.uid);
}
_dismiss() {
if (!this.props.notification.dismissible) {
return;
}
this._hideNotification();
}
_showNotification() {
setTimeout(() => {
if (this._isMounted) {
this.setState({
visible: true
});
}
}, 50);
}
_onTransitionEnd() {
if (this._removeCount > 0) return;
if (this.state.removed) {
this._removeCount += 1;
this._removeNotification();
}
}
componentDidMount() {
var self = this;
var transitionEvent = whichTransitionEvent();
var notification = this.props.notification;
var element = ReactDOM.findDOMNode(this);
this._height = element.offsetHeight;
this._isMounted = true;
// Watch for transition end
if (!this._noAnimation) {
if (transitionEvent) {
element.addEventListener(transitionEvent, this._onTransitionEnd);
} else {
this._noAnimation = true;
}
}
if (notification.autoDismiss) {
this._notificationTimer = new Helpers.Timer(function() {
self._hideNotification();
}, notification.autoDismiss * 1000);
}
this._showNotification();
}
_handleMouseEnter() {
var notification = this.props.notification;
if (notification.autoDismiss) {
this._notificationTimer.pause();
}
}
_handleMouseLeave() {
var notification = this.props.notification;
if (notification.autoDismiss) {
this._notificationTimer.resume();
}
}
_handleNotificationClick() {
var dismissible = this.props.notification.dismissible;
if (
dismissible === 'both' ||
dismissible === 'click' ||
dismissible === true
) {
this._dismiss();
}
}
componentWillUnmount() {
var element = ReactDOM.findDOMNode(this);
var transitionEvent = whichTransitionEvent();
element.removeEventListener(transitionEvent, this._onTransitionEnd);
this._isMounted = false;
}
render() {
var notification = this.props.notification;
var className = 'notification notification-' + notification.level;
var notificationStyle = merge({}, this._styles.notification);
var cssByPos = this._getCssPropertyByPosition();
var dismiss = null;
var actionButton = null;
var title = null;
var message = null;
if (this.props.notification.className) {
className += ' ' + this.props.notification.className;
}
if (this.state.visible) {
className += ' notification-visible';
} else if (this.state.visible === false) {
className += ' notification-hidden';
}
if (notification.dismissible === 'none') {
className += ' notification-not-dismissible';
}
if (this.props.getStyles.overrideStyle) {
if (!this.state.visible && !this.state.removed) {
notificationStyle[cssByPos.property] = cssByPos.value;
}
if (this.state.visible && !this.state.removed) {
notificationStyle.height = this._height;
notificationStyle[cssByPos.property] = 0;
}
if (this.state.removed) {
notificationStyle.overlay = 'hidden';
notificationStyle.height = 0;
notificationStyle.marginTop = 0;
notificationStyle.paddingTop = 0;
notificationStyle.paddingBottom = 0;
}
if (this._styles.notification.isVisible && this._styles.notification.isHidden) {
notificationStyle.opacity = this.state.visible
? this._styles.notification.isVisible.opacity
: this._styles.notification.isHidden.opacity;
}
}
if (notification.title) {
title = (
<h4 className="notification-title" style={ this._styles.title }>
{notification.title}
</h4>
);
}
if (notification.message) {
if (this.props.allowHTML) {
message = (
<div
className="notification-message"
style={ this._styles.messageWrapper }
dangerouslySetInnerHTML={ _allowHTML(notification.message) }
/>
);
} else {
message = (
<div
className="notification-message"
style={ this._styles.messageWrapper }
>
{notification.message}
</div>
);
}
}
if (
notification.dismissible === 'both' ||
notification.dismissible === 'button' ||
notification.dismissible === true
) {
dismiss = (
<span
className="notification-dismiss"
onClick={ this._dismiss }
style={ this._styles.dismiss }
aria-hidden={ true }
>
×
</span>
);
}
if (notification.action) {
actionButton = (
<div
className="notification-action-wrapper"
style={ this._styles.actionWrapper }
>
<button
className="notification-action-button"
onClick={ this._defaultAction }
style={ this._styles.action }
>
{notification.action.label}
</button>
</div>
);
}
if (notification.children) {
actionButton = notification.children;
}
return (
<div
className={ className }
onClick={ this._handleNotificationClick }
onMouseEnter={ this._handleMouseEnter }
onMouseLeave={ this._handleMouseLeave }
style={ notificationStyle }
role="alert"
>
{title}
{message}
{dismiss}
{actionButton}
</div>
);
}
}
NotificationItem.propTypes = {
notification: PropTypes.object,
getStyles: PropTypes.object,
onRemove: PropTypes.func,
allowHTML: PropTypes.bool,
noAnimation: PropTypes.bool,
children: PropTypes.oneOfType([PropTypes.string, PropTypes.element])
};
NotificationItem.defaultProps = {
noAnimation: false,
onRemove: function() {},
allowHTML: false
};
module.exports = NotificationItem;