terra-table
Version:
The Terra Table component provides user a way to display data in an accessible table format.
303 lines (266 loc) • 8.29 kB
JSX
/* eslint-disable jsx-a11y/control-has-associated-label */
import React, { useCallback, useContext } from 'react';
import classNames from 'classnames/bind';
import PropTypes from 'prop-types';
import SectionHeader from 'terra-section-header';
import ThemeContext from 'terra-theme-context';
import GridContext, { GridConstants } from '../utils/GridContext';
import { SUBSECTION_HEADER_LEVEL } from '../utils/constants';
import Row from './Row';
import rowShape from '../proptypes/rowShape';
import subsectionShape from '../proptypes/subsectionShape';
import columnShape from '../proptypes/columnShape';
import styles from './Section.module.scss';
const cx = classNames.bind(styles);
const propTypes = {
/**
* An identifier for the section. This identifier should be unique across the set of sections provided to the DataGrid.
*/
id: PropTypes.string.isRequired,
/**
* Unique identifier for the parent table
*/
tableId: PropTypes.string.isRequired,
/**
* The section's row index position in the table.
*/
sectionRowIndex: PropTypes.number,
/**
* A boolean indicating whether or not the the section is collapsible. If true, the DataGrid's `onRequestSectionCollapse`
* function will be called upon selection of the section header, and an icon indicating collapsibility will be rendered within the seaction header.
*/
isCollapsible: PropTypes.bool,
/**
* A boolean indicating whether or not the section is collapsed. If true, the DataGrid will not render the contents of the section.
*/
isCollapsed: PropTypes.bool,
/**
* A Boolean indicating whether or not the section header is visible.
*/
isHidden: PropTypes.bool,
/**
* A text string to render within the section header.
*/
text: PropTypes.string,
/**
* An array of row objects to be rendered within the section.
*/
rows: PropTypes.arrayOf(rowShape),
/**
* An array of subsection objects to be rendered within the section.
*/
subsections: PropTypes.arrayOf(subsectionShape),
/**
* Enables row selection capabilities for the table.
* Use 'single' for single row selection and 'multiple' for multi-row selection.
*/
rowSelectionMode: PropTypes.string,
/**
* All columns currently displayed.
*/
displayedColumns: PropTypes.arrayOf(columnShape),
/**
* Callback function that will be called when a cell in the row is selected.
* @param {string} rowId rowId
* @param {string} columnId columnId
* @param {number} columnSpanIndex columnSpanIndex
* @param {object} event event
*/
onCellSelect: PropTypes.func,
/**
* String that specifies the height for the rows in the table. Any valid CSS value is accepted.
*/
rowHeight: PropTypes.string,
/**
* String that specifies the minimum height for the rows on the table. rowHeight takes precedence if valid CSS value is passed.
* With this property the height of the cell will grow to fit the cell content.
*/
rowMinimumHeight: PropTypes.string,
/**
* A zero-based index indicating which column represents the row header.
* Index can be set to -1 if row headers are not required.
*/
rowHeaderIndex: PropTypes.number,
/**
* Boolean specifying whether zebra striping is enabled.
*/
isTableStriped: PropTypes.bool,
/**
* Function that is called when a collapsible section is selected. Parameters: `onSectionSelect(sectionId)`
*/
onSectionSelect: PropTypes.func,
/**
* Width to assign for the section header in order to enable sticky behavior.
* This should be a dynamic size based on the current screen width and the state of the table container's client width.
*/
boundingWidth: PropTypes.number,
/**
* @private
* Id of the first row in table
*/
firstRowId: PropTypes.string,
/**
* @private
* Id of the last row in table
*/
lastRowId: PropTypes.string,
};
const defaultProps = {
isCollapsible: false,
isCollapsed: false,
isHidden: false,
rows: [],
};
function Section(props) {
const {
id,
tableId,
sectionRowIndex,
isCollapsible,
isCollapsed,
isHidden,
isTableStriped,
text,
rowSelectionMode,
displayedColumns,
onCellSelect,
rowHeight,
rowHeaderIndex,
rows,
subsections,
onSectionSelect,
rowMinimumHeight,
boundingWidth,
firstRowId,
lastRowId,
} = props;
const theme = useContext(ThemeContext);
const gridContext = useContext(GridContext);
const isGridContext = gridContext.role === GridConstants.GRID;
const hasSectionButton = isCollapsible && onSectionSelect;
const handleClick = useCallback(() => {
onSectionSelect(id);
}, [id, onSectionSelect]);
const sectionHeader = (
<tbody>
{/* Render section rows */}
{!isHidden && (
<tr
aria-rowindex={sectionRowIndex}
className={cx('header-row', theme.className)}
data-section-id={id}
>
<th
id={`${tableId}-${id}`}
className={cx('header-cell')}
align="left"
colSpan={displayedColumns.length}
role="columnheader"
scope="col"
tabIndex={isGridContext && !hasSectionButton ? -1 : undefined}
>
<SectionHeader
className={cx('section-header')}
text={text}
isOpen={hasSectionButton ? !isCollapsed : undefined}
onClick={hasSectionButton ? handleClick : undefined}
isTitleSticky
boundedWidth={boundingWidth}
/>
</th>
</tr>
)}
</tbody>
);
const rowProps = {
displayedColumns,
firstRowId,
height: rowHeight,
isTableStriped,
lastRowId,
onCellSelect,
rowHeaderIndex,
rowMinimumHeight,
rowSelectionMode,
sectionId: !isHidden ? id : undefined,
tableId,
};
if (subsections) {
return (
<>
{sectionHeader}
{!isCollapsed && subsections.map((subsection) => (
<>
<tbody>
<tr
aria-rowindex={subsection.subSectionRowIndex}
className={cx('header-row', theme.className)}
data-subsection-id={subsection.id}
data-subsection-section-id={id}
>
<th
id={`${tableId}-${id}-${subsection.id}`}
className={cx('header-cell')}
align="left"
colSpan={displayedColumns.length}
role="columnheader"
scope="col"
tabIndex={isGridContext ? -1 : undefined}
>
<SectionHeader
className={cx('subsection-header')}
text={subsection.text}
isTitleSticky
boundedWidth={boundingWidth}
level={SUBSECTION_HEADER_LEVEL}
/>
</th>
</tr>
</tbody>
<tbody>
{/* Render subsection rows */}
{!isCollapsed && subsection.rows.map((row, rowIndex) => (
<Row
rowIndex={subsection.subSectionRowIndex + (rowIndex + 1)}
key={row.id}
id={row.id}
cells={row.cells}
ariaLabel={row.ariaLabel}
isSelected={row.isSelected}
subsectionId={!isHidden ? subsection.id : undefined}
{...rowProps}
/>
))}
</tbody>
</>
))}
</>
);
}
return (
<>
{sectionHeader}
<tbody className={cx('section', {
collapsed: isCollapsed,
collapsible: isCollapsible,
}, theme.className)}
>
{/* Render section rows */}
{!isCollapsed && rows.map((row, rowIndex) => (
<Row
rowIndex={sectionRowIndex + (rowIndex + 1)}
key={row.id}
id={row.id}
cells={row.cells}
ariaLabel={row.ariaLabel}
isSelected={row.isSelected}
{...rowProps}
/>
))}
</tbody>
</>
);
}
Section.propTypes = propTypes;
Section.defaultProps = defaultProps;
export default React.memo(Section);