saagie-ui
Version:
Saagie UI from Saagie Design System
227 lines (208 loc) • 5.96 kB
JavaScript
/* eslint-disable no-undef */
import React, {
useEffect, useMemo, useState, useRef,
} from 'react';
import PropTypes from 'prop-types';
import { Manager, Reference } from 'react-popper';
import FocusTrap from 'focus-trap-react';
import classnames from 'classnames';
import { ClickAwayListener } from '../../helpers/clickAwayListener/ClickAwayListener';
import { usePopper } from '../../../helpers';
import { Button } from '../../atoms/button/Button';
export const stopPropagationForConfirmation = (event, action) => {
event.preventDefault();
event.stopPropagation();
action();
};
const propTypes = {
/**
* The label for the cancel button.
*/
cancelLabel: PropTypes.node,
/**
* The color of the cancel button
*/
cancelColor: PropTypes.oneOf(['', 'primary', 'secondary', 'warning', 'danger']),
/**
* The children that will be rendered by Confirm. The element onClick event
* will be intercepted by Confirm.
*/
children: PropTypes.element.isRequired,
className: PropTypes.string,
/**
* The label for the confirmation button.
*/
confirmLabel: PropTypes.node,
/**
* The color of the confirmation button
*/
confirmColor: PropTypes.oneOf(['', 'primary', 'secondary', 'warning', 'danger']),
/**
* The content of the Confirm element.
*/
content: PropTypes.node,
defaultClassName: PropTypes.oneOfType([
PropTypes.string,
PropTypes.arrayOf(PropTypes.string),
]),
/**
* Disable the escape key down listener.
*/
isEscapeKeyDisabled: PropTypes.bool,
/**
* Enable or disable the click away behavior. By default, the confirm will
* close on click away.
*/
isClickOutsideDisabled: PropTypes.bool,
/**
* Render the Confirm in the current DOM hierarchy.
*/
isPortalDisabled: PropTypes.bool,
/**
* Render the Confirm element in one of the available placement.
*/
placement: PropTypes.oneOf(['bottom', 'left', 'top', 'right']),
/**
* The target where the confirm node should render.
*/
portalTarget: PropTypes.instanceOf(Element),
/**
* The component used for the root node.
* Either a string to use a DOM element or a component.
*/
tag: PropTypes.elementType,
/**
* The title of the Confirm.
*/
title: PropTypes.node,
};
const defaultProps = {
cancelLabel: 'Cancel',
cancelColor: '',
className: '',
confirmLabel: 'Confirm',
confirmColor: 'primary',
content: '',
defaultClassName: 'sui-o-popup as--open',
isEscapeKeyDisabled: false,
isClickOutsideDisabled: false,
isPortalDisabled: false,
placement: 'top',
portalTarget: document.body,
tag: 'div',
title: 'Are you sure?',
};
export const Confirm = ({
cancelLabel,
cancelColor,
children,
className,
confirmLabel,
confirmColor,
content,
defaultClassName,
isEscapeKeyDisabled,
isClickOutsideDisabled,
isPortalDisabled: isDisabled,
placement,
portalTarget: target,
tag: Tag,
title,
...attributes
}) => {
if (!children || !children.props) {
return '';
}
const [isOpen, setIsOpen] = useState(false);
const { mountPopperNode } = usePopper(placement, { isDisabled, target });
const callbackRef = useRef(null);
const classes = classnames(
defaultClassName,
className,
);
const handleClose = () => {
setIsOpen(false);
};
const handleShow = (callbackFunction) => () => {
callbackRef.current = callbackFunction;
setIsOpen(true);
};
const handleConfirm = () => {
if (callbackRef.current) {
callbackRef.current();
}
handleClose();
};
/**
* Function to handle the Escape key down.
* @param {KeyboardEvent} event
*/
const handleKeyDown = (event) => {
if (event.key !== 'Escape' && event.keyCode !== 27) {
return;
}
// Swallow the event, in case someone is listening for the escape key on the body.
event.stopPropagation();
if (!isEscapeKeyDisabled) {
handleClose();
}
};
// Escape Key Listener
useEffect(() => {
if (!isEscapeKeyDisabled) {
window.addEventListener('keydown', handleKeyDown);
}
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, []);
const uniqueId = useMemo(() => Math.random().toString(36).substr(2, 9), []);
return (
<Manager>
<Reference>
{({ ref }) => (
React.cloneElement(children, {
ref,
onClick: (e) => stopPropagationForConfirmation(e, handleShow(children.props.onClick)),
})
)}
</Reference>
{isOpen && (mountPopperNode(
({ ref, style }) => (
<ClickAwayListener
isClickOutsideDisabled={isClickOutsideDisabled}
onClickOutside={() => setIsOpen(false)}
>
<FocusTrap>
<Tag
className={classes}
{...attributes}
ref={ref}
style={style}
role="alertdialog"
aria-labelledby={`sui-confirm-title-${uniqueId}`}
aria-describedby={`sui-confirm-content-${uniqueId}`}
>
{!!title && <div id={`sui-confirm-title-${uniqueId}`} className="sui-o-popup__title">{title}</div>}
{!!content && <div id={`sui-confirm-content-${uniqueId}`} className="sui-o-popup__content">{content}</div>}
<div className="sui-o-popup__buttons">
<Button type="button" onClick={handleClose} color={cancelColor}>
{cancelLabel}
</Button>
<Button
type="submit"
color={confirmColor}
onClick={handleConfirm}
>
{confirmLabel}
</Button>
</div>
</Tag>
</FocusTrap>
</ClickAwayListener>
)))}
</Manager>
);
};
Confirm.propTypes = propTypes;
Confirm.defaultProps = defaultProps;