Modal.tsx 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. import clsx from 'clsx';
  2. import React, { useCallback, useEffect, useState } from 'react';
  3. import styles from './Modal.module.scss';
  4. interface IProps {
  5. children: React.ReactNode;
  6. isOpen?: boolean;
  7. onClose: () => void;
  8. size?: 'sm' | 'md' | 'lg' | 'xl';
  9. type?: 'default' | 'primary' | 'success' | 'info' | 'warning' | 'danger';
  10. }
  11. export const Modal: React.FC<IProps> = ({ children, isOpen, onClose, size = 'lg', type }) => {
  12. const style = { display: 'none' };
  13. if (isOpen) {
  14. style.display = 'block';
  15. }
  16. const [modal, setModal] = useState<HTMLDivElement | null>(null);
  17. // On click outside
  18. const handleClickOutside = useCallback(
  19. (event: MouseEvent) => {
  20. if (modal && !modal.contains(event.target as Node)) {
  21. onClose();
  22. }
  23. },
  24. [modal, onClose],
  25. );
  26. // On click outside
  27. useEffect(() => {
  28. document.addEventListener('click', handleClickOutside, true);
  29. return () => document.removeEventListener('click', handleClickOutside, true);
  30. }, [handleClickOutside]);
  31. // Close on escape
  32. const handleEscape = useCallback(
  33. (event: KeyboardEvent) => {
  34. if (event.key === 'Escape') {
  35. onClose();
  36. }
  37. },
  38. [onClose],
  39. );
  40. // Close on escape
  41. useEffect(() => {
  42. document.addEventListener('keydown', handleEscape, true);
  43. return () => document.removeEventListener('keydown', handleEscape, true);
  44. }, [handleEscape]);
  45. return (
  46. <div data-testid="modal" className={clsx('modal modal-sm', styles.dimmedBackground)} tabIndex={-1} style={style} role="dialog">
  47. <div ref={setModal} className={clsx(`modal-dialog modal-dialog-centered modal-${size}`, styles.zoomIn)} role="document">
  48. <div className="shadow modal-content">
  49. <button data-testid="modal-close-button" type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close" onClick={onClose} />
  50. <div data-testid="modal-status" className={clsx('modal-status', { [`bg-${type}`]: Boolean(type), 'd-none': !type })} />
  51. {children}
  52. </div>
  53. </div>
  54. </div>
  55. );
  56. };