Repository URL to install this package:
|
Version:
7.13.0-rc.4 ▾
|
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import isEqual from 'lodash/isEqual';
import Icon from '../../visuals/Icon/Icon';
import Button from '../../controls/Button/Button';
import closeIcon from '../../../components/visuals/Icon/svg/ic_close.svg';
const escButtonCode = 27;
class Dialog extends PureComponent {
constructor(props) {
super(props);
this.dialogRef = null;
this.parentNode = null;
this.recalculatePosition = this.recalculatePosition.bind(this);
this.recalculateTopPosition = this.recalculateTopPosition.bind(this);
this.recalculateLeftPosition = this.recalculateLeftPosition.bind(this);
this.handleKeyDownDismiss = this.handleKeyDownDismiss.bind(this);
this.handleDismiss = this.handleDismiss.bind(this);
this.addListeners = this.addListeners.bind(this);
this.removeListeners = this.removeListeners.bind(this);
}
componentDidMount() {
const { id, isVisible } = this.props;
this.parentNode = document.querySelector(`[data-source="${id}"]`);
this.recalculatePosition();
if (isVisible) {
this.addListeners();
}
}
componentDidUpdate(previousProps) {
const { isVisible, offset, horizontalAlign, verticalAlign } = this.props;
const {
isVisible: prevIsVisible,
offset: prevOffset,
horizontalAlign: prevHorizontalAlign,
verticalAlign: prevVerticalAlign,
} = previousProps;
if (
(isVisible && !prevIsVisible) ||
horizontalAlign !== prevHorizontalAlign ||
verticalAlign !== prevVerticalAlign ||
!isEqual(offset, prevOffset)
) {
this.recalculatePosition();
}
if (isVisible && !prevIsVisible) {
this.addListeners();
}
if (!isVisible && prevIsVisible) {
this.removeListeners();
}
}
componentWillUnmount() {
this.removeListeners();
}
addListeners() {
const { disableCloseOnScroll } = this.props;
// need for skipping opening click
setTimeout(() => {
if (!disableCloseOnScroll) {
window.addEventListener('scroll', this.handleDismiss, true);
}
window.addEventListener('click', this.handleDismiss);
window.addEventListener('resize', this.handleDismiss);
window.addEventListener('keydown', this.handleKeyDownDismiss);
}, 0);
}
removeListeners() {
const { disableCloseOnScroll } = this.props;
if (!disableCloseOnScroll) {
window.removeEventListener('scroll', this.handleDismiss, true);
}
window.removeEventListener('resize', this.handleDismiss);
window.removeEventListener('click', this.handleDismiss);
window.removeEventListener('keydown', this.handleKeyDownDismiss);
}
recalculateTopPosition(parentRect, dialogRect, currentOffset) {
const { verticalAlign } = this.props;
switch (verticalAlign) {
case Dialog.alignType.bottom: {
this.dialogRef.style.top = `${parentRect.bottom - dialogRect.height + currentOffset.bottom}px`;
break;
}
case Dialog.alignType.middle: {
this.dialogRef.style.top = `${parentRect.top +
parentRect.height / 2 -
dialogRect.height / 2 +
currentOffset.middle}px`;
break;
}
default: {
this.dialogRef.style.top = `${parentRect.top + currentOffset.top}px`;
}
}
}
recalculateLeftPosition(parentRect, dialogRect, currentOffset) {
const { horizontalAlign } = this.props;
switch (horizontalAlign) {
case Dialog.alignType.left: {
this.dialogRef.style.left = `${parentRect.left - dialogRect.width + currentOffset.left}px`;
break;
}
case Dialog.alignType.center: {
this.dialogRef.style.left = `${parentRect.left +
parentRect.width / 2 -
dialogRect.width / 2 +
currentOffset.center}px`;
break;
}
default: {
this.dialogRef.style.left = `${parentRect.right + currentOffset.right}px`;
}
}
}
recalculatePosition() {
const { isVisible, offset, onRecalculate } = this.props;
const currentOffset = { ...Dialog.defaultOffset, ...offset };
if (!isVisible || !this.parentNode || !this.dialogRef) return;
if (onRecalculate) {
onRecalculate(this.dialogRef, this.parentNode);
} else {
const parentRect = this.parentNode.getBoundingClientRect();
const dialogRect = this.dialogRef.getBoundingClientRect();
this.recalculateTopPosition(parentRect, dialogRect, currentOffset);
this.recalculateLeftPosition(parentRect, dialogRect, currentOffset);
}
}
handleKeyDownDismiss(e) {
if (e.keyCode === escButtonCode) {
this.handleDismiss();
}
}
handleDismiss() {
const { onDismiss } = this.props;
onDismiss();
}
render() {
const { children, isVisible, isDismissible, onDismiss, className, title } = this.props;
return isVisible ? (
// need for implementation outside click
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-noninteractive-element-interactions
<div
role="dialog"
className={`Dialog ${className}`}
aria-label={title}
ref={ref => {
this.dialogRef = ref;
}}
onClick={e => {
e.stopPropagation();
}}
>
{isDismissible && (
<Button variant="linkDark" className="Dialog__close-button" onClick={onDismiss} aria-label="Close button">
<Icon className="Dialog__close-icon" icon={closeIcon} />
</Button>
)}
{children}
</div>
) : null;
}
}
Dialog.defaultOffset = {
left: 0,
right: 0,
top: 0,
bottom: 0,
center: 0,
middle: 0,
};
Dialog.alignType = {
left: 'left',
right: 'right',
top: 'top',
bottom: 'bottom',
center: 'center',
middle: 'middle',
};
Dialog.propTypes = {
/** Dialog content */
children: PropTypes.node.isRequired,
/** Control flag for displaying dialog component */
isVisible: PropTypes.bool.isRequired,
/** Callback was fired if user clicks on close button or scroll/resize page */
onDismiss: PropTypes.func,
/** Control flag for displaying close button */
isDismissible: PropTypes.bool,
/** An id that was placed in data-source parent attribute */
id: PropTypes.string.isRequired,
/** A11y-enabled modal title */
title: PropTypes.string.isRequired,
/** Align value */
horizontalAlign: PropTypes.oneOf([Dialog.alignType.left, Dialog.alignType.right, Dialog.alignType.center]),
/** Align value */
verticalAlign: PropTypes.oneOf([Dialog.alignType.top, Dialog.alignType.bottom, Dialog.alignType.middle]),
/** Offset values for aligning */
offset: PropTypes.shape({
left: PropTypes.number,
right: PropTypes.number,
top: PropTypes.number,
bottom: PropTypes.number,
center: PropTypes.number,
middle: PropTypes.number,
}),
/** Wrapper class for customization */
className: PropTypes.string,
onRecalculate: PropTypes.func,
/** Disables dialog closing on scroll so we can scroll on dialog if necessary */
disableCloseOnScroll: PropTypes.bool,
};
Dialog.defaultProps = {
isDismissible: false,
onDismiss: () => {},
className: '',
offset: Dialog.defaultOffset,
horizontalAlign: Dialog.alignType.right,
verticalAlign: Dialog.alignType.top,
onRecalculate: null,
disableCloseOnScroll: false,
};
export default Dialog;