wix-style-react
Version:
wix-style-react
221 lines • 8.9 kB
JavaScript
import React, { Children } from 'react';
import PropTypes from 'prop-types';
import CloseButton from '../CloseButton';
import TextLabel from './TextLabel';
import ActionButton from './ActionButton';
import { st, classes } from './Notification.st.css';
import { StatusComplete, StatusWarning, StatusAlert, } from '@wix/wix-ui-icons-common';
import { dataHooks } from './constants';
import { timingMap } from '../Transition/constants';
export const LOCAL_NOTIFICATION = 'local';
export const GLOBAL_NOTIFICATION = 'global';
export const STICKY_NOTIFICATION = 'sticky';
export const DEFAULT_AUTO_HIDE_TIMEOUT = 6000;
export const DEFAULT_TIMEOUT = DEFAULT_AUTO_HIDE_TIMEOUT;
const animationsTimeouts = {
enter: 300,
exit: 300,
};
const themeIcon = {
error: React.createElement(StatusAlert, { className: classes.iconStyling }),
success: React.createElement(StatusComplete, { className: classes.iconStyling }),
warning: React.createElement(StatusWarning, { className: classes.iconStyling }),
};
function mapChildren(children) {
const childrenArray = Children.toArray(children);
return childrenArray.reduce((childrenAsMap, child) => {
switch (child.type.displayName) {
case 'Notification.TextLabel':
childrenAsMap.label = child;
break;
case 'Notification.ActionButton':
childrenAsMap.ctaButton = child;
break;
case 'Notification.CloseButton':
childrenAsMap.closeButton = React.cloneElement(child, {
size: 'small',
});
break;
default:
break;
}
return childrenAsMap;
}, {});
}
class Notification extends React.PureComponent {
constructor() {
super(...arguments);
this.ref = React.createRef();
this.childRef = React.createRef();
this.state = {
hideByCloseClick: false,
hideByTimer: false,
height: 0,
showChildren: false,
};
this._hideNotificationOnCloseClick = () => {
this.setState({ hideByCloseClick: true });
setTimeout(() => this.props.onClose && this.props.onClose('hide-by-close-click'), animationsTimeouts.exit + 100);
};
this._hideNotificationOnTimeout = () => {
this.setState({ hideByTimer: true });
setTimeout(() => this.props.onClose && this.props.onClose('hide-by-timer'), animationsTimeouts.exit + 100);
};
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.show) {
this._setChildrenVisibility(nextProps);
this._bypassCloseFlags();
this._clearCloseTimeout();
this._startCloseTimer(nextProps);
}
}
componentDidMount() {
this._setChildrenVisibility(this.props);
this._startCloseTimer(this.props);
}
componentWillUnmount() {
this._clearCloseTimeout();
this._clearTransitionTimeout();
}
componentDidUpdate(prevProps, prevState) {
const { showChildren, height, hideByCloseClick, hideByTimer } = this.state;
if (prevProps.show !== this.props.show ||
prevState.showChildren !== this.state.showChildren) {
const show = this._shouldShowNotification(this.props.show);
if (show) {
if (showChildren && this.ref.current && height === 0) {
this.setState({ height: this._setHeightToChild(this.ref.current) });
}
}
else {
if (showChildren && height !== 0) {
this.setState({ height: 0 });
}
}
}
if (prevProps.hideByCloseClick !== hideByCloseClick ||
prevProps.hideByTimer !== hideByTimer) {
this._setChildrenVisibility(this.props);
}
}
_setChildrenVisibility({ show }) {
if (this._shouldShowNotification(show)) {
this.setState({ showChildren: true });
}
else {
this.setState({ height: 0 });
this.transitionTimeout = setTimeout(() => {
this.transitionTimeout && this.setState({ showChildren: false });
}, timingMap['medium01']);
}
}
_clearTransitionTimeout() {
if (this.transitionTimeout) {
clearTimeout(this.transitionTimeout);
this.transitionTimeout = null;
}
}
_startCloseTimer({ autoHideTimeout }) {
if (autoHideTimeout) {
this.closeTimeout = setTimeout(() => this._hideNotificationOnTimeout(), autoHideTimeout || DEFAULT_AUTO_HIDE_TIMEOUT);
}
}
_clearCloseTimeout() {
if (this.closeTimeout) {
clearTimeout(this.closeTimeout);
this.closeTimeout = null;
}
}
_bypassCloseFlags() {
this.setState({
hideByCloseClick: false,
hideByTimer: false,
});
}
_shouldShowNotification(show) {
return show && !this.state.hideByCloseClick && !this.state.hideByTimer;
}
_setHeightToChild(node) {
const height = this.childRef && this.childRef.current
? this.childRef.current.offsetHeight
: 0;
if (node && node.firstChild) {
node.firstChild.style.height = `${height}px`;
}
return height;
}
_getChildren() {
if (!this.state.showChildren) {
return null;
}
const { theme, children } = this.props;
const childrenComponents = mapChildren(children);
return (React.createElement("div", { className: classes.wrapper, style: {
overflow: 'hidden',
transition: `height ${timingMap['medium01']}ms`,
height: this.state.height,
} },
React.createElement("div", { "data-hook": dataHooks.notificationContent, className: classes.notification, role: "alert", "aria-labelledby": "notification-label", "aria-live": "polite", ref: this.childRef },
themeIcon[theme] && React.createElement("div", null, themeIcon[theme]),
React.createElement("div", { className: classes.labelWrapper },
childrenComponents.label,
childrenComponents.ctaButton),
childrenComponents.closeButton && (React.createElement("div", { "data-hook": dataHooks.notificationCloseButton, className: classes.closeButton, onClick: this._hideNotificationOnCloseClick, children: childrenComponents.closeButton })))));
}
render() {
const { dataHook, theme, type, zIndex } = this.props;
return (React.createElement("div", { className: st(classes.root, { theme, type }), style: { zIndex }, "data-hook": dataHook, "data-theme": theme, "data-type": type },
React.createElement("div", { className: classes.animator, style: {
overflow: 'hidden',
transition: `height ${timingMap['medium01']}ms`,
height: this.state.height,
} },
React.createElement("div", { ref: this.ref, className: classes.animatorContent }, this._getChildren()))));
}
}
const Close = props => React.createElement(CloseButton, { skin: "lightFilled", ...props });
Close.displayName = 'Notification.CloseButton';
Notification.displayName = 'Notification';
Notification.propTypes = {
/** when set to `true`, notification is shown */
show: PropTypes.bool,
/** Notification theme */
theme: PropTypes.oneOf([
'standard',
'error',
'success',
'warning',
'premium',
]),
/** Sets how <Notification/> should be displayed:
* - `type="global"` will take up space and push the content down.
* - `type="local"` will not take up space and will be displayed on top of content
* - `type="sticky"` will not take up space and will be displayed at the top of whole page and on top of content (position: fixed;)
* */
type: PropTypes.oneOf([
GLOBAL_NOTIFICATION,
LOCAL_NOTIFICATION,
STICKY_NOTIFICATION,
]),
/** When provided, then the Notification will be hidden after the specified timeout. */
autoHideTimeout: PropTypes.number,
/** Notification z-index */
zIndex: PropTypes.number,
onClose: PropTypes.func,
/** Can be either:
* - `<Notification.TextLabel/>` (required)
* - `<Notification.CloseButton/>`
* - `<Notification.ActionButton/>` */
children: PropTypes.node,
};
Notification.defaultProps = {
theme: 'standard',
type: GLOBAL_NOTIFICATION,
onClose: null,
};
Notification.CloseButton = Close;
Notification.TextLabel = TextLabel;
Notification.ActionButton = ActionButton;
export default Notification;
//# sourceMappingURL=Notification.js.map