mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
* update modals styles * chore: color and style improvements * fix: tests * fixes: tests --------- Co-authored-by: Anoop M D <anoop.md1421@gmail.com>
172 lines
4.6 KiB
JavaScript
172 lines
4.6 KiB
JavaScript
import React, { useEffect, useState, useRef } from 'react';
|
||
import StyledWrapper from './StyledWrapper';
|
||
import useFocusTrap from 'hooks/useFocusTrap';
|
||
|
||
const ESC_KEY_CODE = 27;
|
||
const ENTER_KEY_CODE = 13;
|
||
|
||
const ModalHeader = ({ title, handleCancel, customHeader, hideClose }) => (
|
||
<div className="bruno-modal-header">
|
||
{customHeader ? customHeader : <>{title ? <div className="bruno-modal-header-title">{title}</div> : null}</>}
|
||
{handleCancel && !hideClose ? (
|
||
// TODO: Remove data-test-id and use data-testid instead across the codebase.
|
||
<div className="close cursor-pointer" onClick={handleCancel ? () => handleCancel() : null} data-testid="modal-close-button">
|
||
×
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
);
|
||
|
||
const ModalContent = ({ children }) => <div className="bruno-modal-content px-4 py-4">{children}</div>;
|
||
|
||
const ModalFooter = ({
|
||
confirmText,
|
||
cancelText,
|
||
handleSubmit,
|
||
handleCancel,
|
||
confirmDisabled,
|
||
hideCancel,
|
||
hideFooter,
|
||
confirmButtonClass = 'btn-secondary'
|
||
}) => {
|
||
confirmText = confirmText || 'Save';
|
||
cancelText = cancelText || 'Cancel';
|
||
|
||
if (hideFooter) {
|
||
return null;
|
||
}
|
||
|
||
return (
|
||
<div className="flex justify-end p-4 bruno-modal-footer">
|
||
<span className={hideCancel ? 'hidden' : 'mr-2'}>
|
||
<button type="button" onClick={handleCancel} className="btn btn-md btn-close">
|
||
{cancelText}
|
||
</button>
|
||
</span>
|
||
<span>
|
||
<button
|
||
type="submit"
|
||
className={`submit btn btn-md ${confirmButtonClass}`}
|
||
disabled={confirmDisabled}
|
||
onClick={handleSubmit}
|
||
>
|
||
{confirmText}
|
||
</button>
|
||
</span>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
const Modal = ({
|
||
size,
|
||
title,
|
||
customHeader,
|
||
confirmText,
|
||
cancelText,
|
||
handleCancel,
|
||
handleConfirm = () => {},
|
||
children,
|
||
confirmDisabled,
|
||
hideCancel,
|
||
hideFooter,
|
||
hideClose,
|
||
disableCloseOnOutsideClick,
|
||
disableEscapeKey,
|
||
onClick,
|
||
closeModalFadeTimeout = 500,
|
||
dataTestId,
|
||
confirmButtonClass
|
||
}) => {
|
||
const modalRef = useRef(null);
|
||
const [isClosing, setIsClosing] = useState(false);
|
||
|
||
const handleKeydown = (event) => {
|
||
const { keyCode, shiftKey, ctrlKey, altKey, metaKey } = event;
|
||
|
||
// Only handle events from elements inside this modal
|
||
if (keyCode !== ESC_KEY_CODE && (!modalRef.current || !modalRef.current.contains(event.target))) {
|
||
return;
|
||
}
|
||
|
||
switch (keyCode) {
|
||
case ESC_KEY_CODE: {
|
||
if (disableEscapeKey) return;
|
||
return closeModal({ type: 'esc' });
|
||
}
|
||
case ENTER_KEY_CODE: {
|
||
// Skip if a submit button is focused - let native button click handle it to avoid double-fire
|
||
const isSubmitButton = event.target?.type === 'submit';
|
||
if (!shiftKey && !ctrlKey && !altKey && !metaKey && handleConfirm && !isSubmitButton) {
|
||
return handleConfirm();
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
useFocusTrap(modalRef);
|
||
|
||
const closeModal = (args) => {
|
||
setIsClosing(true);
|
||
setTimeout(() => handleCancel(args), closeModalFadeTimeout);
|
||
};
|
||
|
||
useEffect(() => {
|
||
document.addEventListener('keydown', handleKeydown, false);
|
||
return () => {
|
||
document.removeEventListener('keydown', handleKeydown);
|
||
};
|
||
}, [disableEscapeKey, document, handleConfirm]);
|
||
|
||
let classes = 'bruno-modal';
|
||
if (isClosing) {
|
||
classes += ' modal--animate-out';
|
||
}
|
||
if (hideFooter) {
|
||
classes += ' modal-footer-none';
|
||
}
|
||
return (
|
||
<StyledWrapper className={classes} onClick={onClick ? (e) => onClick(e) : null}>
|
||
<div
|
||
className={`bruno-modal-card modal-${size}`}
|
||
ref={modalRef}
|
||
role="dialog"
|
||
aria-labelledby="modal-title"
|
||
aria-describedby="modal-description"
|
||
data-testid={dataTestId}
|
||
>
|
||
<ModalHeader
|
||
title={title}
|
||
hideClose={hideClose}
|
||
handleCancel={() => closeModal({ type: 'icon' })}
|
||
customHeader={customHeader}
|
||
/>
|
||
<ModalContent>{children}</ModalContent>
|
||
<ModalFooter
|
||
confirmText={confirmText}
|
||
cancelText={cancelText}
|
||
handleCancel={() => closeModal({ type: 'button' })}
|
||
handleSubmit={handleConfirm}
|
||
confirmDisabled={confirmDisabled}
|
||
hideCancel={hideCancel}
|
||
hideFooter={hideFooter}
|
||
confirmButtonClass={confirmButtonClass}
|
||
/>
|
||
</div>
|
||
|
||
{/* Clicking on backdrop closes the modal */}
|
||
<div
|
||
className="bruno-modal-backdrop"
|
||
onClick={
|
||
disableCloseOnOutsideClick
|
||
? null
|
||
: () => {
|
||
closeModal({ type: 'backdrop' });
|
||
}
|
||
}
|
||
/>
|
||
</StyledWrapper>
|
||
);
|
||
};
|
||
|
||
export default Modal;
|