saagie-ui
Version:
Saagie UI from Saagie Design System
197 lines (178 loc) • 4.74 kB
JavaScript
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { useId } from '@reach/auto-id';
import { ClickAwayListener } from '../../helpers/clickAwayListener/ClickAwayListener';
import { modifierCSS } from '../../../helpers';
const propTypes = {
/**
* Children to set inside the Modal.
*
* @see ModalHeader
* @see ModalBody
* @see ModalFooter
*/
children: PropTypes.node,
defaultClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
/**
* Class name for the Modal.
*/
className: PropTypes.string,
/**
* The attributes to give to the content of the modal.
*/
contentAttributes: PropTypes.object,
/**
* The tag of the content of the modal.
*/
contentTag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
/**
* The default class name of the content of the modal.
*/
contentDefaultClassName: PropTypes.oneOfType([
PropTypes.string,
PropTypes.arrayOf(PropTypes.string),
]),
/**
* Disable the outside click. Note that by default the outside click is enable and the overlay
* is considered as outside.
*/
isClickOutsideDisabled: PropTypes.bool,
/**
* Disable the dismiss of the modal using Escape key.
*/
isEscapeKeyDisabled: PropTypes.bool,
/**
* Boolean to set the modal as fullscreen variant.
*/
isFullscreen: PropTypes.bool,
/**
* Boolean to open or close the
*/
isOpen: PropTypes.bool,
isPortalDisabled: PropTypes.bool,
/**
* Callback function trigerred on close event.
*/
onClose: PropTypes.func,
portalContainer: PropTypes.instanceOf(Element),
/**
* The size of the Modal
*/
size: PropTypes.oneOf(['', 'xxs', 'xs', 'sm', 'lg', 'xl', 'xxl']),
/**
* The custom tag for the root of the Modal
*/
tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
};
const defaultProps = {
children: '',
className: '',
contentAttributes: {},
contentTag: 'div',
contentDefaultClassName: 'sui-o-modal__content',
defaultClassName: 'sui-o-modal',
isClickOutsideDisabled: false,
isEscapeKeyDisabled: false,
isFullscreen: false,
isOpen: false,
isPortalDisabled: false,
portalContainer: null,
onClose: () => {},
size: '',
tag: 'div',
};
export const ModalContext = React.createContext({});
export const useModalContext = () => React.useContext(ModalContext);
export const Modal = ({
children,
className,
contentAttributes,
contentTag: ContentTag,
contentDefaultClassName,
defaultClassName,
isClickOutsideDisabled,
isEscapeKeyDisabled,
isFullscreen,
isOpen,
onClose,
size,
isPortalDisabled,
portalContainer,
tag: Tag,
...attributes
}) => {
const classes = classnames(
defaultClassName,
className,
isOpen ? 'as--open' : '',
isFullscreen ? 'as--fullscreen' : '',
modifierCSS(size, 'size'),
);
const contentClasses = classnames(
contentDefaultClassName,
);
const handleClose = (event) => {
event.stopPropagation();
if (onClose) {
onClose();
}
};
// Escape Key Listener
useEffect(() => {
/**
* 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();
}
};
if (!isEscapeKeyDisabled && isOpen) {
window.addEventListener('keydown', handleKeyDown);
}
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [isOpen]);
const id = useId();
const mountModal = () => {
const portalTarget = portalContainer || document.body;
const modal = (
<Tag className={classes} {...attributes}>
<ClickAwayListener
onClickOutside={handleClose}
isClickOutsideDisabled={isClickOutsideDisabled}
>
<ContentTag role="dialog" aria-labelledby={`dialog:${id}:label`} aria-modal="true" id={`dialog:${id}`} className={contentClasses} {...contentAttributes}>
{children}
</ContentTag>
</ClickAwayListener>
</Tag>
);
if (isPortalDisabled) {
return modal;
}
return ReactDOM.createPortal(modal, portalTarget);
};
return (
<ModalContext.Provider
value={{
handleClose,
id,
isOpen
}}
>
{mountModal()}
</ModalContext.Provider>
);
};
Modal.propTypes = propTypes;
Modal.defaultProps = defaultProps;