Source

components/TableHeading/TableHeading.tsx

import styleSheet from './TableHeading.styleSheet';
import globalStyleSheet from '../../utils/style/globalStyle.styleSheet';
import { inlineSvgIcons } from '../../utils/style/inlineSvgIcons';
import { useContext } from 'react';
import { store } from '../../store/store';
import { cx } from '../../utils/style/emotion';
import { HeadingsElementInterface } from '../../utils/types/types';

/**
 * @namespace TableHeading
 */

/**
 * The TableHeading component, display the column headers cells, each one containing a button to activate sort function.
 * There is a sorting icon to inform the user on wich column and which direction is currently used for sorting.
 * Use aria attributes to inform screen reader user as well on current sort.
 * set a minimum column width to avoid the column width variation depending on currently displayed results (if not, column widths change on each page change or filter update).
 * @memberof TableHeading
 * @function
 * @return {React.ReactElement} jsx to be injected in the html.
 */
export const TableHeading = (): React.ReactElement => {
  const {
    headings,
    currentSort,
    displayedColumns,
    columnsMinWidth,
    style,
    dispatch,
  } = useContext(store);

  /**
   * Dispatch the heading being selected by user to set a new sort.
   * @param {object} item - the heading element of the headings array corresponding to the header column being clicked.
   * @memberof TableHeading
   * @function
   */
  const handleClick = (item: HeadingsElementInterface): void => {
    dispatch({
      type: 'setCurrentSort',
      payload: { key: item.key, type: item.type },
    });
  };

  /**
   * An object containing the classnames generated from the stylesheet made with emotion and using the style state of the store
   * @memberof TableHeading
   */
  const classNames: { [Class: string]: string } = styleSheet(style);

  return (
    <thead className={classNames.tableHead}>
      <tr>
        {headings.map((item, index) => (
          <th
            key={item.key}
            className={cx(
              classNames.cell,
              index >= displayedColumns && globalStyleSheet.srOnly
            )}
            style={
              index >= displayedColumns
                ? undefined
                : { minWidth: columnsMinWidth[index] + 'px' }
            }
          >
            <button
              className={classNames.button}
              type="button"
              onClick={() => handleClick(item)}
              aria-label={
                currentSort.key === item.key
                  ? `${item.label}, currently selected as ${
                      currentSort.direction > 0 ? 'ascending' : 'descending'
                    } sort`
                  : `${item.label}, click to sort with this key`
              }
            >
              {item.label}
              <img
                className={classNames.sortIcon}
                src={
                  currentSort.key !== item.key
                    ? inlineSvgIcons.noSort
                    : currentSort.direction > 0
                    ? inlineSvgIcons.ascendingSort
                    : inlineSvgIcons.descendingSort
                }
                aria-hidden
                alt={
                  currentSort.key !== item.key
                    ? 'no sort'
                    : currentSort.direction > 0
                    ? 'ascending sort'
                    : 'descending sort'
                }
              />
            </button>
          </th>
        ))}
      </tr>
    </thead>
  );
};