diff --git a/docs/columns.md b/docs/columns.md index 9fe7745a3..52aaa1749 100644 --- a/docs/columns.md +++ b/docs/columns.md @@ -25,6 +25,8 @@ Available properties in a column object: * [headerEvents](#headerEvents) * [headerAlign](#headerAlign) * [headerAttrs](#headerAttrs) +* [headerSortingClasses](#headerSortingClasses) +* [headerSortingStyle](#headerSortingStyle) * [editable](#editable) * [validator](#validator) * [editCellStyle](#editCellStyle) @@ -419,6 +421,35 @@ A new `Object` will be the result of element headerAttrs. > Same as [column.attrs](#attrs), it has lower priority and will be > overwrited when other props related to HTML attributes were given. +### headerSortingClasses - [String | Function] + +`headerSortingClasses` allows to customize `class` for header cell when this column is sorting. + +```js +const headerSortingClasses = 'demo-sorting'; +``` + +Furthermore, it also accepts a callback which takes **4** arguments and `String` is expected to return: + +```js +const headerSortingClasses = (column, sortOrder, isLastSorting, colIndex) => { ... } +``` + +* `column`: The value of current column. +* `sortOrder`: The order of current sorting +* `isLastSorting`: Is the last one of sorted columns. +* `colIndex`: The index of the current column being processed in BootstrapTable. + +### headerSortingStyle - [Object | Function] + +It's similiar to [headerSortingClasses](#headerSortingClasses). It allows to customize the style of header cell when this column is sorting. A style `Object` and `callback` are acceptable. `callback` takes **4** arguments and an `Object` is expected to return: + +```js +const sortingHeaderStyle = { + backgroundColor: 'red' +}; +``` + ## column.editable - [Bool | Function] `column.editable` default is true, means every column is editable if you configure [`cellEdit`](./README.md#cellEdit). But you can disable some columns editable via setting `false`. diff --git a/packages/react-bootstrap-table2-example/examples/sort/default-sort-table.js b/packages/react-bootstrap-table2-example/examples/sort/default-sort-table.js index 0f238a849..9cda0a41e 100644 --- a/packages/react-bootstrap-table2-example/examples/sort/default-sort-table.js +++ b/packages/react-bootstrap-table2-example/examples/sort/default-sort-table.js @@ -41,7 +41,17 @@ const columns = [{ sort: true }]; - +const defaultSorted = [{ + dataField: 'name', + order: 'desc' +}]; + + `; diff --git a/packages/react-bootstrap-table2-example/examples/sort/header-sorting-classes.js b/packages/react-bootstrap-table2-example/examples/sort/header-sorting-classes.js new file mode 100644 index 000000000..d70bdd752 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/sort/header-sorting-classes.js @@ -0,0 +1,61 @@ +/* eslint + no-unused-vars: 0 + arrow-body-style: 0 +*/ + +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table2'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const headerSortingClasses = (column, sortOrder, isLastSorting, colIndex) => ( + sortOrder === 'asc' ? 'demo-sorting-asc' : 'demo-sorting-desc' +); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + sort: true, + headerSortingClasses +}, { + dataField: 'name', + text: 'Product Name', + sort: true, + headerSortingClasses +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +const headerSortingClasses = (column, sortOrder, isLastSorting, colIndex) => ( + sortOrder === 'asc' ? 'demo-sorting-asc' : 'demo-sorting-desc' +); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + sort: true, + headerSortingClasses +}, { + dataField: 'name', + text: 'Product Name', + sort: true, + headerSortingClasses +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/sort/header-sorting-style.js b/packages/react-bootstrap-table2-example/examples/sort/header-sorting-style.js new file mode 100644 index 000000000..c39096542 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/sort/header-sorting-style.js @@ -0,0 +1,54 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table2'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const headerSortingStyle = { backgroundColor: '#c8e6c9' }; + +const columns = [{ + dataField: 'id', + text: 'Product ID', + sort: true, + headerSortingStyle +}, { + dataField: 'name', + text: 'Product Name', + sort: true, + headerSortingStyle +}, { + dataField: 'price', + text: 'Product Price' +}]; + + +const sourceCode = `\ +const headerSortingStyle = { backgroundColor: '#c8e6c9' }; + +const columns = [{ + dataField: 'id', + text: 'Product ID', + sort: true, + headerSortingStyle +}, { + dataField: 'name', + text: 'Product Name', + sort: true, + headerSortingStyle +}, { + 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 a99978238..738cfc63a 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -41,6 +41,8 @@ import RowEventTable from 'examples/rows/row-event'; import EnableSortTable from 'examples/sort/enable-sort-table'; import DefaultSortTable from 'examples/sort/default-sort-table'; import CustomSortTable from 'examples/sort/custom-sort-table'; +import HeaderSortingClassesTable from 'examples/sort/header-sorting-classes'; +import HeaderSortingStyleTable from 'examples/sort/header-sorting-style'; // cell editing import ClickToEditTable from 'examples/cell-edit/click-to-edit-table'; @@ -127,7 +129,9 @@ storiesOf('Work on Rows', module) storiesOf('Sort Table', module) .add('Enable Sort', () => ) .add('Default Sort Table', () => ) - .add('Custom Sort Fuction', () => ); + .add('Custom Sort Fuction', () => ) + .add('Custom Classes on Sorting Header Column', () => ) + .add('Custom Style on Sorting Header Column', () => ); storiesOf('Cell Editing', module) .add('Click to Edit', () => ) diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/base/_base.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/base/_base.scss index 5097d01d6..5ee5a54bb 100644 --- a/packages/react-bootstrap-table2-example/stories/stylesheet/base/_base.scss +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/base/_base.scss @@ -10,4 +10,5 @@ $grey-900: #212121; $pink-500: #E91E63; $green-lighten-2: #81c784; $green-lighten-4: #c8e6c9; +$light-blue: #00BFFF; $markdown-color: #f6f8fa; diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/sort/_index.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/sort/_index.scss new file mode 100644 index 000000000..bb8b1e889 --- /dev/null +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/sort/_index.scss @@ -0,0 +1,8 @@ +.demo-sorting, +.demo-sorting-asc { + background-color: $green-lighten-2; +} + +.demo-sorting-desc { + background-color: $light-blue; +} diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss index 07510ceee..cf0a024e4 100644 --- a/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss @@ -8,4 +8,5 @@ @import "cell-edit/index"; @import "row-selection/index"; @import "rows/index"; -@import "loading-overlay/index"; \ No newline at end of file +@import "sort/index"; +@import "loading-overlay/index"; diff --git a/packages/react-bootstrap-table2/src/header-cell.js b/packages/react-bootstrap-table2/src/header-cell.js index 15f7b529e..fb57adeb0 100644 --- a/packages/react-bootstrap-table2/src/header-cell.js +++ b/packages/react-bootstrap-table2/src/header-cell.js @@ -15,8 +15,10 @@ const HeaderCell = (props) => { index, onSort, sorting, - sortOrder + sortOrder, + isLastSorting } = props; + const { text, sort, @@ -27,7 +29,9 @@ const HeaderCell = (props) => { headerEvents, headerClasses, headerStyle, - headerAttrs + headerAttrs, + headerSortingClasses, + headerSortingStyle } = column; const cellAttrs = { @@ -35,10 +39,10 @@ const HeaderCell = (props) => { ...headerEvents }; const children = headerFormatter ? headerFormatter(column, index) : text; - const cellClasses = _.isFunction(headerClasses) ? headerClasses(column, index) : headerClasses; let cellStyle = {}; let sortSymbol; + let cellClasses = _.isFunction(headerClasses) ? headerClasses(column, index) : headerClasses; if (headerStyle) { cellStyle = _.isFunction(headerStyle) ? headerStyle(column, index) : headerStyle; @@ -56,10 +60,6 @@ const HeaderCell = (props) => { cellStyle.display = 'none'; } - if (cellClasses) cellAttrs.className = cellClasses; - - if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle; - if (sort) { const customClick = cellAttrs.onClick; cellAttrs.onClick = (e) => { @@ -70,11 +70,30 @@ const HeaderCell = (props) => { if (sorting) { sortSymbol = ; + + // append customized classes or style if table was sorting based on the current column. + cellClasses = cs( + cellClasses, + _.isFunction(headerSortingClasses) + ? headerSortingClasses(column, sortOrder, isLastSorting, index) + : headerSortingClasses + ); + + cellStyle = { + ...cellStyle, + ..._.isFunction(headerSortingStyle) + ? headerSortingStyle(column, sortOrder, isLastSorting, index) + : headerSortingStyle + }; } else { sortSymbol = ; } } + if (cellClasses) cellAttrs.className = cs(cellAttrs.className, cellClasses); + + if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle; + return ( { children }{ sortSymbol } @@ -112,7 +131,8 @@ HeaderCell.propTypes = { index: PropTypes.number.isRequired, onSort: PropTypes.func, sorting: PropTypes.bool, - sortOrder: PropTypes.oneOf([Const.SORT_ASC, Const.SORT_DESC]) + sortOrder: PropTypes.oneOf([Const.SORT_ASC, Const.SORT_DESC]), + isLastSorting: PropTypes.bool }; export default HeaderCell; diff --git a/packages/react-bootstrap-table2/src/header.js b/packages/react-bootstrap-table2/src/header.js index e301eb032..da77cb9c7 100644 --- a/packages/react-bootstrap-table2/src/header.js +++ b/packages/react-bootstrap-table2/src/header.js @@ -27,6 +27,8 @@ const Header = (props) => { { columns.map((column, i) => { const currSort = column.dataField === sortField; + const isLastSorting = column.dataField === sortField; + return ( { onSort={ onSort } sorting={ currSort } sortOrder={ sortOrder } + isLastSorting={ isLastSorting } />); }) } diff --git a/packages/react-bootstrap-table2/test/header-cell.test.js b/packages/react-bootstrap-table2/test/header-cell.test.js index 4e2dda5ca..43ef4da01 100644 --- a/packages/react-bootstrap-table2/test/header-cell.test.js +++ b/packages/react-bootstrap-table2/test/header-cell.test.js @@ -436,6 +436,233 @@ describe('HeaderCell', () => { }); }); }); + + describe('when headerSortingClasses is defined ', () => { + const classes = 'foo'; + const order = Const.SORT_DESC; + + describe('if headerSortingClasses is a string', () => { + beforeEach(() => { + column = { ...column, headerSortingClasses: classes }; + wrapper = shallow( + ); + }); + + it('should append classes correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.hasClass(classes)).toBe(true); + }); + + it('should have sortable class on header cell', () => { + expect(wrapper.hasClass('sortable')).toBe(true); + }); + }); + + describe('if headerSortingClasses is a function', () => { + let classesCallBack; + let isLastSorting; + + beforeEach(() => { + classesCallBack = sinon.stub() + .withArgs(column, order, isLastSorting, index) + .returns(classes); + + column = { ...column, headerSortingClasses: classesCallBack }; + wrapper = shallow( + ); + }); + + it('should append classes correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.hasClass(classes)).toBe(true); + }); + + it('should have sortable class on header cell', () => { + expect(wrapper.hasClass('sortable')).toBe(true); + }); + + it('should call custom class function with correct params', () => { + expect(classesCallBack.callCount).toBe(1); + expect(classesCallBack.calledWith(column, order, isLastSorting, index)).toBe(true); + }); + + describe('when the field is last sorting', () => { + it('should call custom classes function with isLastSorting being true', () => { + isLastSorting = true; + classesCallBack.reset(); + + wrapper = shallow( + ); + + expect(classesCallBack.callCount).toBe(1); + expect(classesCallBack.calledWith(column, order, isLastSorting, index)).toBe(true); + }); + }); + }); + + describe('if column.headerClasses is defined as well', () => { + it('should keep both classes', () => { + column = { + ...column, + headerClasses: 'td-test-class', + headerSortingClasses: classes + }; + + wrapper = shallow( + + ); + + expect(wrapper.length).toBe(1); + expect(wrapper.hasClass('sortable')).toBe(true); + expect(wrapper.hasClass(classes)).toBe(true); + expect(wrapper.hasClass(column.headerClasses)).toBe(true); + }); + }); + }); + + describe('when headerSortingStyle is defined', () => { + const style = { backgroundColor: 'red' }; + const order = Const.SORT_DESC; + + describe('if headerSortingStyle is an object', () => { + beforeEach(() => { + column = { ...column, headerSortingStyle: style }; + + wrapper = shallow( + + ); + }); + + it('should append style correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find('th').prop('style')).toEqual(style); + }); + }); + + describe('if headerSortingStyle is a function', () => { + let styleCallBack; + let isLastSorting; + + beforeEach(() => { + styleCallBack = sinon.stub() + .withArgs(column, order, isLastSorting, index) + .returns(style); + + column = { ...column, headerSortingStyle: styleCallBack }; + + wrapper = shallow( + ); + }); + + it('should append style correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find('th').prop('style')).toEqual(style); + }); + + it('should call custom style function with correct params', () => { + expect(styleCallBack.callCount).toBe(1); + expect(styleCallBack.calledWith(column, order, isLastSorting, index)).toBe(true); + }); + + describe('when the field is last sorting', () => { + it('should call custom classes function with isLastSorting being true', () => { + isLastSorting = true; + styleCallBack.reset(); + + wrapper = shallow( + ); + + expect(styleCallBack.callCount).toBe(1); + expect(styleCallBack.calledWith(column, order, isLastSorting, index)).toBe(true); + }); + }); + }); + + describe('if column.headerStyle was defined as well', () => { + it('should keep both styles', () => { + column = { + ...column, + headerStyle: { opacity: '1' }, + headerSortingStyle: style + }; + + wrapper = shallow( + + ); + expect(wrapper.length).toBe(1); + expect(wrapper.find('th').prop('style')).toEqual(expect.objectContaining({ + ...style, + ...column.headerStyle + })); + }); + + it('headerSortingStyle should have higher priority', () => { + column = { + ...column, + headerStyle: { backgroundColor: 'green' }, + headerSortingStyle: style + }; + + wrapper = shallow( + + ); + expect(wrapper.length).toBe(1); + expect(wrapper.find('th').prop('style')).toEqual(expect.objectContaining({ + ...style + })); + expect(wrapper.find('th').prop('style')).not.toEqual(expect.objectContaining({ + ...column.headerStyle + })); + }); + }); + }); }); describe('when column.headerEvents prop is defined and have custom onClick', () => {