diff --git a/packages/terra-framework-docs/CHANGELOG.md b/packages/terra-framework-docs/CHANGELOG.md index 3b5f5ebc386..e9e53c1073b 100644 --- a/packages/terra-framework-docs/CHANGELOG.md +++ b/packages/terra-framework-docs/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * Added + * Added documentation for the new `terra-table` component. * Added test to cover enabling zebra striping for the `terra-table` component. * Added test for tab focus for scrollable tables for the `terra-table` component. diff --git a/packages/terra-framework-docs/src/terra-dev-site/doc/table/Examples/TableWithoutHeaders.doc.mdx b/packages/terra-framework-docs/src/terra-dev-site/doc/table/Examples/TableWithoutHeaders.doc.mdx new file mode 100644 index 00000000000..9669ccccc69 --- /dev/null +++ b/packages/terra-framework-docs/src/terra-dev-site/doc/table/Examples/TableWithoutHeaders.doc.mdx @@ -0,0 +1,3 @@ +import TableWithoutHeaders from './TableWithoutHeaders?dev-site-example'; + + diff --git a/packages/terra-framework-docs/src/terra-dev-site/doc/table/Examples/TableWithoutHeaders.jsx b/packages/terra-framework-docs/src/terra-dev-site/doc/table/Examples/TableWithoutHeaders.jsx new file mode 100644 index 00000000000..c97c04fcec8 --- /dev/null +++ b/packages/terra-framework-docs/src/terra-dev-site/doc/table/Examples/TableWithoutHeaders.jsx @@ -0,0 +1,64 @@ +import React from 'react'; +import Table from 'terra-table'; + +const tableDataJSON = { + cols: [ + { id: 'Column-0', displayName: 'Patient' }, + { id: 'Column-1', displayName: 'Location' }, + { id: 'Column-2', displayName: 'Illness Severity' }, + { id: 'Column-3', displayName: 'Visit' }, + { id: 'Column-4', displayName: 'Allergy' }, + { id: 'Column-5', displayName: 'Primary Contact' }, + ], + rows: [ + { + id: '1', + cells: [ + { content: 'Fleck, Arthur' }, + { content: '1007-MTN' }, + { content: 'Unstable' }, + { content: 'Inpatient, 2 months' }, + { content: '' }, + { content: 'Quinzell, Harleen' }, + ], + }, + { + id: '2', + isSelected: true, + cells: [ + { content: 'Wayne, Bruce' }, + { content: '1007-MTN-DR' }, + { content: 'Stable' }, + { content: 'Outpatient, 2 days' }, + { content: 'Phytochemicals' }, + { content: 'Grayson, Richard' }, + ], + }, + { + id: '3', + cells: [ + { content: 'Parker, Peter' }, + { content: '1018-MTN-DR' }, + { content: 'Severe' }, + { content: 'Inpatient, 2 days' }, + { content: 'Aspirin' }, + { content: 'Grayson, Richard' }, + ], + }, + ], +}; + +const TableWithoutHeaders = () => { + const { cols, rows } = tableDataJSON; + + return ( + + ); +}; + +export default TableWithoutHeaders; diff --git a/packages/terra-framework-docs/src/terra-dev-site/test/table/TableWithoutHeaders.test.jsx b/packages/terra-framework-docs/src/terra-dev-site/test/table/TableWithoutHeaders.test.jsx new file mode 100644 index 00000000000..20fcaa98ba0 --- /dev/null +++ b/packages/terra-framework-docs/src/terra-dev-site/test/table/TableWithoutHeaders.test.jsx @@ -0,0 +1,84 @@ +import React from 'react'; +import Table from 'terra-table'; + +const tableDataJSON = { + cols: [ + { id: 'Column-0', displayName: 'Patient' }, + { id: 'Column-1', displayName: 'Location' }, + { id: 'Column-2', displayName: 'Illness Severity' }, + { id: 'Column-3', displayName: 'Visit' }, + { id: 'Column-4', displayName: 'Allergy' }, + { id: 'Column-5', displayName: 'Primary Contact' }, + { id: 'Column-6', displayName: 'Generic Order Counts' }, + { id: 'Column-7', displayName: 'Patient Age' }, + { id: 'Column-8', displayName: 'Medication History' }, + { id: 'Column-9', displayName: 'My Relationship' }, + { id: 'Column-10', displayName: 'Not Selectable', isSelectable: false }, + ], + rows: [ + { + id: '1', + cells: [ + { content: 'Fleck, Arthur' }, + { content: '1007-MTN' }, + { content: 'Unstable' }, + { content: 'Inpatient, 2 months' }, + { content: '' }, + { content: 'Quinzell, Harleen' }, + { content: '' }, + { isMasked: true }, + { isMasked: true }, + { content: 'Admitting Physician' }, + { content: '', isSelectable: false }, + ], + }, + { + id: '2', + isSelected: true, + cells: [ + { content: 'Wayne, Bruce' }, + { content: '1007-MTN-DR' }, + { content: 'Stable' }, + { content: 'Outpatient, 2 days' }, + { content: 'Phytochemicals' }, + { content: 'Grayson, Richard' }, + { content: '' }, + { content: '' }, + { isMasked: true }, + { content: 'Admitting Physician' }, + { content: '', isSelectable: false }, + ], + }, + { + id: '3', + cells: [ + { content: 'Parker, Peter' }, + { content: '1018-MTN-DR' }, + { content: 'Severe' }, + { content: 'Inpatient, 2 days' }, + { content: 'Aspirin' }, + { content: 'Grayson, Richard' }, + { content: '' }, + { content: '' }, + { isMasked: true }, + { content: 'Primary Care Physician' }, + { content: '', isSelectable: false }, + ], + }, + ], +}; + +const TableWithoutHeaders = () => { + const { cols, rows } = tableDataJSON; + + return ( +
+ ); +}; + +export default TableWithoutHeaders; diff --git a/packages/terra-table/CHANGELOG.md b/packages/terra-table/CHANGELOG.md index a06b7b3cd43..756a9b763ff 100644 --- a/packages/terra-table/CHANGELOG.md +++ b/packages/terra-table/CHANGELOG.md @@ -7,6 +7,7 @@ * Changed * Updated the table component so that the cell dive-in logic would not execute when not in the grid context. + * Updated the table component to allow consumers to control the visibility of column headers in the table. * Modified the table component so that it can receive focus when scrollable. ## 5.0.0-alpha.0 - (October 17, 2023) diff --git a/packages/terra-table/src/Table.jsx b/packages/terra-table/src/Table.jsx index 282d5f50462..512c4c7646f 100644 --- a/packages/terra-table/src/Table.jsx +++ b/packages/terra-table/src/Table.jsx @@ -103,6 +103,12 @@ const propTypes = { hasSelectableRows: PropTypes.bool, /** + * Boolean indicating whether or not the table columns should be displayed. Setting the value to false will hide the columns, + * but the voice reader will use the column header values for a11y. + */ + hasColumnHeaders: PropTypes.bool, + + /* * Boolean specifying whether or not the table should have zebra striping for rows. */ isStriped: PropTypes.bool, @@ -116,6 +122,7 @@ const defaultProps = { pinnedColumns: [], overflowColumns: [], rows: [], + hasColumnHeaders: true, }; const defaultColumnMinimumWidth = 60; @@ -136,6 +143,7 @@ function Table(props) { onColumnSelect, onCellSelect, hasSelectableRows, + hasColumnHeaders, isStriped, rowHeaderIndex, } = props; @@ -301,14 +309,22 @@ function Table(props) { role={gridContext.role} aria-labelledby={ariaLabelledBy} aria-label={ariaLabel} - className={cx('table', theme.className)} + className={cx('table', theme.className, { headerless: !hasColumnHeaders })} {...(activeIndex != null && { onMouseUp, onMouseMove, onMouseLeave: onMouseUp })} > + + {tableColumns.map((column) => ( + // eslint-disable-next-line react/forbid-dom-props + + ))} + + { @@ -39,11 +51,15 @@ const ColumnHeader = (props) => { tableHeight, onColumnSelect, onResizeMouseDown, + hasColumnHeaders, } = props; return ( - + {columns.map((column, columnIndex) => ( { minimumWidth={column.minimumWidth} maximumWidth={column.maximumWidth} headerHeight={headerHeight} - isResizable={column.isResizable} - isSelectable={column.isSelectable} + isResizable={hasColumnHeaders && column.isResizable} + isSelectable={hasColumnHeaders && column.isSelectable} tableHeight={tableHeight} hasError={column.hasError} sortIndicator={column.sortIndicator} @@ -70,4 +86,5 @@ const ColumnHeader = (props) => { }; ColumnHeader.propTypes = propTypes; +ColumnHeader.defaultProps = defaultProps; export default React.memo(ColumnHeader); diff --git a/packages/terra-table/src/subcomponents/ColumnHeader.module.scss b/packages/terra-table/src/subcomponents/ColumnHeader.module.scss new file mode 100644 index 00000000000..9da1303dd52 --- /dev/null +++ b/packages/terra-table/src/subcomponents/ColumnHeader.module.scss @@ -0,0 +1,13 @@ +:local { + .hidden { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + white-space: nowrap; /* Ensures words are not read one at a time on screen readers*/ + width: 1px; + } +} diff --git a/packages/terra-table/src/subcomponents/ColumnHeaderCell.jsx b/packages/terra-table/src/subcomponents/ColumnHeaderCell.jsx index 5c7d57409f0..28b34a1c539 100644 --- a/packages/terra-table/src/subcomponents/ColumnHeaderCell.jsx +++ b/packages/terra-table/src/subcomponents/ColumnHeaderCell.jsx @@ -202,7 +202,7 @@ const ColumnHeaderCell = (props) => { onMouseDown={isSelectable && onColumnSelect ? handleMouseDown : undefined} onKeyDown={isSelectable && onColumnSelect ? handleKeyDown : undefined} // eslint-disable-next-line react/forbid-dom-props - style={{ width: `${width}px`, height: headerHeight, left: cellLeftEdge }} + style={{ height: headerHeight, left: cellLeftEdge }} >
{errorIcon} diff --git a/packages/terra-table/tests/jest/ColumnHeader.test.jsx b/packages/terra-table/tests/jest/ColumnHeader.test.jsx index 9d507e4d551..157cffba5c4 100644 --- a/packages/terra-table/tests/jest/ColumnHeader.test.jsx +++ b/packages/terra-table/tests/jest/ColumnHeader.test.jsx @@ -128,4 +128,52 @@ describe('ColumnHeader', () => { expect(wrapper).toMatchSnapshot(); }); + + it('verifies that the hasColumnHeaders prop hides the table column headers when set to false', () => { + const columns = [{ + id: 'Column-0', + displayName: ' Vitals', + }, { + id: 'Column-1', + displayName: ' Patient', + }]; + + const wrapper = shallow( + , + ); + + // Verify that column headers are not present + const columnHeader = wrapper.find('.hidden'); + expect(columnHeader).toHaveLength(1); + + expect(wrapper).toMatchSnapshot(); + }); + + it('verifies that the column headers are not hidden when the hasColumnHeader is true', () => { + const columns = [{ + id: 'Column-0', + displayName: ' Vitals', + }, { + id: 'Column-1', + displayName: ' Patient', + }]; + + const wrapper = shallow( + , + ); + + // Verify that column headers are present + const columnHeader = wrapper.find('.hidden'); + expect(columnHeader).toHaveLength(0); + + expect(wrapper).toMatchSnapshot(); + }); }); diff --git a/packages/terra-table/tests/jest/ColumnHeaderCell.test.jsx b/packages/terra-table/tests/jest/ColumnHeaderCell.test.jsx index a122c8b92a9..1ce344338b8 100644 --- a/packages/terra-table/tests/jest/ColumnHeaderCell.test.jsx +++ b/packages/terra-table/tests/jest/ColumnHeaderCell.test.jsx @@ -34,7 +34,6 @@ describe('ColumnHeaderCell', () => { expect(columnHeader.props().role).toBe('columnheader'); expect(columnHeader.props().scope).toBe('col'); expect(columnHeader.props().tabIndex).toEqual(undefined); - expect(columnHeader.props().style.width).toBe('100px'); expect(columnHeader.props().style.height).toBe('150px'); const headerContainer = columnHeader.find('.header-container[role="button"]'); @@ -69,7 +68,6 @@ describe('ColumnHeaderCell', () => { expect(columnHeader.props().scope).toBe('col'); expect(columnHeader.props().tabIndex).toEqual(undefined); expect(columnHeader.props()['aria-sort']).toBe('ascending'); - expect(columnHeader.props().style.width).toBe('100px'); expect(columnHeader.props().style.height).toBe('150px'); const headerContainer = columnHeader.find('.header-container[role="button"]'); @@ -104,7 +102,6 @@ describe('ColumnHeaderCell', () => { expect(columnHeader.props().scope).toBe('col'); expect(columnHeader.props().tabIndex).toEqual(undefined); expect(columnHeader.props()['aria-sort']).toBe('descending'); - expect(columnHeader.props().style.width).toBe('100px'); expect(columnHeader.props().style.height).toBe('150px'); const headerContainer = columnHeader.find('.header-container[role="button"]'); @@ -139,7 +136,6 @@ describe('ColumnHeaderCell', () => { expect(columnHeader.props().scope).toBe('col'); expect(columnHeader.props().tabIndex).toEqual(undefined); expect(columnHeader.props()['aria-sort']).toBeUndefined(); - expect(columnHeader.props().style.width).toBe('100px'); expect(columnHeader.props().style.height).toBe('150px'); const headerContainer = columnHeader.find('.header-container[role="button"]'); @@ -175,7 +171,6 @@ describe('ColumnHeaderCell', () => { expect(columnHeader.props().scope).toBe('col'); expect(columnHeader.props().tabIndex).toEqual(undefined); expect(columnHeader.props()['aria-sort']).toBe('ascending'); - expect(columnHeader.props().style.width).toBe('100px'); expect(columnHeader.props().style.height).toBe('150px'); const headerContainer = columnHeader.find('.header-container[role="button"]'); @@ -217,7 +212,6 @@ describe('ColumnHeaderCell', () => { expect(columnHeader.props().tabIndex).toEqual(0); expect(columnHeader.props()['aria-sort']).toBe('ascending'); expect(columnHeader.props().onMouseDown).toBeDefined(); - expect(columnHeader.props().style.width).toBe('100px'); expect(columnHeader.props().style.height).toBe('150px'); const headerContainer = columnHeader.find('.header-container[role="button"]'); @@ -259,7 +253,6 @@ describe('ColumnHeaderCell', () => { expect(columnHeader.props().tabIndex).toEqual(undefined); expect(columnHeader.props()['aria-sort']).toBe('ascending'); expect(columnHeader.props().onMouseDown).toBeUndefined(); - expect(columnHeader.props().style.width).toBe('100px'); expect(columnHeader.props().style.height).toBe('150px'); const headerContainer = columnHeader.find('.header-container[role="button"]'); diff --git a/packages/terra-table/tests/jest/Table.test.jsx b/packages/terra-table/tests/jest/Table.test.jsx index f0896ff21e2..25d5d452cb0 100644 --- a/packages/terra-table/tests/jest/Table.test.jsx +++ b/packages/terra-table/tests/jest/Table.test.jsx @@ -221,6 +221,25 @@ describe('Table', () => { expect(wrapper).toMatchSnapshot(); }); + + it('verifies that the column widths are set properly in the colgroup', () => { + const wrapper = shallowWithIntl( + +
+ , + ).dive().dive(); + + // Verify that column headers are not present + const column = wrapper.find('col').get(0); + expect(column.props.style.width).toBe('150px'); + + expect(wrapper).toMatchSnapshot(); + }); }); describe('with pinned columns', () => { diff --git a/packages/terra-table/tests/jest/__snapshots__/ColumnHeader.test.jsx.snap b/packages/terra-table/tests/jest/__snapshots__/ColumnHeader.test.jsx.snap index 7d1cfdc4884..25e501c8bcd 100644 --- a/packages/terra-table/tests/jest/__snapshots__/ColumnHeader.test.jsx.snap +++ b/packages/terra-table/tests/jest/__snapshots__/ColumnHeader.test.jsx.snap @@ -113,3 +113,58 @@ exports[`ColumnHeader renders a default column header 1`] = ` `; + +exports[`ColumnHeader verifies that the column headers are not hidden when the hasColumnHeader is true 1`] = ` + + + + + + +`; + +exports[`ColumnHeader verifies that the hasColumnHeaders prop hides the table column headers when set to false 1`] = ` + + + + + + +`; diff --git a/packages/terra-table/tests/jest/__snapshots__/ColumnHeaderCell.test.jsx.snap b/packages/terra-table/tests/jest/__snapshots__/ColumnHeaderCell.test.jsx.snap index a17e69a6750..c757854ca32 100644 --- a/packages/terra-table/tests/jest/__snapshots__/ColumnHeaderCell.test.jsx.snap +++ b/packages/terra-table/tests/jest/__snapshots__/ColumnHeaderCell.test.jsx.snap @@ -11,7 +11,6 @@ exports[`ColumnHeaderCell renders a column header cell with ascending sort 1`] = Object { "height": "150px", "left": null, - "width": "100px", } } > @@ -42,7 +41,6 @@ exports[`ColumnHeaderCell renders a column header cell with ascending sort and e Object { "height": "150px", "left": null, - "width": "100px", } } > @@ -79,7 +77,6 @@ exports[`ColumnHeaderCell renders a column header cell with descending sort 1`] Object { "height": "150px", "left": null, - "width": "100px", } } > @@ -109,7 +106,6 @@ exports[`ColumnHeaderCell renders a column header cell with error 1`] = ` Object { "height": "150px", "left": null, - "width": "100px", } } > @@ -143,7 +139,6 @@ exports[`ColumnHeaderCell renders a column header cell with onColumnSelect callb Object { "height": "150px", "left": null, - "width": "100px", } } tabIndex={0} @@ -181,7 +176,6 @@ exports[`ColumnHeaderCell renders a column header cell with onColumnSelect callb Object { "height": "150px", "left": null, - "width": "100px", } } > @@ -217,7 +211,6 @@ exports[`ColumnHeaderCell renders a default column header cell 1`] = ` Object { "height": "150px", "left": null, - "width": "100px", } } > @@ -290,7 +283,6 @@ exports[`ColumnHeaderCell renders a pinned column header cell 1`] = ` Object { "height": "150px", "left": 0, - "width": "100px", } } > diff --git a/packages/terra-table/tests/jest/__snapshots__/Table.test.jsx.snap b/packages/terra-table/tests/jest/__snapshots__/Table.test.jsx.snap index 325dbb56f9b..f1942cd1e2c 100644 --- a/packages/terra-table/tests/jest/__snapshots__/Table.test.jsx.snap +++ b/packages/terra-table/tests/jest/__snapshots__/Table.test.jsx.snap @@ -116,6 +116,7 @@ Array [
- + + + + + - + - + + + + + - + - + + + + + + @@ -4250,7 +4334,6 @@ exports[`Table verifies row selection column header selection 1`] = ` Object { "height": "2.5rem", "left": 240, - "width": "200px", } } > @@ -4341,7 +4424,6 @@ exports[`Table verifies row selection column header selection 1`] = ` Object { "height": "2.5rem", "left": null, - "width": "200px", } } > @@ -4358,7 +4440,7 @@ exports[`Table verifies row selection column header selection 1`] = ` - + `; +exports[`Table verifies that the column widths are set properly in the colgroup 1`] = ` +
+
+ + + + + + + + + + + + + + +
+ +`; + exports[`Table verifies that the table created is consistent with the rows and overflowColumns props 1`] = `
+ + + + + + + + + + + + + + + { }); }); + describe('Table Without Header', () => { + const tableWithoutHeadersSelector = '#table-without-headers'; + + beforeEach(() => { + browser.url('/raw/tests/cerner-terra-framework-docs/table/table-without-headers'); + }); + + it('Renders a table without column headers', () => { + Terra.validates.element('table-without-column-headers', { selector: tableWithoutHeadersSelector }); + }); + }); + describe('Zebra Striped Table', () => { const zebraStripeTableSelector = '#zebra-striped-table';