department-of-veterans-affairs/vets-website

View on GitHub
src/applications/vaos/components/calendar/CalendarOptionsSlots.jsx

Summary

Maintainability
A
3 hrs
Test Coverage
import React from 'react';
import moment from 'moment';
import classNames from 'classnames';

/* 
 * Because we want to create a background for a jagged grid of cells,
 * we need to set padding and border radius for each cell depending on
 * where it is in the grid and how many cells per row we're displaying
 * 
 * This was a huge pain to figure out
 */
function getOptionClasses(index, optionCount, rowSize) {
  return classNames(
    'vaos-calendar__option-cell',
    'vaos-calendar__option-cell--radio',
    {
      'vaos-u-border-radius--top-left': index === 0,
      'vaos-u-border-radius--top-right':
        index + 1 === rowSize ||
        (index + 1 === optionCount && optionCount < rowSize),
      'vaos-u-border-radius--bottom-left':
        index % rowSize === 0 && index + rowSize >= optionCount,
      'vaos-u-border-radius--bottom-right':
        (index + 1 === optionCount && optionCount < rowSize) ||
        ((index + 1) % rowSize === 0 && index + rowSize >= optionCount),
      'vads-u-padding-top--2': rowSize - index > 0,
      'vads-u-padding-left--2': index % rowSize === 0,
      'vads-u-padding-right--2':
        (index + 1) % rowSize === 0 ||
        (index + 1 === optionCount && optionCount < rowSize),
      'vaos-calendar__option-cell--last':
        index + 1 === optionCount &&
        (index + 1) % rowSize !== 0 &&
        optionCount > rowSize,
    },
  );
}

export default function CalendarOptionsSlots({
  availableSlots,
  currentlySelectedDate,
  selectedDates,
  rowSize,
  selectedCellIndex,
  maxSelections,
  hasError,
  onChange,
  id,
  timezone,
  showWeekends,
}) {
  const currentSlots = availableSlots.filter(slot =>
    slot?.start?.startsWith(currentlySelectedDate),
  );

  // [0, 1, 2, 3, 4, 5, 6]
  const maxCellsPerRow = rowSize;
  const middleCellIndex = showWeekends ? [2, 3, 4] : [2];
  const tuesThursCellIndex = [2, 4];
  const beginningCellIndex = [0, 1];
  const endCellIndexes = showWeekends ? [5, 6] : [3, 4];
  const justifyClasses =
    currentSlots.length < maxCellsPerRow
      ? {
          'vads-u-justify-content--flex-start': beginningCellIndex.includes(
            selectedCellIndex,
          ),
          'vads-u-justify-content--center': middleCellIndex.includes(
            selectedCellIndex,
          ),
          'vads-u-justify-content--flex-end': endCellIndexes.includes(
            selectedCellIndex,
          ),
        }
      : {};

  const marginClasses =
    currentSlots.length <= 1 && showWeekends
      ? {
          'vaos-calendar__option-cell--full-width': tuesThursCellIndex.includes(
            selectedCellIndex,
          ),
        }
      : {};

  // If list of items won't fill row, align items closer to selected cell
  const cssClasses = classNames('vaos-calendar__options', {
    'vads-u-padding-left--1p5': hasError,
    ...marginClasses,
    ...justifyClasses,
  });
  return (
    <div className={cssClasses}>
      {currentSlots.map((slot, index) => {
        const checked = selectedDates.some(
          selectedDate => selectedDate === slot.start,
        );
        let time = moment(slot.start);
        if (slot.start.endsWith('Z') && timezone) {
          time = time.tz(timezone);
        }
        const meridiem = time.format('A');
        const screenReaderMeridiem = meridiem.replace(/\./g, '').toUpperCase();
        const label = (
          <>
            {time.format('h:mm')} <span aria-hidden="true">{meridiem}</span>{' '}
            <span className="sr-only">{screenReaderMeridiem}</span>
          </>
        );

        if (maxSelections > 1) {
          throw new Error(
            'Multi-select is currently not implemented without using renderOptions',
          );
        }

        return (
          <div
            className={getOptionClasses(index, currentSlots.length, rowSize)}
            key={`option-${index}`}
          >
            <div className="vaos-calendar__option vaos-calendar__option--radio">
              <input
                id={`${id}_${currentlySelectedDate}_${index}`}
                type="radio"
                name={id}
                value={slot.start}
                checked={checked}
                onChange={() => onChange(slot.start)}
              />
              <label
                className="vads-u-margin--0 vads-u-font-weight--bold vads-u-color--primary"
                htmlFor={`${id}_${currentlySelectedDate}_${index}`}
              >
                <span aria-hidden="true">{label}</span>
                <span className="vads-u-visibility--screen-reader">
                  {label} option selected
                </span>
              </label>
            </div>
          </div>
        );
      })}
    </div>
  );
}