@saas-ui/modals
Version:
A modal manager for Chakra UI
469 lines (462 loc) • 12 kB
JavaScript
'use client'
import {
BaseModal,
FormDialog,
Modal,
createFormDialog
} from "./chunk-FRC2HTO7.mjs";
// src/dialog.tsx
import * as React from "react";
import {
AlertDialog,
AlertDialogBody,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogContent,
AlertDialogOverlay,
ButtonGroup,
Button
} from "@chakra-ui/react";
import { jsx, jsxs } from "react/jsx-runtime";
var ConfirmDialog = (props) => {
const {
title,
cancelLabel = "Cancel",
confirmLabel = "Confirm",
cancelProps,
confirmProps,
buttonGroupProps,
isOpen,
closeOnCancel = true,
closeOnConfirm = true,
leastDestructiveFocus = "cancel",
onClose,
onCancel,
onConfirm,
children,
...rest
} = props;
const cancelRef = React.useRef(null);
const confirmRef = React.useRef(null);
const [isLoading, setIsLoading] = React.useState(false);
const handleConfirm = async () => {
try {
const result = onConfirm == null ? void 0 : onConfirm();
if (typeof (result == null ? void 0 : result.then) === "function") {
setIsLoading(true);
await result;
}
closeOnConfirm && onClose();
} catch (e) {
throw e;
} finally {
setIsLoading(false);
}
};
return /* @__PURE__ */ jsx(
AlertDialog,
{
isOpen,
onClose,
...rest,
leastDestructiveRef: leastDestructiveFocus === "cancel" ? cancelRef : confirmRef,
children: /* @__PURE__ */ jsx(AlertDialogOverlay, { children: /* @__PURE__ */ jsxs(AlertDialogContent, { children: [
/* @__PURE__ */ jsx(AlertDialogHeader, { children: title }),
/* @__PURE__ */ jsx(AlertDialogBody, { children }),
/* @__PURE__ */ jsx(AlertDialogFooter, { children: /* @__PURE__ */ jsxs(ButtonGroup, { ...buttonGroupProps, children: [
/* @__PURE__ */ jsx(
Button,
{
ref: cancelRef,
...cancelProps,
onClick: () => {
onCancel == null ? void 0 : onCancel();
closeOnCancel && onClose();
},
children: (cancelProps == null ? void 0 : cancelProps.children) || cancelLabel
}
),
/* @__PURE__ */ jsx(
Button,
{
ref: confirmRef,
isLoading,
...confirmProps,
onClick: handleConfirm,
children: (confirmProps == null ? void 0 : confirmProps.children) || confirmLabel
}
)
] }) })
] }) })
}
);
};
// src/drawer.tsx
import {
Drawer as ChakraDrawer,
DrawerOverlay,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerBody,
DrawerCloseButton
} from "@chakra-ui/react";
import { runIfFn } from "@chakra-ui/utils";
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
var BaseDrawer = (props) => {
const {
title,
children,
footer,
isOpen,
onClose,
hideCloseButton,
hideOverlay,
headerProps,
contentProps,
footerProps,
...rest
} = props;
return /* @__PURE__ */ jsxs2(ChakraDrawer, { isOpen, onClose, ...rest, children: [
!hideOverlay && /* @__PURE__ */ jsx2(DrawerOverlay, {}),
/* @__PURE__ */ jsxs2(DrawerContent, { ...contentProps, children: [
title && /* @__PURE__ */ jsx2(DrawerHeader, { ...headerProps, children: title }),
!hideCloseButton && /* @__PURE__ */ jsx2(DrawerCloseButton, {}),
runIfFn(children, {
isOpen,
onClose
}),
footer && /* @__PURE__ */ jsx2(DrawerFooter, { ...footerProps, children: footer })
] })
] });
};
var Drawer = (props) => {
const { children, isOpen, onClose, ...rest } = props;
return /* @__PURE__ */ jsx2(BaseDrawer, { isOpen, onClose, ...rest, children: /* @__PURE__ */ jsx2(DrawerBody, { children: runIfFn(children, {
isOpen,
onClose
}) }) });
};
// src/menu.tsx
import {
ModalFooter,
chakra,
forwardRef,
useMenuContext,
useMenuList,
createStylesContext,
useMultiStyleConfig,
Menu,
useBreakpointValue
} from "@chakra-ui/react";
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
var [StylesProvider] = createStylesContext("SuiMenuDialog");
var MenuDialog = (props) => {
const { onClose, onCloseComplete, ...rest } = props;
return /* @__PURE__ */ jsx3(
Menu,
{
variant: "dialog",
onClose: () => {
onClose == null ? void 0 : onClose();
onCloseComplete == null ? void 0 : onCloseComplete();
},
...rest
}
);
};
var MenuDialogList = forwardRef(
(props, forwardedRef) => {
const {
rootProps,
title,
footer,
initialFocusRef,
hideCloseButton,
motionPreset = "slideInBottom",
isCentered: isCenteredProp,
...rest
} = props;
const { isOpen, onClose, menuRef } = useMenuContext();
const { ref, ...ownProps } = useMenuList(rest, forwardedRef);
const styles = useMultiStyleConfig("Menu", props);
const isCentered = useBreakpointValue({ base: true, md: false });
return /* @__PURE__ */ jsxs3(
BaseModal,
{
isOpen,
onClose,
initialFocusRef: initialFocusRef || menuRef,
title,
hideCloseButton,
motionPreset,
isCentered: isCenteredProp != null ? isCenteredProp : isCentered,
contentProps: { mx: 4 },
children: [
/* @__PURE__ */ jsx3(StylesProvider, { value: styles, children: /* @__PURE__ */ jsx3(
chakra.div,
{
...ownProps,
ref,
__css: {
outline: 0,
maxHeight: "80vh",
// can override this in theme
overflowY: "auto",
// can override this in theme
...styles.list,
boxShadow: "none",
border: 0,
_dark: {
/* @ts-expect-error */
...styles.list._dark || {},
boxShadow: "none"
}
}
}
) }),
footer && /* @__PURE__ */ jsx3(ModalFooter, { children: footer })
]
}
);
}
);
// src/provider.tsx
import * as React2 from "react";
// src/default-modals.ts
var defaultModals = {
alert: ConfirmDialog,
confirm: ConfirmDialog,
drawer: Drawer,
modal: Modal,
menu: MenuDialog,
form: FormDialog
};
// src/provider.tsx
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
var ModalsContext = React2.createContext(null);
var initialModalState = {
id: null,
props: null,
type: "modal"
};
function ModalsProvider({ children, modals }) {
const _instances = React2.useMemo(() => /* @__PURE__ */ new Set(), []);
const [activeModals, setActiveModals] = React2.useState({
modal: initialModalState
});
const getModalComponent = React2.useMemo(() => {
const _modals = {
...defaultModals,
...modals
};
return (type = "modal") => {
const component = _modals[type] || _modals.modal;
return component;
};
}, [modals]);
const setActiveModal = (modal, scope) => {
if (!scope) {
return setActiveModals({
modal
});
}
setActiveModals((prevState) => ({
...prevState,
[scope]: modal
}));
};
const open = (componentOrOptions, options) => {
let _options;
if (typeof componentOrOptions === "function") {
_options = {
component: componentOrOptions,
...options
};
} else {
_options = componentOrOptions;
}
const {
id = _instances.size + 1,
type = "modal",
scope = "modal",
component,
...props
} = _options;
const modal = {
id,
props,
type,
scope,
component,
isOpen: true
};
_instances.add(modal);
setActiveModal(modal, scope);
return id;
};
const drawer = (options) => {
return open({
...options,
type: "drawer"
});
};
const alert = (options) => {
return open({
...options,
scope: "alert",
type: "alert",
cancelProps: {
display: "none"
},
confirmProps: {
label: "OK"
},
leastDestructiveFocus: "confirm"
});
};
const confirm = (options) => {
return open({
...options,
scope: "alert",
type: "confirm"
});
};
const menu = (options) => {
return open({
...options,
type: "menu"
});
};
const form = (options) => {
return open({
...options,
type: "form"
});
};
const close = async (id, force) => {
var _a, _b;
const modals2 = [...Array.from(_instances)];
const modal = modals2.filter((modal2) => modal2.id === id)[0];
if (!modal) {
return;
}
const shouldClose = await ((_b = (_a = modal.props) == null ? void 0 : _a.onClose) == null ? void 0 : _b.call(_a, { force }));
if (shouldClose === false) {
return;
}
const scoped = modals2.filter(({ scope }) => scope === modal.scope);
if (scoped.length === 1) {
setActiveModal(
{
...modal,
isOpen: false
},
modal.scope
);
} else if (scoped.length > 1) {
setActiveModal(scoped[scoped.length - 2], modal.scope);
} else {
setActiveModal(
{
id: null,
props: null,
type: modal.type
// Keep type same as last modal type to make sure the animation isn't interrupted
},
modal.scope
);
}
setTimeout(() => closeComplete(id), 200);
};
const closeComplete = (id) => {
const modals2 = [...Array.from(_instances)];
const modal = modals2.filter((modal2) => modal2.id === id)[0];
_instances.delete(modal);
const scoped = modal && modals2.filter(({ scope }) => scope === modal.scope);
if ((scoped == null ? void 0 : scoped.length) === 1) {
setActiveModal(initialModalState, modal.scope);
}
};
const closeAll = () => {
_instances.forEach((modal) => {
var _a, _b;
return (_b = (_a = modal.props) == null ? void 0 : _a.onClose) == null ? void 0 : _b.call(_a, { force: true });
});
_instances.clear();
setActiveModal(initialModalState);
};
const context = {
open,
drawer,
alert,
confirm,
menu,
form,
close,
closeAll
};
const content = React2.useMemo(
() => Object.entries(activeModals).map(([scope, config]) => {
const Component = config.component || getModalComponent(config.type);
const { title, body, children: children2, ...props } = config.props || {};
return /* @__PURE__ */ jsx4(
Component,
{
title,
children: body || children2,
...props,
isOpen: !!config.isOpen,
onClose: () => close(config.id),
onCloseComplete: () => closeComplete(config.id)
},
scope
);
}),
[activeModals]
);
return /* @__PURE__ */ jsxs4(ModalsContext.Provider, { value: context, children: [
content,
children
] });
}
var useModalsContext = () => React2.useContext(ModalsContext);
var useModals = () => {
const modals = useModalsContext();
if (!modals) {
throw new Error("useModals must be used within a ModalsProvider");
}
return modals;
};
// src/create-modals.tsx
import { jsx as jsx5 } from "react/jsx-runtime";
var createModals = (options) => {
const modals = {
...defaultModals,
...options.modals
};
const Provider = (props) => {
return /* @__PURE__ */ jsx5(ModalsProvider, { children: props.children, modals });
};
return {
ModalsProvider: Provider,
useModals
};
};
export {
BaseDrawer,
BaseModal,
ConfirmDialog,
Drawer,
FormDialog,
MenuDialog,
MenuDialogList,
Modal,
ModalsContext,
ModalsProvider,
createFormDialog,
createModals,
useModals,
useModalsContext
};
//# sourceMappingURL=index.mjs.map