@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
242 lines (239 loc) • 8.17 kB
JavaScript
'use client';
import { stopPropagation } from "../../utils/dom.mjs";
import { styles } from "./style.mjs";
import { ModalBackdrop, ModalContent, ModalFooter, ModalHeader, ModalPopup, ModalPortal, ModalRoot, ModalTitle } from "./atoms.mjs";
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
import { cx } from "antd-style";
import { useDragControls } from "motion/react";
import { Maximize2, Minimize2, X } from "lucide-react";
//#region src/base-ui/Modal/Modal.tsx
const OkBtn = ({ confirmLoading, okButtonProps, okText, onOk }) => {
const { className: userCls, danger, disabled: userDisabled, onClick: userOnClick, ...restOk } = okButtonProps ?? {};
return /* @__PURE__ */ jsxs("button", {
type: "button",
...restOk,
className: cx(styles.buttonBase, danger ? styles.dangerOkButton : styles.okButton, userCls),
disabled: confirmLoading || userDisabled,
onClick: (e) => {
onOk(e);
userOnClick?.(e);
},
children: [confirmLoading && /* @__PURE__ */ jsx("span", { className: styles.loadingSpinner }), okText]
});
};
const CancelBtn = ({ cancelButtonProps, cancelText, onCancel }) => {
const { className: userCls, onClick: userOnClick, ...restCancel } = cancelButtonProps ?? {};
return /* @__PURE__ */ jsx("button", {
type: "button",
...restCancel,
className: cx(styles.buttonBase, styles.cancelButton, userCls),
onClick: (e) => {
onCancel(e);
userOnClick?.(e);
},
children: cancelText
});
};
const Modal = memo(({ open, title, children, onOk, onCancel, okText = "OK", cancelText = "Cancel", okButtonProps, cancelButtonProps, confirmLoading, footer, width, height, maskClosable = true, closable = true, closeIcon, className, style, classNames, styles: semanticStyles, zIndex, afterClose, afterOpenChange, loading, getContainer, mask = true, keyboard, draggable = true, allowFullscreen = false }) => {
const dragControls = useDragControls();
const constraintsRef = useRef(null);
const [isFullscreen, setIsFullscreen] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const [isDenying, setIsDenying] = useState(false);
const denyTimerRef = useRef(void 0);
useEffect(() => () => clearTimeout(denyTimerRef.current), []);
const triggerDeny = useCallback(() => {
clearTimeout(denyTimerRef.current);
setIsDenying(true);
denyTimerRef.current = setTimeout(() => setIsDenying(false), 400);
}, []);
const handleOpenChange = useCallback((nextOpen, eventDetails) => {
if (!open) return;
if (!nextOpen && keyboard === false && eventDetails.reason === "escape-key") return;
if (!nextOpen && !maskClosable && eventDetails.reason === "outside-press") {
triggerDeny();
return;
}
if (!nextOpen) onCancel?.(new MouseEvent("click"));
}, [
onCancel,
keyboard,
maskClosable,
open,
triggerDeny
]);
const handleExitComplete = useCallback(() => {
setIsFullscreen(false);
afterClose?.();
afterOpenChange?.(false);
}, [afterClose, afterOpenChange]);
const handleAnimationComplete = useCallback(() => {
if (open) afterOpenChange?.(true);
}, [open, afterOpenChange]);
const handleDragStart = useCallback((e) => {
if (draggable && !isFullscreen) {
dragControls.start(e);
setIsDragging(true);
}
}, [
draggable,
dragControls,
isFullscreen
]);
const handleDragEnd = useCallback(() => {
setIsDragging(false);
}, []);
const handleOk = useCallback((e) => {
onOk?.(e);
}, [onOk]);
const handleCancel = useCallback((e) => {
onCancel?.(e);
}, [onCancel]);
const footerNode = useMemo(() => {
if (footer === false || footer === null) return null;
const cancelBtnNode = /* @__PURE__ */ jsx(CancelBtn, {
cancelButtonProps,
cancelText,
onCancel: handleCancel
});
const okBtnNode = /* @__PURE__ */ jsx(OkBtn, {
confirmLoading,
okButtonProps,
okText,
onOk: handleOk
});
const defaultFooter = /* @__PURE__ */ jsxs(Fragment$1, { children: [cancelBtnNode, okBtnNode] });
if (typeof footer === "function") {
const BoundCancelBtn = () => cancelBtnNode;
const BoundOkBtn = () => okBtnNode;
return footer(defaultFooter, {
CancelBtn: BoundCancelBtn,
OkBtn: BoundOkBtn
});
}
return footer ?? defaultFooter;
}, [
footer,
cancelButtonProps,
cancelText,
handleCancel,
confirmLoading,
okButtonProps,
okText,
handleOk
]);
const container = getContainer === false ? void 0 : getContainer ?? void 0;
const backdropZIndex = zIndex ? { zIndex } : void 0;
const popupZIndex = zIndex ? { zIndex: (zIndex || 1e3) + 1 } : void 0;
const shouldDrag = draggable && !isFullscreen;
const dragProps = shouldDrag ? {
drag: true,
dragConstraints: constraintsRef,
dragControls,
dragElastic: 0,
dragListener: false,
dragMomentum: false,
whileDrag: { cursor: "grabbing" }
} : {};
const showTitle = title !== void 0 && title !== false && title !== null;
const showHeader = showTitle || closable || allowFullscreen;
const hasHeight = height !== void 0;
const panelStyle = {
...hasHeight && !isFullscreen ? { height } : {},
...style
};
return /* @__PURE__ */ jsx(ModalRoot, {
open: open ?? false,
onExitComplete: handleExitComplete,
onOpenChange: handleOpenChange,
children: /* @__PURE__ */ jsxs(ModalPortal, {
container,
children: [mask && /* @__PURE__ */ jsx(ModalBackdrop, {
className: classNames?.mask,
style: {
...backdropZIndex,
...semanticStyles?.mask
}
}), /* @__PURE__ */ jsxs(ModalPopup, {
className: classNames?.wrapper,
popupStyle: {
...popupZIndex,
...semanticStyles?.wrapper
},
ref: constraintsRef,
style: panelStyle,
width: isFullscreen ? void 0 : width,
motionProps: {
...dragProps,
onAnimationComplete: handleAnimationComplete
},
panelClassName: cx(className, isFullscreen && styles.fullscreenPopupInner, isDenying && styles.denyAnimation),
children: [
showHeader && /* @__PURE__ */ jsxs(ModalHeader, {
className: cx(classNames?.header, shouldDrag && styles.headerDraggable),
style: {
...isDragging ? { cursor: "grabbing" } : {},
...semanticStyles?.header
},
onPointerCancel: handleDragEnd,
onPointerDown: handleDragStart,
onPointerUp: handleDragEnd,
children: [showTitle ? /* @__PURE__ */ jsx(ModalTitle, {
className: classNames?.title,
style: semanticStyles?.title,
children: title
}) : /* @__PURE__ */ jsx("span", {}), /* @__PURE__ */ jsxs("div", {
className: styles.headerActions,
onPointerDown: stopPropagation,
children: [allowFullscreen && /* @__PURE__ */ jsx("button", {
"aria-label": isFullscreen ? "Exit fullscreen" : "Fullscreen",
className: styles.fullscreenToggle,
type: "button",
onClick: () => setIsFullscreen((prev) => !prev),
children: isFullscreen ? /* @__PURE__ */ jsx(Minimize2, { size: 14 }) : /* @__PURE__ */ jsx(Maximize2, { size: 14 })
}), closable && /* @__PURE__ */ jsx("button", {
"aria-label": "Close",
className: styles.closeInline,
type: "button",
onClick: handleCancel,
children: closeIcon ?? /* @__PURE__ */ jsx(X, { size: 18 })
})]
})]
}),
/* @__PURE__ */ jsx(ModalContent, {
className: classNames?.body,
style: {
...hasHeight || isFullscreen ? { flex: 1 } : {},
...semanticStyles?.body
},
children: loading ? /* @__PURE__ */ jsx("div", {
style: {
display: "flex",
justifyContent: "center",
padding: "32px 0"
},
children: /* @__PURE__ */ jsx("span", {
className: styles.loadingSpinner,
style: {
height: 24,
width: 24
}
})
}) : children
}),
footerNode !== null && /* @__PURE__ */ jsx(ModalFooter, {
className: classNames?.footer,
style: semanticStyles?.footer,
children: footerNode
})
]
})]
})
});
});
Modal.displayName = "Modal";
var Modal_default = Modal;
//#endregion
export { Modal_default as default };
//# sourceMappingURL=Modal.mjs.map