UNPKG

@lobehub/ui

Version:

Lobe UI is an open-source UI component library for building AIGC web apps

221 lines (218 loc) 7.29 kB
'use client'; import { useIsClient } from "../../hooks/useIsClient.mjs"; import { useAppElement } from "../../ThemeProvider/AppElementContext.mjs"; import { registerDevSingleton } from "../../utils/devSingleton.mjs"; import { styles } from "./style.mjs"; import { ModalBackdrop, ModalClose, ModalContent, ModalFooter, ModalHeader, ModalPopup, ModalPortal, ModalRoot, ModalTitle } from "./atoms.mjs"; import { ModalContext, useModalContext } from "./context.mjs"; import { memo, useCallback, useEffect, useState, useSyncExternalStore } from "react"; import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime"; import { cx } from "antd-style"; import { createPortal } from "react-dom"; //#region src/base-ui/Modal/imperative.tsx const ModalPortalWrapper = ({ children, root }) => { const appElement = useAppElement(); return createPortal(children, root ?? appElement ?? document.body); }; const ConfirmBody = ({ config }) => { const { close } = useModalContext(); const [loading, setLoading] = useState(false); const { cancelText = "Cancel", content, okButtonProps, okText = "OK", onCancel, onOk } = config; const { danger, className: okUserCls, ...restOkProps } = okButtonProps ?? {}; const handleCancel = useCallback(() => { close(); onCancel?.(); }, [close, onCancel]); const handleOk = useCallback(async () => { if (onOk) try { const result = onOk(); if (result && typeof result.then === "function") { setLoading(true); await result; setLoading(false); } } catch { setLoading(false); return; } close(); }, [close, onOk]); return /* @__PURE__ */ jsxs(Fragment$1, { children: [content && /* @__PURE__ */ jsx("div", { style: { padding: "16px 24px" }, children: content }), /* @__PURE__ */ jsxs(ModalFooter, { children: [/* @__PURE__ */ jsx("button", { className: cx(styles.buttonBase, styles.cancelButton), type: "button", onClick: handleCancel, children: cancelText }), /* @__PURE__ */ jsxs("button", { ...restOkProps, disabled: loading, type: "button", className: cx(styles.buttonBase, danger ? styles.dangerOkButton : styles.okButton, okUserCls), onClick: handleOk, children: [loading && /* @__PURE__ */ jsx("span", { className: styles.loadingSpinner }), okText] })] })] }); }; ConfirmBody.displayName = "ConfirmBody"; let systemSeed = 0; function createModalSystem() { const systemId = systemSeed++; const singletonName = systemId === 0 ? "BaseModalHost" : `BaseModalHost-${systemId}`; let modalStack = []; let modalSeed = 0; const listeners = /* @__PURE__ */ new Set(); const notify = () => listeners.forEach((l) => l()); const subscribe = (l) => { listeners.add(l); return () => listeners.delete(l); }; const EMPTY = []; const getSnapshot = () => modalStack; const getServerSnapshot = () => EMPTY; const updateModal = (id, next) => { let changed = false; modalStack = modalStack.map((item) => { if (item.id !== id) return item; changed = true; return { ...item, props: { ...item.props, ...next } }; }); if (changed) notify(); }; const closeModal = (id) => { updateModal(id, { open: false }); }; const destroyModal = (id) => { const next = modalStack.filter((item) => item.id !== id); if (next.length === modalStack.length) return; modalStack = next; notify(); }; const StackItem = memo(({ entry }) => { const { id, props } = entry; const { children, classNames, content, footer, maskClosable, onOpenChange, onOpenChangeComplete, open, styles: semanticStyles, title, width } = props; const isOpen = open ?? true; const handleOpenChange = useCallback((nextOpen, eventDetails) => { if (!nextOpen && maskClosable === false && eventDetails?.reason === "outside-press") return; if (!nextOpen) closeModal(id); onOpenChange?.(nextOpen); }, [ id, maskClosable, onOpenChange ]); const handleExitComplete = useCallback(() => { onOpenChangeComplete?.(false); destroyModal(id); }, [id, onOpenChangeComplete]); const close = useCallback(() => closeModal(id), [id]); const setCanDismissByClickOutside = useCallback((value) => updateModal(id, { maskClosable: value }), [id]); const showTitle = title !== void 0 && title !== false && title !== null; return /* @__PURE__ */ jsx(ModalContext, { value: { close, setCanDismissByClickOutside }, children: /* @__PURE__ */ jsx(ModalRoot, { open: isOpen, onExitComplete: handleExitComplete, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxs(ModalPortal, { children: [/* @__PURE__ */ jsx(ModalBackdrop, { className: classNames?.backdrop, style: semanticStyles?.backdrop }), /* @__PURE__ */ jsxs(ModalPopup, { className: classNames?.popup, popupStyle: semanticStyles?.popup, width, children: [ showTitle && /* @__PURE__ */ jsxs(ModalHeader, { className: classNames?.header, style: semanticStyles?.header, children: [/* @__PURE__ */ jsx(ModalTitle, { className: classNames?.title, style: semanticStyles?.title, children: title }), /* @__PURE__ */ jsx(ModalClose, { className: classNames?.close, style: semanticStyles?.close })] }), /* @__PURE__ */ jsx(ModalContent, { className: classNames?.content, style: semanticStyles?.content, children: content ?? children }), footer ] })] }) }) }); }); StackItem.displayName = "ModalStackItem"; const StackRenderer = memo(({ stack }) => { if (!useIsClient()) return null; return stack.map((entry) => /* @__PURE__ */ jsx(StackItem, { entry }, entry.id)); }); StackRenderer.displayName = "ModalStackRenderer"; const Host = ({ root }) => { const stack = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); const isClient = useIsClient(); useEffect(() => { if (!isClient) return; return registerDevSingleton(singletonName, root ?? document.body); }, [isClient, root]); if (!isClient) return null; if (stack.length === 0) return null; return /* @__PURE__ */ jsx(ModalPortalWrapper, { root, children: /* @__PURE__ */ jsx(StackRenderer, { stack }) }); }; const create = (props) => { const id = `base-modal-${Date.now()}-${modalSeed++}`; modalStack = [...modalStack, { id, props: { ...props, open: props.open ?? true } }]; notify(); return { close: () => closeModal(id), destroy: () => destroyModal(id), setCanDismissByClickOutside: (value) => updateModal(id, { maskClosable: value }), update: (nextProps) => updateModal(id, nextProps) }; }; const confirm = (config) => { const instance = create({ content: /* @__PURE__ */ jsx(ConfirmBody, { config }), styles: { content: { padding: 0 } }, title: config.title, width: 420 }); return { close: instance.close, destroy: instance.destroy }; }; return { ModalHost: Host, confirmModal: confirm, createModal: create }; } const defaultSystem = createModalSystem(); const ModalHost = defaultSystem.ModalHost; const createModal = defaultSystem.createModal; const confirmModal = defaultSystem.confirmModal; //#endregion export { ModalHost, confirmModal, createModal, createModalSystem }; //# sourceMappingURL=imperative.mjs.map