All files / components/SettingsModal SettingsModal.tsx

94.59% Statements 70/74
75% Branches 9/12
100% Functions 2/2
94.59% Lines 70/74

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 751x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 6x 10x 10x 10x 6x 4x     4x     4x 4x 4x 6x 6x 6x 6x 6x 6x 6x 10x 10x 10x 1x 1x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 3x 10x 10x 10x 10x 10x 10x 10x 10x  
import { useEffect, useState } from 'react';
 
import { debounce } from 'lodash';
 
import { c } from '../../helpers';
import { hasClosestElement } from '../../helpers/htmlSelectorsHelpers';
import { IconVerticalDots } from '../Icons';
import {
  SETTINGS_MODAL_TIMEOUT_TO_CLOSE,
  SettingsModalPosition,
} from './SettingsModalConstants';
import { SettingsModalProps } from './SettingsModalProps';
 
import styles from './SettingsModal.module.css';
 
export default function SettingsModal({
  className,
  items = [],
  position = SettingsModalPosition.LEFT,
  visible,
  onClose,
}: SettingsModalProps): JSX.Element {
  const [openerWrapperId] = useState(crypto.randomUUID());
  const [innerVisible, setInnerVisible] = useState(visible);
 
  useEffect(() => {
    setInnerVisible(visible);
  }, [visible]);
 
  useEffect(() => {
    const handleClick = debounce((event: MouseEvent): void => {
      if (event.defaultPrevented) {
        return;
      }
      if (hasClosestElement(event, openerWrapperId)) {
        return;
      }
 
      setInnerVisible(false);
      onClose?.();
    }, SETTINGS_MODAL_TIMEOUT_TO_CLOSE);
 
    window.document.body.addEventListener('click', handleClick, true);
 
    return () => {
      window.document.body.removeEventListener('click', handleClick);
    };
  }, [openerWrapperId, onClose]);
 
  const handleOpenerClick = (): void => {
    setInnerVisible(!innerVisible);
  };
 
  return (
    <div
      className={c(
        className,
        styles.settings_menu,
        innerVisible ? styles.open : styles.close,
        styles[position]
      )}
      id={openerWrapperId}
    >
      <ul className={c(styles.menu)}>
        {items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
      <button onClick={handleOpenerClick} className={c(styles.opener)}>
        <IconVerticalDots className={c(styles.opener_icon)} />
      </button>
    </div>
  );
}