import type { ReactElement, FC } from "react"; import { useImperativeHandle, useEffect, useRef, useMemo, useLayoutEffect, useState, useCallback, } from "react"; import type { IModal, TModals, TModalInstance, TModalRef, TModalContext, } from "./config/types"; import { ModalContext } from "./context"; import { ModalCtrl } from "./ui/ModalCtrl"; import { ModalInner } from "./ui/ModalInner"; import { ModalWrapper } from "./ui/ModalWrapper"; import { Core } from "../../plugins/core"; import type { TAction } from "../../plugins/modals/config/types"; import type { IComponentBaseProps } from "../../types/components/index"; import { useCN } from "../../utils/react/useCN"; import { useIsSSR } from "../../utils/react/useIsSSR"; import "./style.pcss"; /** * Компонент модального окна * @returns {ReactElement} */ const Modal: FC = ({ baseClass = "modal", extraClasses = {}, extraAttrs = {}, children = null, id, ref, isOpen = false, isInline = false, footerSlot = null, headerSlot = null, titleSlot = null, ctrlSlot = null, onAction = null, async, assets, fancyboxCfg, preset, cfg = {}, }): ReactElement => { // SSR mode? const { isSSR } = useIsSSR(); // Wrapper ref const templateRef = useRef(null); // Fancybox const plugin = useMemo(() => { return (Core.instance as unknown as Core)?.getPluginInstance("Modals") as TModalInstance; }, []); // Actual state const [ isModalOpen, setIsModalOpen ] = useState(isOpen); // Opening state const [ isLoading, setIsLoading ] = useState(false); // Prev state const prevOpen = useRef(isModalOpen); // Parsed config const parsedCfg = useMemo(() => { const { redirectAfterClose = null, isReloadAfterClose = false, } = cfg; return JSON.stringify(({ redirectAfterClose, isReloadAfterClose, })); }, [ cfg ]); // Classnames const { getCN } = useCN(baseClass); // Modal action handler const onActionHandler = useCallback((data: TAction) => { const { action } = data; if (action === "close") { setIsLoading(false); setIsModalOpen(false); prevOpen.current = false; } if (typeof onAction === "function") { onAction(data); } }, [ onAction ]); useEffect(() => { if (isOpen !== prevOpen.current) { setIsModalOpen(isOpen); } }, [ isOpen ]); useLayoutEffect(() => { if (isModalOpen !== prevOpen.current && !isLoading) { if (plugin) { const staticMethods = (plugin.constructor as TModals); if (isModalOpen) { // open modal via Fancybox if (!staticMethods.isOpen(id)) { staticMethods.getOpen({ id, src: "#" + id, assets, async, type: "inline" as const, onAction: onActionHandler, fancyboxCfg, }) .then(() => { prevOpen.current = true; return Promise.resolve(); }) .catch(() => { prevOpen.current = false; return Promise.reject(); }) .finally(() => { setIsLoading(false); }); } } else { // close modal via Fancybox if (staticMethods.isOpen(id)) { staticMethods.getClose(id) .catch(() => { return Promise.resolve(); }) .finally(() => { prevOpen.current = false; }); } } } else { console.error(`[Modals] Attempt to use "Modal" element without plugin. Please run "Modals" in "Core" first`); } } }, [ isModalOpen, isLoading ]); useLayoutEffect(() => { return () => { if (plugin) { const staticMethods = (plugin.constructor as TModals); if (staticMethods.isOpen(id)) { staticMethods.getClose(id) .catch(() => { return Promise.resolve(); }) .finally(() => { setIsLoading(false); }); } } }; }, []); useImperativeHandle(ref, (): TModalRef => ({ el: templateRef.current, ref: { current: null }, utils: null, getData: () => null, stateActions: { setIsOpen: (isOpen = true) => setIsModalOpen(isOpen), }, }), []); const contextValue = useMemo(() => ({ getCN, id, plugin, cfg, footerSlot, titleSlot, headerSlot, preset, }), [ plugin, parsedCfg, footerSlot, titleSlot, headerSlot, preset ]); return ( ); }; export type { IModal }; export { Modal, };