UNPKG

react-cm-ui

Version:
284 lines (247 loc) 8.16 kB
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);