diff --git a/docs/Column.md b/docs/Column.md index 6c9682eb6..938d1f1f8 100644 --- a/docs/Column.md +++ b/docs/Column.md @@ -16,6 +16,7 @@ Describes the header and cell contents of a table column. | flexShrink | Number | | Flex shrink style; defaults to 1 | | headerClassName | String | | CSS class to apply to this column's header | | headerRenderer | Function | | Optional callback responsible for rendering a column's header column. [Learn more](#headerrenderer) | +| id | String | | Optional id to set on the column header; used for [`aria-describedby`](https://www.w3.org/TR/wai-aria/states_and_properties#aria-describedby) | | label | String | | Header label for this column | | maxWidth | Number | | Maximum width of column; this property will only be used if :flexGrow is greater than 0 | | minWidth | Number | | Minimum width of column | diff --git a/source/Table/Column.js b/source/Table/Column.js index dc2d3003f..6fc3e52bb 100644 --- a/source/Table/Column.js +++ b/source/Table/Column.js @@ -52,6 +52,9 @@ export default class Column extends Component { */ headerRenderer: PropTypes.func.isRequired, + /** Optional id to set on the column header */ + id: PropTypes.string, + /** Header label for this column */ label: PropTypes.string, diff --git a/source/Table/Table.jest.js b/source/Table/Table.jest.js index 70f16f02f..2b92c390b 100644 --- a/source/Table/Table.jest.js +++ b/source/Table/Table.jest.js @@ -41,6 +41,7 @@ describe('Table', () => { cellDataGetter, cellRenderer, columnData = { data: 123 }, + columnID, columnStyle, disableSort = false, headerRenderer, @@ -70,6 +71,7 @@ describe('Table', () => { headerRenderer={headerRenderer} disableSort={disableSort} style={columnStyle} + id={columnID} /> { expect(row.getAttribute('role')).toEqual('row') }) + it('should set aria role on a cell', () => { + const rendered = findDOMNode(render(getMarkup())) + const cell = rendered.querySelector('.ReactVirtualized__Table__rowColumn') + expect(cell.getAttribute('role')).toEqual('gridcell') + }) + + it('should set aria-describedby on a cell when the column has an id', () => { + const columnID = 'column-header-test' + const rendered = findDOMNode(render(getMarkup({ + columnID + }))) + const cell = rendered.querySelector('.ReactVirtualized__Table__rowColumn') + expect(cell.getAttribute('aria-describedby')).toEqual(columnID) + }) + it('should attach a11y properties to a row if :onRowClick is specified', () => { const rendered = findDOMNode(render(getMarkup({ onRowClick: () => {} @@ -903,6 +920,39 @@ describe('Table', () => { expect(row.tabIndex).toEqual(-1) }) + it('should set aria role on a header column', () => { + const rendered = findDOMNode(render(getMarkup())) + const header = rendered.querySelector('.ReactVirtualized__Table__headerColumn') + expect(header.getAttribute('role')).toEqual('columnheader') + }) + + it('should set aria-sort ascending on a header column if the column is sorted ascending', () => { + const rendered = findDOMNode(render(getMarkup({ + sortBy: 'name', + sortDirection: SortDirection.ASC + }))) + const header = rendered.querySelector('.ReactVirtualized__Table__headerColumn') + expect(header.getAttribute('aria-sort')).toEqual('ascending') + }) + + it('should set aria-sort descending on a header column if the column is sorted descending', () => { + const rendered = findDOMNode(render(getMarkup({ + sortBy: 'name', + sortDirection: SortDirection.DESC + }))) + const header = rendered.querySelector('.ReactVirtualized__Table__headerColumn') + expect(header.getAttribute('aria-sort')).toEqual('descending') + }) + + it('should set id on a header column when the column has an id', () => { + const columnID = 'column-header-test' + const rendered = findDOMNode(render(getMarkup({ + columnID + }))) + const header = rendered.querySelector('.ReactVirtualized__Table__headerColumn') + expect(header.getAttribute('id')).toEqual(columnID) + }) + it('should attach a11y properties to a header column if sort is enabled', () => { const rendered = findDOMNode(render(getMarkup({ disableSort: false, @@ -910,7 +960,6 @@ describe('Table', () => { }))) const row = rendered.querySelector('.ReactVirtualized__Table__headerColumn') expect(row.getAttribute('aria-label')).toEqual('Name') - expect(row.getAttribute('role')).toEqual('rowheader') expect(row.tabIndex).toEqual(0) }) @@ -920,7 +969,6 @@ describe('Table', () => { }))) const row = rendered.querySelector('.ReactVirtualized__Table__headerColumn') expect(row.getAttribute('aria-label')).toEqual(null) - expect(row.getAttribute('role')).toEqual(null) expect(row.tabIndex).toEqual(-1) }) }) diff --git a/source/Table/Table.js b/source/Table/Table.js index b08d38fd7..a7c1b2056 100644 --- a/source/Table/Table.js +++ b/source/Table/Table.js @@ -384,7 +384,8 @@ export default class Table extends PureComponent { cellRenderer, className, columnData, - dataKey + dataKey, + id } = column.props const cellData = cellDataGetter({ columnData, dataKey, rowData }) @@ -396,10 +397,18 @@ export default class Table extends PureComponent { ? renderedCell : null + const a11yProps = {} + + if (id) { + a11yProps['aria-describedby'] = id + } + return (
@@ -410,7 +419,7 @@ export default class Table extends PureComponent { _createHeader ({ column, index }) { const { headerClassName, headerStyle, onHeaderClick, sort, sortBy, sortDirection } = this.props - const { dataKey, disableSort, headerRenderer, label, columnData } = column.props + const { dataKey, disableSort, headerRenderer, id, label, columnData } = column.props const sortEnabled = !disableSort && sort const classNames = cn( @@ -455,17 +464,27 @@ export default class Table extends PureComponent { } a11yProps['aria-label'] = column.props['aria-label'] || label || dataKey - a11yProps.role = 'rowheader' a11yProps.tabIndex = 0 a11yProps.onClick = onClick a11yProps.onKeyDown = onKeyDown } + if (sortBy === dataKey) { + a11yProps['aria-sort'] = sortDirection === SortDirection.ASC + ? 'ascending' + : 'descending' + } + + if (id) { + a11yProps.id = id + } + return (
{renderedHeader}