/* * React Fabric * @version: 1.0.0 * * * The MIT License (MIT) * Copyright (c) 2024 Adarsh Pastakia * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import { FloatingFocusManager, FloatingOverlay, FloatingPortal, useDismiss, useFloating, useInteractions, useRole, } from "@floating-ui/react"; import { cn } from "@react-fabric/utilities"; import { Activity, use, useEffect, useId } from "react"; import { LoadingLine } from "../../components/animations/LoadingLine"; import { Icon } from "../../components/icon/Icon"; import { ModalContext } from "../../context/context"; import type { CallbackReturn, ChildrenProp, CssPropWithMap, TestProps } from "../../types"; import type { TitlebarProps } from "../titlebar/Titlebar"; import { Titlebar } from "../titlebar/Titlebar"; export interface ModalProps extends TestProps, ChildrenProp, TitlebarProps, CssPropWithMap<{ root: string; body?: string; titlebar?: string; title?: string; icon?: string }> { /** * show loading indicator */ loading?: boolean; /** * modal size */ size?: "sm" | "md" | "lg" | "xl" | "screen"; /** * height */ height?: string | number; /** * width */ width?: string | number; /** * minimum height */ minHeight?: string | number; /** * minimum width */ minWidth?: string | number; /** * portal root element */ root?: HTMLElement; /** * hide mask background */ hideMask?: boolean; closeOnEscape?: boolean; /** * close handler */ onClose?: (args: T | null) => void; /** * before close handler, return false to prevent close */ onBeforeClose?: (args: T | null) => CallbackReturn; } export function Modal({ icon, className, classNames, title, actions, onClose, onBeforeClose, children, loading, root, size, hideMask, width, height, minHeight, minWidth, closeOnEscape, ...props }: ModalProps) { const id = useId(); const { activeModal, registerModal, removeModal } = use(ModalContext); const { refs, context } = useFloating({ open: true, onOpenChange: () => { if (id === activeModal) handleClose(null); }, }); const dismiss = useDismiss(context, { outsidePressEvent: "mousedown", escapeKey: closeOnEscape, enabled: id === activeModal, }); const role = useRole(context); // Merge all the interactions into prop getters const { getFloatingProps } = useInteractions([dismiss, role]); const handleClose = (args: T | null) => { void Promise.resolve(onBeforeClose?.(args)).then((ret) => { if (ret !== false) { if (refs.floating.current) refs.floating.current.dataset.show = "false"; setTimeout(() => { onClose?.(args); }, 250); } if (ret === false) setTimeout(() => refs.floating.current?.querySelector("[role='dialog']")?.focus(), 50); }); }; useEffect(() => { if (activeModal === id) { const tmr = setTimeout(() => { if (refs.floating.current) refs.floating.current.dataset.show = "true"; }, 50); return () => { clearTimeout(tmr); }; } }, [activeModal, id, refs.floating]); useEffect(() => { registerModal?.(id, () => onClose?.(null)); return () => { removeModal?.(id); }; // eslint-disable-next-line @eslint-react/exhaustive-deps }, [id]); return ( refs.setFloating(el)} {...getFloatingProps()} {...props} >
{actions}
} className={cn(classNames?.titlebar)} classNames={classNames} /> {loading && }
{children}
); }