WikiEducationFoundation/WikiEduDashboard

View on GitHub
app/assets/javascripts/components/common/weekday_picker.jsx

Summary

Maintainability
C
1 day
Test Coverage
F
56%
import React from 'react';
import PropTypes from 'prop-types';

const WEEKDAYS_LONG = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const WEEKDAYS_SHORT = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];

const localeUtils = {
  formatWeekdayLong: function (weekday) {
    return WEEKDAYS_LONG[weekday];
  },
  formatWeekdayShort: function (weekday) {
    return WEEKDAYS_SHORT[weekday];
  }
};

const keys = {
  LEFT: 37,
  RIGHT: 39,
  ENTER: 13,
  SPACE: 32
};

const WeekdayPicker = ({
  className: componentClassName,
  style,
  tabIndex,

  ariaModifier,
  modifiers,

  locale,
  onWeekdayClick,
  onWeekdayMouseEnter,
  onWeekdayMouseLeave,
}) => {
  const getModifiersForDay = (weekday, modifierFunctions) => {
    const resultModifiers = [];
    if (modifierFunctions) {
      Object.keys(modifierFunctions).forEach((modifier) => {
        const func = modifierFunctions[modifier];
        if (func(weekday)) {
          resultModifiers.push(modifier);
        }
      });
    }
    return resultModifiers;
  };

  const focusPreviousDay = (dayNode) => {
    const body = dayNode.parentNode.parentNode.parentNode.parentNode;
    const dayNodes = body.querySelectorAll('.DayPicker-Weekday:not(.DayPicker-Weekday--outside)');
    let nodeIndex;
    for (let i = 0; i < dayNodes.length; i += 1) {
      if (dayNodes[i] === dayNode) {
        nodeIndex = i;
        break;
      }
    }
    if (nodeIndex !== 0) {
      dayNodes[nodeIndex - 1].focus();
    }
  };

  const focusNextDay = (dayNode) => {
    const body = dayNode.parentNode.parentNode.parentNode.parentNode;
    const dayNodes = body.querySelectorAll('.DayPicker-Weekday:not(.DayPicker-Weekday--outside)');
    let nodeIndex;
    for (let i = 0; i < dayNodes.length; i += 1) {
      if (dayNodes[i] === dayNode) {
        nodeIndex = i;
        break;
      }
    }

    if (nodeIndex !== dayNodes.length - 1) {
      dayNodes[nodeIndex + 1].focus();
    }
  };

  const handleDayKeyDown = (e, weekday, modifiersProp) => {
    e.persist();
    switch (e.keyCode) {
      case keys.LEFT:
        e.preventDefault();
        e.stopPropagation();
        focusPreviousDay(e.target);
        break;
      case keys.RIGHT:
        e.preventDefault();
        e.stopPropagation();
        focusNextDay(e.target);
        break;
      case keys.ENTER:
      case keys.SPACE:
        e.preventDefault();
        e.stopPropagation();
        if (onWeekdayClick) {
          handleWeekdayClick(e, weekday, modifiersProp);
        }
        break;
      default:
        // no default
    }
  };

  const handleWeekdayClick = (e, weekday, modifiersProp) => {
    e.persist();
    onWeekdayClick(e, weekday, modifiersProp);
  };

  const handleWeekdayMouseEnter = (e, weekday, modifiersProp) => {
    e.persist();
    onWeekdayMouseEnter(e, weekday, modifiersProp);
  };

  const handleWeekdayMouseLeave = (e, weekday, modifiersProp) => {
    e.persist();
    onWeekdayMouseLeave(e, weekday, modifiersProp);
  };

  const renderWeekDays = () => {
    const weekdays = [];
    for (let i = 0; i < 7; i += 1) {
      weekdays.push(renderWeekday(i));
    }
    return (
      <div className="DayPicker-Month">
        <div className="DayPicker-Weekdays">
          <div>
            {weekdays}
          </div>
        </div>
      </div>
    );
  };

  const renderWeekday = (weekday) => {
    let dayClassName = 'DayPicker-Weekday';
    let customModifiers = [];

    if (modifiers) {
      const propModifiers = getModifiersForDay(weekday, modifiers);
      customModifiers = [...customModifiers, ...propModifiers];
    }

    dayClassName += customModifiers.map(modifier => ` ${dayClassName}--${modifier}`).join('');

    const ariaSelected = customModifiers.indexOf(ariaModifier) > -1;

    let tabIndexValue = null;
    if (onWeekdayClick) {
      tabIndexValue = -1;
      if (weekday === 0) {
        tabIndexValue = tabIndex;
      }
    }

    const onClickHandler = onWeekdayClick ? e => handleWeekdayClick(e, weekday, customModifiers) : null;
    const onMouseEnterHandler = onWeekdayMouseEnter ? e => handleWeekdayMouseEnter(e, weekday, customModifiers) : null;
    const onMouseLeaveHandler = onWeekdayMouseLeave ? e => handleWeekdayMouseLeave(e, weekday, customModifiers) : null;

    return (
      <button
        key={weekday} className={dayClassName} tabIndex={tabIndexValue}
        aria-pressed={ariaSelected}
        onClick={onClickHandler}
        onKeyDown={e => handleDayKeyDown(e, weekday, customModifiers)}
        onMouseEnter={onMouseEnterHandler}
        onMouseLeave={onMouseLeaveHandler}
      >
        <span title={localeUtils.formatWeekdayLong(weekday)}>
          {localeUtils.formatWeekdayShort(weekday)}
        </span>
      </button>
    );
  };

  let finalClassName = `WeekdayPicker DayPicker DayPicker--${locale}`;

  if (!onWeekdayClick) {
    finalClassName = `${finalClassName} WeekdayPicker--InteractionDisabled`;
  }
  if (componentClassName) {
    finalClassName = `${finalClassName} ${componentClassName}`;
  }

  return (
    <div
      className={finalClassName}
      role="widget"
      style={style}
      tabIndex={tabIndex}
    >
      {renderWeekDays()}
    </div>
  );
};

WeekdayPicker.propTypes = {
  className: PropTypes.string,
  style: PropTypes.object,
  tabIndex: PropTypes.number,

  ariaModifier: PropTypes.string,
  modifiers: PropTypes.object,

  locale: PropTypes.string,
  onWeekdayClick: PropTypes.func,
  onWeekdayMouseEnter: PropTypes.func,
  onWeekdayMouseLeave: PropTypes.func,
};

export default WeekdayPicker;