ahbeng/NUSMods

View on GitHub
website/src/views/timetable/TimetableRow.tsx

Summary

Maintainability
A
1 hr
Test Coverage
import * as React from 'react';

import { HoverLesson, ModifiableLesson } from 'types/timetables';
import { OnHoverCell, OnModifyCell } from 'types/views';

import { convertTimeToIndex } from 'utils/timify';
import styles from './TimetableRow.scss';
import TimetableCell from './TimetableCell';

type Props = {
  verticalMode: boolean;
  showTitle: boolean;
  startingIndex: number;
  endingIndex: number;
  lessons: ModifiableLesson[];
  hoverLesson?: HoverLesson | null;
  onCellHover: OnHoverCell;
  onModifyCell?: OnModifyCell;
};

/**
 * Position the lessons properly on the row.
 * In horizontal mode, we use margin to insert space for elements,
 * which are relative to each other.
 * In vertical mode, we use absolute positioning to place lessons
 * relative to the parent.
 *
 * Reasoning for doing so is that we need rows to resize according to their
 * children's height, in which absolute positioning would not allow.
 */
const TimetableRow: React.FC<Props> = (props) => {
  const { startingIndex, endingIndex, lessons, onModifyCell, verticalMode } = props;
  const totalCols = endingIndex - startingIndex;

  let lastStartIndex = startingIndex;

  return (
    <div className={styles.timetableRow}>
      {lessons.map((lesson) => {
        const startIndex = convertTimeToIndex(lesson.startTime);
        const endIndex = convertTimeToIndex(lesson.endTime);

        const size = Math.max(endIndex - startIndex, 1);

        const dirStyle = verticalMode ? 'top' : 'marginLeft';
        const sizeStyle = verticalMode ? 'height' : 'width';

        const dirValue = startIndex - (verticalMode ? startingIndex : lastStartIndex);
        const style = {
          // calc() adds a 1px gap between cells
          [dirStyle]: `calc(${(dirValue / totalCols) * 100}% + 1px)`,
          [sizeStyle]: `calc(${(size / totalCols) * 100}% - 1px)`,
        };

        lastStartIndex = endIndex;

        const conditionalProps =
          lesson.isModifiable && onModifyCell
            ? {
                onClick: (position: ClientRect) => onModifyCell(lesson, position),
              }
            : {};

        return (
          <TimetableCell
            key={lesson.startTime}
            style={style}
            lesson={lesson}
            showTitle={props.showTitle}
            hoverLesson={props.hoverLesson}
            onHover={props.onCellHover}
            transparent={lesson.startTime === lesson.endTime}
            {...conditionalProps}
          />
        );
      })}
    </div>
  );
};

export default TimetableRow;