diff --git a/docs/README.md b/docs/README.md index b5bd0bd..56f1fa3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,6 +17,7 @@ * [hover](#hover) * [condensed](#condensed) * [id](#id) +* [tabIndexCell](#tabIndexCell) * [classes](#classes) * [wrapperClasses](#wrapperClasses) * [headerClasses](#headerClasses) @@ -112,6 +113,10 @@ Same as bootstrap `.table-condensed` class for making a table more compact by cu ### id - [String] Customize id on `table` element. + +### tabIndexCell - [Bool] +Enable the `tabIndex` attribute on `` element. + ### classes - [String] Customize class on `table` element. diff --git a/packages/react-bootstrap-table2-example/examples/basic/tabindex-column.js b/packages/react-bootstrap-table2-example/examples/basic/tabindex-column.js new file mode 100644 index 0000000..144cdce --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/basic/tabindex-column.js @@ -0,0 +1,54 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 3dbcede..519542e 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -14,6 +14,7 @@ import CustomizedIdClassesTable from 'examples/basic/customized-id-classes'; import CaptionTable from 'examples/basic/caption-table'; import LargeTable from 'examples/basic/large-table'; import ExposedAPITable from 'examples/basic/exposed-function'; +import TabIndexCellTable from 'examples/basic/tabindex-column'; // bootstrap 4 import Bootstrap4DefaultSortTable from 'examples/bootstrap4/sort'; @@ -195,7 +196,8 @@ storiesOf('Basic Table', module) .add('Customized id and class table', () => ) .add('Table with caption', () => ) .add('Large Table', () => ) - .add('Exposed API', () => ); + .add('Exposed API', () => ) + .add('Enable tabIndex on Cell', () => ); storiesOf('Bootstrap 4', module) .addDecorator(bootstrapStyle(BOOTSTRAP_VERSION.FOUR)) diff --git a/packages/react-bootstrap-table2/src/body.js b/packages/react-bootstrap-table2/src/body.js index 7bcd373..a32a82e 100644 --- a/packages/react-bootstrap-table2/src/body.js +++ b/packages/react-bootstrap-table2/src/body.js @@ -5,7 +5,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import _ from './utils'; -import Row from './row/simple-row'; +import SimpleRow from './row/simple-row'; import RowAggregator from './row/aggregate-row'; import RowSection from './row/row-section'; import Const from './const'; @@ -24,6 +24,7 @@ class Body extends React.Component { const { columns, data, + tabIndexCell, keyField, isEmpty, noDataIndication, @@ -45,7 +46,7 @@ class Body extends React.Component { } content = ; } else { - let RowComponent = Row; + let RowComponent = SimpleRow; const selectRowEnabled = selectRow.mode !== Const.ROW_SELECT_DISABLED; const expandRowEnabled = !!expandRow.renderer; @@ -73,11 +74,13 @@ class Body extends React.Component { const baseRowProps = { key, row, + tabIndexCell, columns, keyField, cellEdit, value: key, rowIndex: index, + visibleColumnSize, attrs: rowEvents || {}, ...additionalRowProps }; diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index 1a1d16d..053cac1 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -43,6 +43,7 @@ class BootstrapTable extends PropsBaseResolver(Component) { data, columns, keyField, + tabIndexCell, id, classes, striped, @@ -89,6 +90,7 @@ class BootstrapTable extends PropsBaseResolver(Component) { + { expandColumnRenderer ? expandColumnRenderer({ expanded diff --git a/packages/react-bootstrap-table2/src/row-selection/selection-cell.js b/packages/react-bootstrap-table2/src/row-selection/selection-cell.js index 8d46da1..3d6f023 100644 --- a/packages/react-bootstrap-table2/src/row-selection/selection-cell.js +++ b/packages/react-bootstrap-table2/src/row-selection/selection-cell.js @@ -15,6 +15,7 @@ export default class SelectionCell extends Component { onRowSelect: PropTypes.func, disabled: PropTypes.bool, rowIndex: PropTypes.number, + tabIndex: PropTypes.number, clickToSelect: PropTypes.bool, selectionRenderer: PropTypes.func } @@ -29,7 +30,8 @@ export default class SelectionCell extends Component { this.props.rowIndex !== nextProps.rowIndex || this.props.selected !== nextProps.selected || this.props.disabled !== nextProps.disabled || - this.props.rowKey !== nextProps.rowKey; + this.props.rowKey !== nextProps.rowKey || + this.props.tabIndex !== nextProps.tabIndex; return shouldUpdate; } @@ -60,14 +62,18 @@ export default class SelectionCell extends Component { mode: inputType, selected, disabled, + tabIndex, selectionRenderer } = this.props; + const attrs = {}; + if (tabIndex !== -1) attrs.tabIndex = tabIndex; + return ( { ({ bootstrap4 }) => ( - + { selectionRenderer ? selectionRenderer({ mode: inputType, diff --git a/packages/react-bootstrap-table2/src/row/aggregate-row.js b/packages/react-bootstrap-table2/src/row/aggregate-row.js index 1b3df9d..32c997a 100644 --- a/packages/react-bootstrap-table2/src/row/aggregate-row.js +++ b/packages/react-bootstrap-table2/src/row/aggregate-row.js @@ -1,4 +1,5 @@ /* eslint react/prop-types: 0 */ +/* eslint no-plusplus: 0 */ import React from 'react'; import PropTypes from 'prop-types'; import _ from '../utils'; @@ -55,6 +56,8 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co expanded, selected, selectable, + visibleColumnSize, + tabIndexCell, ...rest } = this.props; const key = _.get(row, keyField); @@ -66,6 +69,8 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co newAttrs.onClick = this.createClickEventHandler(newAttrs.onClick); } + let tabIndexStart = (rowIndex * visibleColumnSize) + 1; + return ( ) : null } @@ -91,6 +97,7 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co rowIndex={ rowIndex } selected={ selected } disabled={ !selectable } + tabIndex={ tabIndexCell ? tabIndexStart++ : -1 } /> ) : null @@ -101,6 +108,7 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co keyField={ keyField } rowIndex={ rowIndex } shouldUpdate={ this.shouldUpdateRowContent } + tabIndexStart={ tabIndexCell ? tabIndexStart : -1 } { ...rest } /> diff --git a/packages/react-bootstrap-table2/src/row/row-pure-content.js b/packages/react-bootstrap-table2/src/row/row-pure-content.js index cd1cbfd..5b498ff 100644 --- a/packages/react-bootstrap-table2/src/row/row-pure-content.js +++ b/packages/react-bootstrap-table2/src/row/row-pure-content.js @@ -1,5 +1,6 @@ /* eslint react/prop-types: 0 */ /* eslint react/no-array-index-key: 0 */ +/* eslint no-plusplus: 0 */ import React from 'react'; import _ from '../utils'; @@ -25,9 +26,12 @@ export default class RowPureContent extends React.Component { onStart, clickToEdit, dbclickToEdit, - EditingCellComponent + EditingCellComponent, + tabIndexStart } = this.props; + let tabIndex = tabIndexStart; + return columns.map((column, index) => { if (!column.hidden) { const { dataField } = column; @@ -87,6 +91,10 @@ export default class RowPureContent extends React.Component { editableCell = column.editable(content, row, rowIndex, index); } + if (tabIndexStart !== -1) { + cellAttrs.tabIndex = tabIndex++; + } + return ( - + ); } } -Row.propTypes = { +SimpleRow.propTypes = { row: PropTypes.object.isRequired, rowIndex: PropTypes.number.isRequired, columns: PropTypes.array.isRequired, @@ -47,11 +54,11 @@ Row.propTypes = { attrs: PropTypes.object }; -Row.defaultProps = { +SimpleRow.defaultProps = { editable: true, style: {}, className: null, attrs: {} }; -export default Row; +export default SimpleRow; diff --git a/packages/react-bootstrap-table2/test/cell.test.js b/packages/react-bootstrap-table2/test/cell.test.js index e2e42ab..1187c13 100644 --- a/packages/react-bootstrap-table2/test/cell.test.js +++ b/packages/react-bootstrap-table2/test/cell.test.js @@ -198,6 +198,26 @@ describe('Cell', () => { }); }); + describe('when props.tabIndex is change', () => { + const column = { dataField: 'name', text: 'Product Name' }; + beforeEach(() => { + props = { + row, + columnIndex: 1, + rowIndex: 1, + tabIndex: 5, + column + }; + wrapper = shallow( + ); + }); + + it('should return true', () => { + nextProps = { ...props, tabIndex: 2 }; + expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true); + }); + }); + describe('if column.isDummyField is true', () => { describe('when content is change', () => { const column = { dataField: '', text: 'Product Name', isDummyField: true }; diff --git a/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js b/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js index 7de9a40..fb6f798 100644 --- a/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js +++ b/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js @@ -57,6 +57,27 @@ describe('', () => { }); }); + describe('when tabIndex prop has been changed', () => { + beforeEach(() => { + props = { + selected: false, + mode, + rowIndex, + disabled: false, + tabIndex: 0, + rowKey: 1 + }; + wrapper = shallow( + + ); + }); + + it('should return true', () => { + nextProps = { ...props, tabIndex: 2 }; + expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true); + }); + }); + describe('when disabled prop has been changed', () => { beforeEach(() => { props = { diff --git a/packages/react-bootstrap-table2/test/row/row-pure-content.test.js b/packages/react-bootstrap-table2/test/row/row-pure-content.test.js index 70982c5..3000f93 100644 --- a/packages/react-bootstrap-table2/test/row/row-pure-content.test.js +++ b/packages/react-bootstrap-table2/test/row/row-pure-content.test.js @@ -104,6 +104,47 @@ describe('RowPureContent', () => { }); }); + describe('when tabIndexStart prop is -1', () => { + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should not render tabIndex prop on Cell', () => { + wrapper.find(Cell).forEach((cell) => { + expect(cell.prop('tabIndex')).toBeUndefined(); + }); + }); + }); + + describe('when tabIndexStart prop is not -1', () => { + const tabIndexStart = 4; + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render correct tabIndex prop on Cell', () => { + wrapper.find(Cell).forEach((cell, i) => { + expect(cell.prop('tabIndex')).toEqual(tabIndexStart + i); + }); + }); + }); + describe('when editingRowIdx and editingColIdx prop is defined', () => { const editingRowIdx = rowIndex; const editingColIdx = 1; diff --git a/packages/react-bootstrap-table2/test/row/simple-row.test.js b/packages/react-bootstrap-table2/test/row/simple-row.test.js index 744f3cb..3d0f6c4 100644 --- a/packages/react-bootstrap-table2/test/row/simple-row.test.js +++ b/packages/react-bootstrap-table2/test/row/simple-row.test.js @@ -57,6 +57,49 @@ describe('SimpleRow', () => { expect(wrapper.length).toBe(1); expect(wrapper.find(RowPureContent)).toHaveLength(1); }); + + describe('when tabIndexCell prop is enable', () => { + const visibleColumnSize = 3; + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render correct tabIndexStart', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find(RowPureContent)).toHaveLength(1); + expect(wrapper.find(RowPureContent).prop('tabIndexStart')).toBe((rowIndex * visibleColumnSize) + 1); + }); + }); + + describe('when tabIndexCell prop is disable', () => { + const visibleColumnSize = 3; + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should always render tabIndexStart as -1', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find(RowPureContent)).toHaveLength(1); + expect(wrapper.find(RowPureContent).prop('tabIndexStart')).toBe(-1); + }); + }); }); describe('shouldComponentUpdate', () => {