react-cm-ui
Version:
React UI for Healthy Church
284 lines (247 loc) • 8.16 kB
JSX
import {
isUndefined,
isFunction,
forEachRight,
} from 'lodash';
import { Portal } from 'react-portal';
import ClassNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import ReactDOM from 'react-dom';
import BannerItem from './bannerItem';
import Header from '../header';
import Icon from '../icon';
import domUtils from '../../utils/domUtils';
import withTheme from '../../styles/withTheme';
const propTypes = {
children: PropTypes.node,
className: PropTypes.string,
id: PropTypes.oneOfType([
PropTypes.number,
PropTypes.string,
]).isRequired,
isOpen: PropTypes.bool.isRequired,
level: PropTypes.oneOf([
'error',
'purple',
'secondary',
'success',
'teal',
'warning',
]),
levelIcon: PropTypes.string,
message: PropTypes.string,
onAfterClose: PropTypes.func,
onClose: PropTypes.func.isRequired,
onOpen: PropTypes.func,
style: PropTypes.shape({}),
theme: PropTypes.shape({
zIndex: PropTypes.shape({
banner: PropTypes.number,
}),
}).isRequired,
title: PropTypes.string,
topPosition: PropTypes.number,
type: PropTypes.oneOf([
'alert',
'notification',
]),
};
const defaultProps = {
children: undefined,
className: undefined,
level: undefined,
levelIcon: undefined,
message: undefined,
onAfterClose: undefined,
onOpen: undefined,
style: undefined,
title: undefined,
topPosition: undefined,
type: undefined,
};
class Banner extends React.Component {
constructor(props) {
super(props);
this.state = {
isOpen: props.isOpen,
};
this.onAnimationComplete = this.onAnimationComplete.bind(this);
this.onBeforeClose = this.onBeforeClose.bind(this);
this.onClose = this.onClose.bind(this);
this.onOpen = this.onOpen.bind(this);
}
componentDidMount() {
const {
isOpen,
} = this.state;
if (isOpen) {
this.onOpen();
}
}
componentDidUpdate(prevProps) {
const {
isOpen: prevIsOpen,
} = prevProps;
const {
isOpen,
} = this.props;
if (!prevIsOpen && isOpen) {
this.setState({
isOpen,
}, () => {
this.onOpen();
});
}
if (prevIsOpen && !isOpen) {
this.onBeforeClose();
}
}
onAnimationComplete() {
const { id, isOpen, onAfterClose } = this.props;
// eslint-disable-next-line react/no-find-dom-node
const nodePortal = ReactDOM.findDOMNode(this);
const bannerContainer = nodePortal.querySelector('.banner-container');
const animationEvent = domUtils.cssAnimationType(bannerContainer);
bannerContainer.removeEventListener(animationEvent, this.onAnimationComplete);
this.setState({
isOpen: false,
}, () => {
this.bannerYPositions();
if (!isUndefined(onAfterClose) && !isOpen) {
onAfterClose(id);
}
});
}
onBeforeClose() {
// eslint-disable-next-line react/no-find-dom-node
const nodePortal = ReactDOM.findDOMNode(this);
const bannerContainer = nodePortal.querySelector('.banner-container');
const animationEvent = domUtils.cssAnimationType(bannerContainer);
bannerContainer.className += ' animate-out';
bannerContainer.addEventListener(animationEvent, this.onAnimationComplete);
}
onClose() {
const {
id,
onClose,
} = this.props;
onClose(id);
}
onOpen() {
const {
onOpen,
theme: {
zIndex,
},
} = this.props;
// eslint-disable-next-line react/no-find-dom-node
const nodePortal = ReactDOM.findDOMNode(this);
nodePortal.style.zIndex = zIndex.banner;
this.bannerYPositions();
if (isFunction(onOpen)) {
onOpen();
}
}
bannerYPositions() {
const { topPosition } = this.props;
let bannersYPosition = topPosition || 22;
const containersArray = document.querySelectorAll('.ui.banner .banner-container');
const containersLength = containersArray.length - 1;
forEachRight(containersArray, (element, index) => {
const el = element;
if (index !== containersLength) {
bannersYPosition += el.offsetHeight + 11;
}
el.style.top = `${bannersYPosition}px`;
});
}
render() {
const {
children,
className,
id,
level,
levelIcon,
message,
style,
title,
type,
} = this.props;
const { isOpen } = this.state;
if (!isOpen) {
return false;
}
const containerClasses = ClassNames('ui', 'banner', className, {
'banner-level-error': level === 'error',
'banner-level-purple': level === 'purple', // Rename when better defined.
'banner-level-secondary': !level || level === 'secondary',
'banner-level-success': level === 'success',
'banner-level-teal': level === 'teal', // Rename when better defined.
'banner-level-warning': level === 'warning',
'banner-type-alert': type === 'alert',
'banner-type-notification': type === 'notification',
});
const containerInnerClasses = ClassNames('banner-container', className);
const hasCloseButton = type === 'notification';
let levelBasedIcon;
switch (level) {
case 'error':
levelBasedIcon = 'times';
break;
case 'success':
levelBasedIcon = 'check';
break;
case 'warning':
levelBasedIcon = 'exclamation';
break;
case 'info':
default:
levelBasedIcon = 'circle';
}
return (
<Portal>
<div className={containerClasses} id={id} style={style}>
<div className={containerInnerClasses}>
<div className="banner-level-type">
<Icon compact inverse type={levelIcon || levelBasedIcon} />
</div>
<div className="banner-message-container">
<Header
size="small"
style={{
marginBottom: '5px',
paddingRight: hasCloseButton ? '16px' : null,
}}
>
{title}
</Header>
{message ? (
<span className="font-size-xxsmall">{message}</span>
) : null}
{children}
</div>
{hasCloseButton ? (
<Icon
compact
id={`ui-button--close_banner_${id}`}
inverse
onClick={this.onClose}
type="times"
style={{
position: 'absolute',
right: '11px',
top: '11px',
}}
/>
) : null}
</div>
</div>
</Portal>
);
}
}
Banner.Item = BannerItem;
Banner.propTypes = propTypes;
Banner.defaultProps = defaultProps;
export default withTheme(Banner);