terra-clinical-data-grid
Version:
An organizational component that renders a collection of data in a grid-like format.
187 lines (169 loc) • 5.67 kB
JSX
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import classNamesBind from 'classnames/bind';
import ThemeContext from 'terra-theme-context';
import memoize from 'memoize-one';
import { KEY_RETURN, KEY_SPACE } from 'keycode-js';
import styles from './Cell.module.scss';
const cx = classNamesBind.bind(styles);
const propTypes = {
/**
* Accessible text provided for the Cell.
*/
ariaLabel: PropTypes.string,
/**
* String identifier of the section in which the Cell will be rendered.
*/
sectionId: PropTypes.string.isRequired,
/**
* String identifier of the row in which the Cell will be rendered.
*/
rowId: PropTypes.string.isRequired,
/**
* String identifier of the column in which the Cell will be rendered.
*/
columnId: PropTypes.string.isRequired,
/**
* String-formatted width that the Cell should be rendered as. Values are suggested to be in `rem`s (ex `'5rem'`), but any valid CSS height value is accepted.
*/
width: PropTypes.string.isRequired,
/**
* Boolean indicating whether the Cell is selectable.
*/
isSelectable: PropTypes.bool,
/**
* Boolean indicating whether the Cell is actively selected.
*/
isSelected: PropTypes.bool,
/**
* Function that will be called upon Cell selection. The `isSelectable` prop must be true for this function to be called.
* Parameters: `onSelect(sectionId, rowId, columnId)`
*/
onSelect: PropTypes.func,
/**
* Function that will be called upon the mouse entering the selectable region of the Cell. The `isSelectable` prop must be true for this function to be called.
* Parameters: `onHoverStart(event)`
*/
onHoverStart: PropTypes.func,
/**
* Function that will be called upon the mouse leaving the selectable region of the Cell. The `isSelectable` prop must be true for this function to be called.
* Parameters: `onHoverEnd(event)`
*/
onHoverEnd: PropTypes.func,
/**
* Content that will rendered within the Cell.
*/
children: PropTypes.node,
/**
* Function that will be called with a ref to the Cell's selectable element. Parameters: `selectableRefCallback(selectableRef)`
*/
selectableRefCallback: PropTypes.func,
/**
* Boolean indicating whether the cell should be highlighted with background styling.
*/
isColumnHighlighted: PropTypes.bool,
/**
* Boolean indicating whether the cell is in the first row, also used with column highlight background styling.
*/
isFirstRow: PropTypes.bool,
/**
* Boolean indicating whether the cell is in the last row, also used with column highlight background styling.
*/
isLastRow: PropTypes.bool,
};
class Cell extends React.Component {
constructor(props) {
super(props);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleTargetClick = this.handleTargetClick.bind(this);
this.getCellStyles = memoize(this.getCellStyles);
}
handleKeyDown(event) {
if (event.nativeEvent.keyCode === KEY_RETURN || event.nativeEvent.keyCode === KEY_SPACE) {
const { onSelect } = this.props;
if (onSelect) {
event.preventDefault();
onSelect(this.props.sectionId, this.props.rowId, this.props.columnId);
}
}
}
handleTargetClick() {
const { onSelect } = this.props;
if (onSelect) {
onSelect(this.props.sectionId, this.props.rowId, this.props.columnId);
}
}
/* eslint-disable class-methods-use-this */
/**
* This function is memoized in the constructor so that for a given width value, the same object reference will be returned.
* This allows for repeat renders of the Cell to occur more efficiently if the width value has not changed between renders.
*/
getCellStyles(width) {
return {
width,
};
}
/* eslint-enable class-methods-use-this */
render() {
const {
sectionId,
rowId,
columnId,
isSelectable,
isSelected,
width,
onSelect,
children,
selectableRefCallback,
onHoverStart,
onHoverEnd,
ariaLabel,
isColumnHighlighted,
isFirstRow,
isLastRow,
...customProps
} = this.props;
/* eslint-disable react/forbid-dom-props, jsx-a11y/no-static-element-interactions */
const theme = this.context;
const role = isSelectable ? 'button' : undefined;
const tabIndex = isSelectable ? '0' : undefined;
const highlightCellClassNames = !isSelected && isColumnHighlighted && cx([
{ highlighted: !isSelectable },
{ 'highlighted-selectable': isSelectable },
{ first: isFirstRow },
{ last: isLastRow },
]);
return (
<div
{...customProps}
className={classNames(cx('container', theme.className), customProps.className)}
style={this.getCellStyles(width)}
aria-selected={isSelected ? true : undefined}
>
<div
role={role}
className={cx([
'content',
highlightCellClassNames,
{ selectable: isSelectable },
{ selected: isSelected },
])}
onClick={isSelectable ? this.handleTargetClick : undefined}
onKeyDown={isSelectable ? this.handleKeyDown : undefined}
onMouseEnter={onHoverStart}
onMouseLeave={onHoverEnd}
tabIndex={tabIndex}
ref={selectableRefCallback}
aria-label={ariaLabel}
>
{children}
</div>
</div>
);
/* eslint-enable react/forbid-dom-props, jsx-a11y/no-static-element-interactions */
}
}
Cell.propTypes = propTypes;
Cell.contextType = ThemeContext;
export default Cell;