TacoTableRow.js

import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import TacoTableCell from './TacoTableCell';

const propTypes = {
  columns: PropTypes.array.isRequired,
  columnGroups: PropTypes.array,
  columnSummaries: PropTypes.array,
  className: PropTypes.string,
  highlighted: PropTypes.bool,
  highlightedColumnId: PropTypes.string,
  isBottomData: PropTypes.bool,
  onClick: PropTypes.func,
  onColumnHighlight: PropTypes.func,
  onDoubleClick: PropTypes.func,
  onHighlight: PropTypes.func,
  plugins: PropTypes.array,
  rowData: PropTypes.object.isRequired,
  rowNumber: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  tableData: PropTypes.array,
  CellComponent: PropTypes.func,
};

const defaultProps = {
  columnSummaries: [],
  CellComponent: TacoTableCell,
};

/**
 * React component for rendering table rows, uses `<tr>`.
 *
 * @prop {Object[]} columns  The column definitions
 * @prop {Object[]} columnGroups  How to group columns - an array of
 *   `{ header:String, columns:[colId1, colId2, ...], className:String}`
 * @prop {Object[]} columnSummaries  An array of summaries, one for each column, matched by index
 * @prop {String} className  The class name for the row
 * @prop {Boolean} highlighted Whether this row is highlighted or not
 * @prop {String} highlightedColumnId   The ID of the highlighted column
 * @prop {Boolean} isBottomData  Whether this row is in the bottom data area or not
 * @prop {Function} onClick  callback for when a row is clicked
 * @prop {Function} onColumnHighlight  callback for when a column is highlighted / unhighlighted
 * @prop {Function} onDoubleClick  callback for when a row is double clicked
 * @prop {Function} onHighlight  callback for when a row is highlighted / unhighlighted
 * @prop {Object[]} plugins  Collection of plugins to run to compute cell style,
 *    cell class name, column summaries
 * @prop {Object} rowData  The data to render in this row
 * @prop {Number} rowNumber  The row number in the table (bottom-${i} for bottom data)
 * @prop {Object[]} tableData  The table data
 * @prop {Function} CellComponent  Allow configuration of what component to use to render cells
 * @extends React.Component
 */
class TacoTableRow extends React.PureComponent {
  /**
   * @param {Object} props React props
   */
  constructor(props) {
    super(props);

    this.handleMouseEnter = this.handleMouseEnter.bind(this);
    this.handleMouseLeave = this.handleMouseLeave.bind(this);
    this.handleClick = this.handleClick.bind(this);
    this.handleDoubleClick = this.handleDoubleClick.bind(this);
  }

  /**
   * Handler for when the mouse enters the `<tr>`. Calls `onHighlight(rowData)`.
   * @private
   */
  handleMouseEnter() {
    const { onHighlight, rowData } = this.props;
    onHighlight(rowData);
  }

  /**
   * Handler for when the mouse enters the `<tr>`. Calls `onHighlight(null)`.
   * @private
   */
  handleMouseLeave() {
    const { onHighlight } = this.props;
    onHighlight(null);
  }

  /**
   * Handler for when a row is clicked. Calls `onClick(rowData, rowNumber, isBottomData)`.
   * @private
   */
  handleClick() {
    const { onClick, rowData, rowNumber, isBottomData } = this.props;
    onClick(rowData, rowNumber, isBottomData);
  }

  /**
   * Handler for when a row is double clicked. Calls `onDoubleClick(rowData, rowNumber, isBottomData)`.
   * @private
   */
  handleDoubleClick() {
    const { onDoubleClick, rowData, rowNumber, isBottomData } = this.props;
    onDoubleClick(rowData, rowNumber, isBottomData);
  }

  /**
   * Main render method
   * @return {React.Component}
   */
  render() {
    const { className, columnSummaries, columns, rowData, rowNumber, tableData, CellComponent,
      plugins, onHighlight, onClick, onColumnHighlight, onDoubleClick, highlighted,
      highlightedColumnId, columnGroups, isBottomData } = this.props;

    // attach mouse listeners for highlighting
    let onMouseEnter;
    let onMouseLeave;
    if (onHighlight) {
      onMouseEnter = this.handleMouseEnter;
      onMouseLeave = this.handleMouseLeave;
    }

    // attach click handler
    let onClickHandler;
    if (onClick) {
      onClickHandler = this.handleClick;
    }

    // attach double click handler
    let onDoubleClickHandler;
    if (onDoubleClick) {
      onDoubleClickHandler = this.handleDoubleClick;
    }

    return (
      <tr
        className={classNames(className, { 'row-highlight': highlighted })}
        onMouseEnter={onMouseEnter}
        onMouseLeave={onMouseLeave}
        onClick={onClickHandler}
        onDoubleClick={onDoubleClickHandler}
      >
        {columns.map((column, i) => {
          // find the associated column group if configured
          let columnGroup;
          if (columnGroups) {
            columnGroup = columnGroups.find(group =>
              group.columns.includes(column.id));
          }

          return (
            <CellComponent
              key={i}
              column={column}
              columnGroup={columnGroup}
              columnSummary={columnSummaries[i]}
              columns={columns}
              plugins={plugins}
              rowNumber={rowNumber}
              rowData={rowData}
              tableData={tableData}
              onHighlight={onColumnHighlight}
              highlightedColumn={column.id === highlightedColumnId}
              highlightedRow={highlighted}
              isBottomData={isBottomData}
            />
          );
        })}
      </tr>
    );
  }
}

TacoTableRow.propTypes = propTypes;
TacoTableRow.defaultProps = defaultProps;

export default TacoTableRow;