ahbeng/NUSMods

View on GitHub
website/src/views/planner/ModuleMenu.tsx

Summary

Maintainability
A
2 hrs
Test Coverage
import { memo, useLayoutEffect, useRef, useState } from 'react';

import { ChevronDown } from 'react-feather';
import Downshift from 'downshift';
import classnames from 'classnames';
import styles from './PlannerModule.scss';

type Props = {
  readonly removeModule: () => void;
  readonly editCustomData: () => void;
};

type MenuItem = {
  label: string;
  action: () => void;
  className?: string;
};

const ModuleMenu = memo((props: Props) => {
  const [isMenuOverflowing, setMenuOverflowing] = useState<boolean>(false);
  const [hasRendered, setHasRendered] = useState<boolean>(false);
  const myRef = useRef<HTMLDivElement>(null);

  const menuItems: MenuItem[] = [
    { label: 'Edit Unit and Title', action: props.editCustomData },
    { label: 'Remove', action: props.removeModule, className: 'dropdown-item-danger' },
  ];

  useLayoutEffect(() => {
    if (myRef.current === null) {
      return;
    }

    const rect = myRef.current.getBoundingClientRect();
    setMenuOverflowing(rect.right <= window.innerWidth);
  }, [hasRendered, myRef]);

  const toggleRender = () => setHasRendered((hasAlreadyRendered) => !hasAlreadyRendered);

  return (
    <Downshift
      onSelect={(item) => {
        menuItems.forEach(({ label, action }) => {
          if (item === label) {
            action();
          }
        });
      }}
    >
      {({ getItemProps, getMenuProps, highlightedIndex, isOpen, toggleMenu }) => (
        <div className={styles.menuBtn}>
          <button
            className={classnames('btn close')}
            type="button"
            onClick={() => {
              toggleMenu();
              toggleRender();
            }}
            data-toggle="dropdown"
            aria-haspopup="true"
            aria-expanded={isOpen}
          >
            <ChevronDown />
          </button>
          <div
            className={classnames(
              styles.menu,
              'dropdown-menu',
              { show: isOpen },
              isMenuOverflowing ? styles.menuRight : styles.menuLeft,
            )}
            {...getMenuProps({ ref: myRef })}
          >
            {menuItems.map(({ label, className }, itemIndex) => (
              <button
                type="button"
                key={label}
                className={classnames('dropdown-item', className, {
                  'dropdown-selected': highlightedIndex === itemIndex,
                })}
                {...getItemProps({ item: label })}
              >
                {label}
              </button>
            ))}
          </div>
        </div>
      )}
    </Downshift>
  );
});

export default ModuleMenu;