UNPKG

@dr.pogodin/react-utils

Version:

Collection of generic ReactJS components and utils

138 lines (133 loc) 4.79 kB
import { useEffect, useMemo, useRef } from 'react'; import ReactDom from 'react-dom'; import themed from '@dr.pogodin/react-themes'; const baseTheme = { "context": "-dr-pogodin-react-utils___build-web-shared-components-Modal-base-theme___context___sCYXfW", "ad": "-dr-pogodin-react-utils___build-web-shared-components-Modal-base-theme___ad___e0BH-f", "hoc": "-dr-pogodin-react-utils___build-web-shared-components-Modal-base-theme___hoc___vqUuSP", "overlay": "-dr-pogodin-react-utils___build-web-shared-components-Modal-base-theme___overlay___uAH4as", "container": "-dr-pogodin-react-utils___build-web-shared-components-Modal-base-theme___container___zqpc6q" }; const S = { "scrollingDisabledByModal": "-dr-pogodin-react-utils___build-web-shared-components-Modal-styles___scrollingDisabledByModal___yfvOIZ" }; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; /** * The `<Modal>` component implements a simple themeable modal window, wrapped * into the default theme. `<BaseModal>` exposes the base non-themed component. * **Children:** Component children are rendered as the modal content. * @param {object} props Component properties. Beside props documented below, * [Other theming properties](https://www.npmjs.com/package/@dr.pogodin/react-themes#themed-component-properties) are supported as well. * @param {function} [props.onCancel] The callback to trigger when user * clicks outside the modal, or presses Escape. It is expected to hide the * modal. * @param {ModalTheme} [props.theme] _Ad hoc_ theme. */ const BaseModal = ({ cancelOnScrolling, children, containerStyle, dontDisableScrolling, onCancel, overlayStyle, style, testId, testIdForOverlay, theme }) => { const containerRef = useRef(null); const overlayRef = useRef(null); // Sets up modal cancellation of scrolling, if opted-in. useEffect(() => { if (cancelOnScrolling && onCancel) { window.addEventListener('scroll', onCancel); window.addEventListener('wheel', onCancel); } return () => { if (cancelOnScrolling && onCancel) { window.removeEventListener('scroll', onCancel); window.removeEventListener('wheel', onCancel); } }; }, [cancelOnScrolling, onCancel]); // Disables window scrolling, if it is not opted-out. useEffect(() => { if (!dontDisableScrolling) { document.body.classList.add(S.scrollingDisabledByModal); } return () => { if (!dontDisableScrolling) { document.body.classList.remove(S.scrollingDisabledByModal); } }; }, [dontDisableScrolling]); const focusLast = useMemo(() => /*#__PURE__*/_jsx("div", { onFocus: () => { const elems = containerRef.current.querySelectorAll('*'); for (let i = elems.length - 1; i >= 0; --i) { elems[i].focus(); if (document.activeElement === elems[i]) return; } overlayRef.current?.focus(); } // TODO: Have a look at this later. // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex , tabIndex: 0 }), []); return /*#__PURE__*/ReactDom.createPortal(/*#__PURE__*/_jsxs("div", { children: [focusLast, /*#__PURE__*/_jsx("div", { "aria-label": "Cancel", className: theme.overlay, "data-testid": process.env.NODE_ENV === 'production' ? undefined : testIdForOverlay, onClick: e => { if (onCancel) { onCancel(); e.stopPropagation(); } }, onKeyDown: e => { if (e.key === 'Escape' && onCancel) { onCancel(); e.stopPropagation(); } }, ref: node => { if (node && node !== overlayRef.current) { overlayRef.current = node; node.focus(); } }, role: "button", style: overlayStyle, tabIndex: 0 }), /*#__PURE__*/_jsx("div", { // eslint-disable-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions "aria-modal": "true", className: theme.container, "data-testid": process.env.NODE_ENV === 'production' ? undefined : testId, onClick: e => { e.stopPropagation(); }, onWheel: event => { event.stopPropagation(); }, ref: containerRef, role: "dialog", style: style ?? containerStyle, children: children }), /*#__PURE__*/_jsx("div", { onFocus: () => { overlayRef.current?.focus(); } // TODO: Have a look at this later. // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex , tabIndex: 0 }), focusLast] }), document.body); }; export default themed(BaseModal, 'Modal', baseTheme); /* Non-themed version of the Modal. */ export { BaseModal }; //# sourceMappingURL=index.js.map