@intility/bifrost-react
Version:
React library for Intility's design system, Bifrost.
117 lines (116 loc) • 5.08 kB
JavaScript
"use client";
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import React, { forwardRef, useLayoutEffect, useRef, useState } from "react";
import classNames from "classnames";
import { faXmark } from "@fortawesome/free-solid-svg-icons/faXmark";
import useLocale from "../../hooks/useLocale.js";
import setRef from "../../utils/setRef.js";
import Icon from "../Icon/Icon.js";
import useFloatingMessage from "../../hooks/useFloatingMessage.js";
import { FloatingMessageStack } from "../FloatingMessage/FloatingMessage.js";
const Modal = /*#__PURE__*/ forwardRef(({ children, className, header, style, width = "100%", isOpen = false, onRequestClose, noPadding = false, noCloseButton = false, noCloseOnOverlayClick = false, noCloseOnEsc = false, transparent = false, ...props }, ref)=>{
const locale = useLocale();
const { _stack } = useFloatingMessage();
const dialogRef = useRef(null);
// store the target of the previous mousedown event on the dialog (which can be a child element)
// to prevent closing the dialog if mousedown and click (release) targets are different
const previousDialogMouseDownTarget = useRef(null);
// state to delay rendering modal content until the dialog is actually opened, to ensure react's internal autoFocus still works
const [dialogIsOpen, setDialogIsOpen] = useState(false);
useLayoutEffect(()=>{
if (isOpen && !dialogRef.current?.open) {
// open boolean toggled to true, and currently closed, open the modal
dialogRef.current?.showModal();
}
if (!isOpen && dialogRef.current?.open) {
// toggled to false and is currently open, close the modal
dialogRef.current?.close();
}
// update dialogIsOpen state to allow rendering children
setDialogIsOpen(isOpen);
}, [
isOpen
]);
return /*#__PURE__*/ _jsxs("dialog", {
...props,
className: classNames(className, "bf-modal", "bf-scrollbar-small", "bf-open-sans"),
style: {
...style,
"--bf-modal-width": typeof width === "number" ? width + "px" : width
},
onCancel: (e)=>{
// allow custom onCancel prop
props.onCancel?.(e);
// if it's not the dialog cancel event, do nothing
if (e.target !== dialogRef.current) return;
// Prevent closing on ESC (except when pressing ESC twice in chromium due to browser bug)
e.preventDefault();
if (noCloseOnEsc) {
// prevent calling onRequestClose on ESC
return;
}
onRequestClose?.();
},
// Workaround for chromium bug where the dialog element closes when the ESC
// key is pressed two times in a row, even if preventing default on the onCancel event
// https://issues.chromium.org/issues/41491338
onClose: ()=>{
if (isOpen) {
dialogRef.current?.showModal();
}
},
onMouseDown: (e)=>{
previousDialogMouseDownTarget.current = e.target;
},
onClick: (e)=>{
if (// clicking on dialog content
dialogRef.current === e.target && // clicking and dragging cursor outside content
previousDialogMouseDownTarget.current === e.target && !noCloseOnOverlayClick) {
// close on overlay click
onRequestClose?.();
}
},
ref: (r)=>{
setRef(ref, r);
setRef(dialogRef, r);
},
children: [
onRequestClose && !noCloseButton && /*#__PURE__*/ _jsx("button", {
type: "button",
onClick: ()=>onRequestClose(),
className: "bf-modal-close",
"aria-label": locale.closeModal,
children: /*#__PURE__*/ _jsx(Icon, {
icon: faXmark
})
}),
dialogIsOpen && /*#__PURE__*/ _jsxs(_Fragment, {
children: [
/*#__PURE__*/ _jsxs("div", {
className: classNames("bf-modal-content", {
"bf-modal-transparent": transparent,
"bf-no-padding": noPadding
}),
children: [
header && /*#__PURE__*/ _jsx("header", {
className: "bf-modal-header",
children: header
}),
children
]
}),
_stack && /*#__PURE__*/ _jsx(FloatingMessageStack, {})
]
}),
!dialogIsOpen && /*#__PURE__*/ _jsxs("div", {
hidden: true,
children: [
header,
children
]
})
]
});
});
Modal.displayName = "Modal";
export default Modal;