UNPKG

@intility/bifrost-react

Version:

React library for Intility's design system, Bifrost.

117 lines (116 loc) 5.08 kB
"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;