@ozen-ui/kit
Version:
React component library
100 lines (99 loc) • 6.58 kB
JavaScript
import { __assign, __read, __rest, __spreadArray } from "tslib";
import './Modal.css';
import React, { useRef, useEffect, useState, useCallback, forwardRef, } from 'react';
import { CSSTransition } from 'react-transition-group';
import { isServer } from '../../constants/environment';
import { useClickOutside } from '../../hooks/useClickOutside';
import { useEventListener } from '../../hooks/useEventListener';
import { useFocusTrap } from '../../hooks/useFocusTrap';
import { useLockBodyScroll } from '../../hooks/useLockBodyScroll';
import { useMultiRef } from '../../hooks/useMultiRef';
import { usePortalContainer } from '../../hooks/usePortalContainer';
import { useThemeProps } from '../../hooks/useThemeProps';
import { cn } from '../../utils/classname';
import { isKey } from '../../utils/isKey';
import { Backdrop } from '../Backdrop';
import { PortalBase } from '../PortalBase';
import { ModalConsumer } from './components';
import { MODAL_DEFAULT_CLOSE_ONLY_ON_TOP, MODAL_DEFAULT_DISABLE_CLICK_OUTSIDE, MODAL_DEFAULT_DISABLE_SCROLL_LOCK, MODAL_DEFAULT_ESCAPE_KEY_DOWN, MODAL_DEFAULT_HIDE_BACKDROP, MODAL_DEFAULT_KEEP_MOUNTED, MODAL_DEFAULT_OPEN, MODAL_DEFAULT_PORTAL_CONTAINER, } from './constants';
import { useModalManager } from './index';
export var cnModal = cn('Modal');
export var Modal = forwardRef(function (inProps, ref) {
var props = useThemeProps({
name: 'Modal',
props: inProps,
});
var _a = props.open, open = _a === void 0 ? MODAL_DEFAULT_OPEN : _a, _b = props.keepMounted, keepMounted = _b === void 0 ? MODAL_DEFAULT_KEEP_MOUNTED : _b, _c = props.hideBackdrop, hideBackdrop = _c === void 0 ? MODAL_DEFAULT_HIDE_BACKDROP : _c, _d = props.disableScrollLock, disableScrollLock = _d === void 0 ? MODAL_DEFAULT_DISABLE_SCROLL_LOCK : _d, _e = props.disableClickOutside, disableClickOutside = _e === void 0 ? MODAL_DEFAULT_DISABLE_CLICK_OUTSIDE : _e, _f = props.disableEscapeKeyDown, disableEscapeKeyDown = _f === void 0 ? MODAL_DEFAULT_ESCAPE_KEY_DOWN : _f, children = props.children, onEnter = props.onEnter, onEntered = props.onEntered, onExit = props.onExit, onExitedProp = props.onExited, onClose = props.onClose, backdropProps = props.backdropProps, windowProps = props.windowProps, transitionProps = props.transitionProps, className = props.className, ignoreClickOutsideRefs = props.ignoreClickOutsideRefs, _g = props.container, containerProp = _g === void 0 ? MODAL_DEFAULT_PORTAL_CONTAINER : _g, _h = props.closeOnlyOnTop, closeOnlyOnTop = _h === void 0 ? MODAL_DEFAULT_CLOSE_ONLY_ON_TOP : _h, other = __rest(props, ["open", "keepMounted", "hideBackdrop", "disableScrollLock", "disableClickOutside", "disableEscapeKeyDown", "children", "onEnter", "onEntered", "onExit", "onExited", "onClose", "backdropProps", "windowProps", "transitionProps", "className", "ignoreClickOutsideRefs", "container", "closeOnlyOnTop"]);
var hasBackdrop = !hideBackdrop;
var rootRef = useRef(null);
var windowInnerRef = useRef(null);
var focusedElement = useRef(null);
var _j = __read(useState(false), 2), openState = _j[0], setOpenState = _j[1];
var _k = __read(useState(false), 2), opened = _k[0], setOpened = _k[1];
var container = usePortalContainer(containerProp);
var _l = useModalManager(windowInnerRef, 1000, openState), isTop = _l.isTop, refsClickOutside = _l.refsClickOutside;
var portalMultiRef = useMultiRef([rootRef, ref]);
var trapRef = useFocusTrap({
active: isTop,
focusinTrap: false,
});
var isCloseHandlersActive = closeOnlyOnTop ? isTop : true;
var modalConsumerMultiRef = useMultiRef([
windowInnerRef,
trapRef,
windowProps === null || windowProps === void 0 ? void 0 : windowProps.ref,
]);
useClickOutside({
refs: __spreadArray(__spreadArray([
windowInnerRef
], __read(refsClickOutside), false), __read((ignoreClickOutsideRefs || [])), false),
handler: onClose,
active: open && !disableClickOutside && isCloseHandlersActive,
});
// Блокирует прокрутку основного окна приложения
useLockBodyScroll(opened && !disableScrollLock);
useEffect(function () {
var _a;
// Сохраняем фокус активного элемента до момента открытия всплывающего окна
if (open) {
setOpened(true);
focusedElement.current =
document.activeElement;
(_a = focusedElement.current) === null || _a === void 0 ? void 0 : _a.blur();
}
setOpenState(open);
}, [open]);
var onExited = useCallback(function () {
var _a, _b;
if (isServer) {
return;
}
// Возвращаем фокус активного элемента
if (((_a = windowInnerRef.current) === null || _a === void 0 ? void 0 : _a.contains(document.activeElement)) ||
document.activeElement === document.body) {
(_b = focusedElement.current) === null || _b === void 0 ? void 0 : _b.focus({ preventScroll: true });
}
setOpened(false);
onExitedProp === null || onExitedProp === void 0 ? void 0 : onExitedProp();
}, [onExitedProp]);
// Нажатие клавиши {ESC}
useEventListener({
eventName: 'keydown',
handler: function (event) {
if (!isKey(event, 'Escape'))
return;
onClose === null || onClose === void 0 ? void 0 : onClose();
},
active: isCloseHandlersActive && !disableEscapeKeyDown,
});
if (!container) {
return null;
}
return (React.createElement(CSSTransition, __assign({ classNames: cnModal({ animation: true }), nodeRef: rootRef, timeout: 300 }, transitionProps, { in: openState, onEnter: onEnter, onEntered: onEntered, onExit: onExit, onExited: onExited, unmountOnExit: !keepMounted, appear: true }),
React.createElement(PortalBase, __assign({}, other, { container: container, className: cnModal({ hidden: keepMounted && !openState, hasBackdrop: hasBackdrop }, [
className,
]), ref: portalMultiRef }),
!hideBackdrop && (React.createElement(Backdrop, __assign({ zIndex: -1, open: openState }, backdropProps, { className: backdropProps === null || backdropProps === void 0 ? void 0 : backdropProps.className }))),
React.createElement(ModalConsumer, __assign({}, windowProps, { ref: modalConsumerMultiRef }), children))));
});
Modal.displayName = 'Modal';