"use client"; import type { PropsWithChildren } from "react"; import { createContext, useCallback, useContext, useReducer, useRef, } from "react"; import { getDefaultZIndex, Modal } from "@mantine/core"; import { randomId } from "@mantine/hooks"; import type { stringOrTranslation } from "@homarr/translation"; import { translateIfNecessary } from "@homarr/translation"; import { useI18n } from "@homarr/translation/client"; import type { ConfirmModalProps } from "./confirm-modal"; import { ConfirmModal } from "./confirm-modal"; import { modalReducer } from "./reducer"; import type { inferInnerProps, ModalDefinition } from "./type"; interface ModalContextProps { openModalInner: (props: { modal: TModal; innerProps: inferInnerProps; options: OpenModalOptions; }) => void; closeModal: (id: string) => void; } export const ModalContext = createContext(null); export const ModalProvider = ({ children }: PropsWithChildren) => { const t = useI18n(); const [state, dispatch] = useReducer(modalReducer, { modals: [], current: null, }); const stateRef = useRef(state); stateRef.current = state; const closeModal = useCallback( (id: string, canceled?: boolean) => { dispatch({ type: "CLOSE", modalId: id, canceled }); }, [stateRef, dispatch], ); const openModalInner: ModalContextProps["openModalInner"] = useCallback( ({ modal, innerProps, options }) => { const id = randomId(); const { title, ...rest } = options; dispatch({ type: "OPEN", modal: { id, modal, props: { ...modal.options, ...rest, defaultTitle: title ?? modal.options.defaultTitle, innerProps, }, }, }); return id; }, [dispatch], ); const handleCloseModal = useCallback( () => state.current && closeModal(state.current.id), [closeModal, state.current?.id], ); const activeModals = state.modals.filter( (modal) => modal.id === state.current?.id || modal.props.keepMounted, ); return ( {activeModals.map((modal) => { const { defaultTitle: _ignored, ...otherModalProps } = modal.reference.modalProps; return ( 0} onClose={handleCloseModal} > {modal.reference.content} ); })} {children} ); }; interface OpenModalOptions { keepMounted?: boolean; title?: stringOrTranslation; } export const useModalAction = ( modal: TModal, ) => { const context = useContext(ModalContext); if (!context) throw new Error("ModalContext is not provided"); return { openModal: ( innerProps: inferInnerProps, options: OpenModalOptions | void, ) => { context.openModalInner({ modal, innerProps, options: options ?? {} }); }, }; }; export const useConfirmModal = () => { const { openModal } = useModalAction(ConfirmModal); return { openConfirmModal: (props: ConfirmModalProps) => openModal(props, { title: props.title }), }; };