From 0550848cb175006400d54c25647a00780a0b2681 Mon Sep 17 00:00:00 2001 From: MaximKudriavtsev Date: Thu, 9 Nov 2017 14:14:15 +0300 Subject: [PATCH] feat(react-grid): allow to focus each column and change sorting (#448) --- packages/dx-react-demos/package.json | 3 +- packages/dx-react-demos/setup.js | 3 + .../featured-virtual-scrolling/demo.test.jsx | 3 - .../featured-remote-data/demo.test.jsx | 2 +- .../src/templates/table-header-cell.jsx | 24 ++- .../src/templates/table-header-cell.test.jsx | 62 ++++++++ .../table-header-cell/sorting-control.jsx | 22 ++- .../sorting-control.test.jsx | 18 +++ .../dx-react-grid-material-ui/package.json | 5 + packages/dx-react-grid-material-ui/setup.js | 3 + .../src/plugins/table-header-row.jsx | 13 +- .../src/templates/table-header-cell.jsx | 88 ++++------- .../src/templates/table-header-cell.test.jsx | 149 ++++++++++++------ .../table-header-cell/sorting-control.jsx | 72 +++++---- .../docs/reference/table-header-row.md | 18 ++- .../src/plugins/table-header-row.jsx | 15 +- .../src/plugins/table-header-row.test.jsx | 28 ++++ packages/dx-testing/index.js | 4 +- 18 files changed, 377 insertions(+), 155 deletions(-) create mode 100644 packages/dx-react-demos/setup.js create mode 100644 packages/dx-react-grid-bootstrap3/src/templates/table-header-cell/sorting-control.test.jsx create mode 100644 packages/dx-react-grid-material-ui/setup.js diff --git a/packages/dx-react-demos/package.json b/packages/dx-react-demos/package.json index 2cac1ae3d9..4f2283e2c7 100644 --- a/packages/dx-react-demos/package.json +++ b/packages/dx-react-demos/package.json @@ -24,7 +24,8 @@ "\\.css$": "/css-stub.js" }, "setupFiles": [ - "babel-polyfill" + "babel-polyfill", + "/setup.js" ] }, "dependencies": { diff --git a/packages/dx-react-demos/setup.js b/packages/dx-react-demos/setup.js new file mode 100644 index 0000000000..67b8fe533f --- /dev/null +++ b/packages/dx-react-demos/setup.js @@ -0,0 +1,3 @@ +if (typeof (window) !== 'undefined') { + window.requestAnimationFrame = () => {}; +} diff --git a/packages/dx-react-demos/src/bootstrap3/featured-virtual-scrolling/demo.test.jsx b/packages/dx-react-demos/src/bootstrap3/featured-virtual-scrolling/demo.test.jsx index b6e860d8e8..ad181f8770 100644 --- a/packages/dx-react-demos/src/bootstrap3/featured-virtual-scrolling/demo.test.jsx +++ b/packages/dx-react-demos/src/bootstrap3/featured-virtual-scrolling/demo.test.jsx @@ -4,17 +4,14 @@ import Demo from './demo'; describe('BS3 featured: virtual scrolling demo', () => { let getRect; - let originalRaf; beforeEach(() => { getRect = jest.spyOn(Element.prototype, 'getBoundingClientRect'); - originalRaf = window.requestAnimationFrame; window.requestAnimationFrame = jest.fn().mockImplementationOnce(callback => callback()); }); afterEach(() => { getRect.mockRestore(); - window.requestAnimationFrame = originalRaf; }); it('should work', () => { diff --git a/packages/dx-react-demos/src/material-ui/featured-remote-data/demo.test.jsx b/packages/dx-react-demos/src/material-ui/featured-remote-data/demo.test.jsx index 6c369aca8c..3cc50fc46a 100644 --- a/packages/dx-react-demos/src/material-ui/featured-remote-data/demo.test.jsx +++ b/packages/dx-react-demos/src/material-ui/featured-remote-data/demo.test.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { mount } from 'enzyme'; import Demo from './demo'; -describe('BS3 featured: remote data demo', () => { +describe('MUI featured: remote data demo', () => { beforeEach(() => { window.fetch = jest.fn(() => Promise.resolve()); }); diff --git a/packages/dx-react-grid-bootstrap3/src/templates/table-header-cell.jsx b/packages/dx-react-grid-bootstrap3/src/templates/table-header-cell.jsx index f68c80c1d2..49d2049cb2 100644 --- a/packages/dx-react-grid-bootstrap3/src/templates/table-header-cell.jsx +++ b/packages/dx-react-grid-bootstrap3/src/templates/table-header-cell.jsx @@ -7,6 +7,9 @@ import { ResizingControl } from './table-header-cell/resizing-control'; import { GroupingControl } from './table-header-cell/grouping-control'; import { SortingControl } from './table-header-cell/sorting-control'; +const ENTER_KEY_CODE = 13; +const SPACE_KEY_CODE = 32; + export class TableHeaderCell extends React.PureComponent { constructor(props) { super(props); @@ -14,15 +17,21 @@ export class TableHeaderCell extends React.PureComponent { this.state = { dragging: false, }; - - this.onCellClick = (e) => { + this.onClick = (e) => { const { allowSorting, changeSortingDirection } = this.props; - if (!allowSorting) return; - e.stopPropagation(); + const isActionKeyDown = e.keyCode === ENTER_KEY_CODE || e.keyCode === SPACE_KEY_CODE; + const isMouseClick = e.keyCode === undefined; + + if (!allowSorting || !(isActionKeyDown || isMouseClick)) return; + const cancelSortingRelatedKey = e.metaKey || e.ctrlKey; + const cancel = (isMouseClick && cancelSortingRelatedKey) + || (isActionKeyDown && cancelSortingRelatedKey); + + e.preventDefault(); changeSortingDirection({ keepOther: e.shiftKey || cancelSortingRelatedKey, - cancel: cancelSortingRelatedKey, + cancel, }); }; } @@ -49,9 +58,10 @@ export class TableHeaderCell extends React.PureComponent { } : {}), ...(allowSorting || allowDragging ? { cursor: 'pointer' } : null), ...(dragging || tableColumn.draft ? { opacity: 0.3 } : null), + padding: '5px', ...style, }} - onClick={this.onCellClick} + onClick={this.onClick} > {allowGroupingByClick && ( {allowSorting ? ( @@ -73,6 +84,7 @@ export class TableHeaderCell extends React.PureComponent { align={align} sortingDirection={sortingDirection} columnTitle={columnTitle} + onClick={this.onClick} /> ) : ( columnTitle diff --git a/packages/dx-react-grid-bootstrap3/src/templates/table-header-cell.test.jsx b/packages/dx-react-grid-bootstrap3/src/templates/table-header-cell.test.jsx index c729a22dd5..c3e9bb44a3 100644 --- a/packages/dx-react-grid-bootstrap3/src/templates/table-header-cell.test.jsx +++ b/packages/dx-react-grid-bootstrap3/src/templates/table-header-cell.test.jsx @@ -232,4 +232,66 @@ describe('TableHeaderCell', () => { textOverflow: 'ellipsis', }); }); + describe('with keyboard navigation', () => { + const ENTER_KEY_CODE = 13; + const SPACE_KEY_CODE = 32; + + it('should handle the "Enter" and "Space" keys down', () => { + const changeSortingDirection = jest.fn(); + const tree = mount(( + + )); + + const targetElement = tree.find('SortingControl'); + targetElement.simulate('keydown', { keyCode: ENTER_KEY_CODE }); + expect(changeSortingDirection) + .toHaveBeenCalled(); + + changeSortingDirection.mockClear(); + targetElement.simulate('keydown', { keyCode: SPACE_KEY_CODE }); + expect(changeSortingDirection) + .toHaveBeenCalled(); + + changeSortingDirection.mockClear(); + targetElement.simulate('keydown', { keyCode: 51 }); + expect(changeSortingDirection) + .not.toHaveBeenCalled(); + }); + + it('should keep other sorting parameters on sorting change when the "Shift" key is pressed', () => { + const changeSortingDirection = jest.fn(); + const tree = mount(( + + )); + + const targetElement = tree.find('SortingControl'); + targetElement.simulate('keydown', { keyCode: ENTER_KEY_CODE, shiftKey: true }); + expect(changeSortingDirection) + .toHaveBeenCalledWith({ keepOther: true, cancel: undefined }); + }); + + it('should handle the "Ctrl" key with sorting', () => { + const changeSortingDirection = jest.fn(); + const tree = mount(( + + )); + + const targetElement = tree.find('SortingControl'); + targetElement.simulate('keydown', { keyCode: ENTER_KEY_CODE, ctrlKey: true }); + expect(changeSortingDirection) + .toHaveBeenCalledWith({ keepOther: true, cancel: true }); + }); + }); }); diff --git a/packages/dx-react-grid-bootstrap3/src/templates/table-header-cell/sorting-control.jsx b/packages/dx-react-grid-bootstrap3/src/templates/table-header-cell/sorting-control.jsx index 990178bca3..bea390028c 100644 --- a/packages/dx-react-grid-bootstrap3/src/templates/table-header-cell/sorting-control.jsx +++ b/packages/dx-react-grid-bootstrap3/src/templates/table-header-cell/sorting-control.jsx @@ -3,10 +3,22 @@ import PropTypes from 'prop-types'; import { SortingIndicator } from '../parts/sorting-indicator'; -export const SortingControl = ({ align, sortingDirection, columnTitle }) => +const handleMouseDown = (e) => { e.currentTarget.style.outline = 'none'; }; +const handleBlur = (e) => { e.currentTarget.style.outline = ''; }; + +export const SortingControl = ({ + align, sortingDirection, columnTitle, onClick, +}) => (align === 'right' ? ( ) : ( {columnTitle}   @@ -32,6 +51,7 @@ SortingControl.propTypes = { align: PropTypes.string.isRequired, sortingDirection: PropTypes.oneOf(['asc', 'desc']), columnTitle: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired, }; SortingControl.defaultProps = { diff --git a/packages/dx-react-grid-bootstrap3/src/templates/table-header-cell/sorting-control.test.jsx b/packages/dx-react-grid-bootstrap3/src/templates/table-header-cell/sorting-control.test.jsx new file mode 100644 index 0000000000..87dee86c53 --- /dev/null +++ b/packages/dx-react-grid-bootstrap3/src/templates/table-header-cell/sorting-control.test.jsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { mount } from 'enzyme'; + +import { SortingControl } from './sorting-control'; + +describe('with keyboard navigation', () => { + it('can get focus', () => { + const tree = mount(( + + )); + + expect(tree.find('span').prop('tabIndex')) + .toBe(0); + }); +}); diff --git a/packages/dx-react-grid-material-ui/package.json b/packages/dx-react-grid-material-ui/package.json index 50f2d53436..0bd8122054 100644 --- a/packages/dx-react-grid-material-ui/package.json +++ b/packages/dx-react-grid-material-ui/package.json @@ -41,6 +41,11 @@ "lint": "eslint \"src/**\"", "lint:fix": "yarn run lint -- --fix" }, + "jest": { + "setupFiles": [ + "/setup.js" + ] + }, "devDependencies": { "@devexpress/dx-grid-core": "1.0.0-alpha.14", "@devexpress/dx-react-core": "1.0.0-alpha.14", diff --git a/packages/dx-react-grid-material-ui/setup.js b/packages/dx-react-grid-material-ui/setup.js new file mode 100644 index 0000000000..67b8fe533f --- /dev/null +++ b/packages/dx-react-grid-material-ui/setup.js @@ -0,0 +1,3 @@ +if (typeof (window) !== 'undefined') { + window.requestAnimationFrame = () => {}; +} diff --git a/packages/dx-react-grid-material-ui/src/plugins/table-header-row.jsx b/packages/dx-react-grid-material-ui/src/plugins/table-header-row.jsx index b5cc040d8f..031b2278c4 100644 --- a/packages/dx-react-grid-material-ui/src/plugins/table-header-row.jsx +++ b/packages/dx-react-grid-material-ui/src/plugins/table-header-row.jsx @@ -7,10 +7,16 @@ import { TableRow } from '../templates/table-row'; const defaultHeaderCellTemplate = props => ; const defaultHeaderRowTemplate = props => ; +const defaultMessages = { + sortingHint: 'Sort', +}; export class TableHeaderRow extends React.PureComponent { render() { - const { headerCellTemplate, headerRowTemplate, ...restProps } = this.props; + const { + headerCellTemplate, headerRowTemplate, + messages, ...restProps + } = this.props; return ( ); @@ -31,9 +38,13 @@ export class TableHeaderRow extends React.PureComponent { TableHeaderRow.propTypes = { headerCellTemplate: PropTypes.func, headerRowTemplate: PropTypes.func, + messages: PropTypes.shape({ + sortingHint: PropTypes.string, + }), }; TableHeaderRow.defaultProps = { headerCellTemplate: undefined, headerRowTemplate: undefined, + messages: {}, }; diff --git a/packages/dx-react-grid-material-ui/src/templates/table-header-cell.jsx b/packages/dx-react-grid-material-ui/src/templates/table-header-cell.jsx index 6bd5008342..93c92f8d4e 100644 --- a/packages/dx-react-grid-material-ui/src/templates/table-header-cell.jsx +++ b/packages/dx-react-grid-material-ui/src/templates/table-header-cell.jsx @@ -11,12 +11,18 @@ import { GroupingControl } from './table-header-cell/grouping-control'; import { ResizingControl } from './table-header-cell/resizing-control'; import { SortingControl } from './table-header-cell/sorting-control'; +const ENTER_KEY_CODE = 13; +const SPACE_KEY_CODE = 32; + const styles = theme => ({ plainTitle: { - textOverflow: 'ellipsis', overflow: 'hidden', + textOverflow: 'ellipsis', + height: theme.spacing.unit * 3, + lineHeight: `${theme.spacing.unit * 3}px`, }, cell: { + outline: 'none', position: 'relative', overflow: 'visible', paddingRight: theme.spacing.unit, @@ -33,9 +39,6 @@ const styles = theme => ({ cellDraggable: { cursor: 'pointer', }, - cellClickable: { - cursor: 'pointer', - }, cellDimmed: { opacity: 0.3, }, @@ -46,25 +49,6 @@ const styles = theme => ({ clearPadding: { padding: 0, }, - title: { - height: '24px', - lineHeight: '24px', - whiteSpace: 'nowrap', - overflow: 'hidden', - verticalAlign: 'middle', - }, - titleRight: { - textAlign: 'right', - }, - titleLeft: { - textAlign: 'left', - }, - titleRightOffset: { - marginLeft: (theme.spacing.unit * 2) - 2, - }, - titleLeftOffset: { - marginRight: (theme.spacing.unit * 2) - 2, - }, }); class TableHeaderCellBase extends React.PureComponent { @@ -75,14 +59,19 @@ class TableHeaderCellBase extends React.PureComponent { dragging: false, }; - this.onCellClick = (e) => { - const { allowSorting, changeSortingDirection } = this.props; - if (!allowSorting) return; - e.stopPropagation(); + this.onClick = (e) => { + const { changeSortingDirection } = this.props; + const isActionKeyDown = e.keyCode === ENTER_KEY_CODE || e.keyCode === SPACE_KEY_CODE; + const isMouseClick = e.keyCode === undefined; + const cancelSortingRelatedKey = e.metaKey || e.ctrlKey; + const cancel = (isMouseClick && cancelSortingRelatedKey) + || (isActionKeyDown && cancelSortingRelatedKey); + + e.preventDefault(); changeSortingDirection({ keepOther: e.shiftKey || cancelSortingRelatedKey, - cancel: cancelSortingRelatedKey, + cancel, }); }; } @@ -93,34 +82,25 @@ class TableHeaderCellBase extends React.PureComponent { allowGroupingByClick, groupByColumn, allowDragging, dragPayload, allowResizing, changeColumnWidth, changeDraftColumnWidth, - classes, + classes, getMessage, } = this.props; const { dragging } = this.state; const align = column.align || 'left'; const columnTitle = column.title || column.name; + const tooltipText = getMessage('sortingHint'); const tableCellClasses = classNames({ [classes.cell]: true, [classes.cellRight]: align === 'right', [classes.cellNoUserSelect]: allowDragging || allowSorting, [classes.cellDraggable]: allowDragging, - [classes.cellClickable]: allowSorting, [classes.cellDimmed]: dragging || tableColumn.draft, }); - - const titleClasses = classNames({ - [classes.title]: true, - [classes.titleRight]: align === 'right', - [classes.titleLeft]: align === 'left', - [classes.titleRightOffset]: align === 'right' && allowGroupingByClick, - [classes.titleLeftOffset]: align === 'left' && allowGroupingByClick, - }); - const cellLayout = ( {allowGroupingByClick && ( )} -
- {allowSorting ? ( - - ) : ( -
- {columnTitle} -
- )} -
+ {allowSorting ? ( + + ) : ( +
+ {columnTitle} +
+ )} {allowResizing && ( { let classes; beforeAll(() => { resetConsole = setupConsole({ ignore: ['validateDOMNesting', 'SheetsRegistry'] }); - classes = getClasses(); + classes = getClasses(); mount = createMount({ context: { table: {} }, childContextTypes: { table: () => null } }); }); afterAll(() => { @@ -35,6 +36,7 @@ describe('TableHeaderCell', () => { it('should use column name if title is not specified', () => { const tree = mount(( { }} changeSortingDirection={changeSortingDirection} allowSorting + getMessage={jest.fn()} /> )); - tree.find(TableHeaderCell).simulate('click', { ctrlKey: true }); + tree.find(TableSortLabel).simulate('click', { ctrlKey: true }); expect(changeSortingDirection.mock.calls).toHaveLength(1); expect(changeSortingDirection.mock.calls[0][0].cancel).toBeTruthy(); @@ -66,11 +69,11 @@ describe('TableHeaderCell', () => { const tree = mount(( )); expect(tree.find(TableCell).hasClass(classes.cellNoUserSelect)).toBeFalsy(); - expect(tree.find(TableCell).hasClass(classes.cellClickable)).toBeFalsy(); expect(tree.find(TableCell).hasClass(classes.cellDraggable)).toBeFalsy(); }); @@ -79,11 +82,11 @@ describe('TableHeaderCell', () => { )); expect(tree.find(TableCell).hasClass(classes.cellNoUserSelect)).toBeTruthy(); - expect(tree.find(TableCell).hasClass(classes.cellClickable)).toBeTruthy(); }); it('should have correct styles when dragging is allowed', () => { @@ -92,6 +95,7 @@ describe('TableHeaderCell', () => { )); @@ -106,6 +110,7 @@ describe('TableHeaderCell', () => { )); @@ -128,6 +133,7 @@ describe('TableHeaderCell', () => { allowResizing changeDraftColumnWidth={changeDraftColumnWidth} changeColumnWidth={changeColumnWidth} + getMessage={jest.fn()} /> )); @@ -139,59 +145,108 @@ describe('TableHeaderCell', () => { .toBe(changeColumnWidth); }); - it('should have correct offset when grouping by click is not allowed and column align is left', () => { + it('should pass correct text to SortingControl', () => { const tree = mount(( {}} /> )); - expect(tree.find(`.${classes.titleLeftOffset}`)).toHaveLength(0); - expect(tree.find(`.${classes.titleRightOffset}`)).toHaveLength(0); - expect(tree.find(`.${classes.titleLeft}`)).toHaveLength(1); - expect(tree.find(`.${classes.titleRight}`)).toHaveLength(0); + const tooltip = tree.find('Tooltip'); + expect(tooltip.exists()) + .toBeTruthy(); + expect(tooltip.prop('title')) + .toBe('Sort'); }); - it('should have correct offset when grouping by click is allowed column align is left', () => { - const tree = mount(( - - )); + describe('with keyboard navigation', () => { + const ENTER_KEY_CODE = 13; + const SPACE_KEY_CODE = 32; - expect(tree.find(`.${classes.titleLeftOffset}`)).toHaveLength(1); - expect(tree.find(`.${classes.titleRightOffset}`)).toHaveLength(0); - expect(tree.find(`.${classes.titleLeft}`)).toHaveLength(1); - expect(tree.find(`.${classes.titleRight}`)).toHaveLength(0); - }); + it('can not get focus if sorting is not allow', () => { + const tree = mount(( + + )); - it('should have correct offset when grouping by click is not allowed and column align is right', () => { - const tree = mount(( - - )); + expect(tree.find(SortingControl).exists()) + .not.toBeTruthy(); + }); - expect(tree.find(`.${classes.titleLeftOffset}`)).toHaveLength(0); - expect(tree.find(`.${classes.titleRightOffset}`)).toHaveLength(0); - expect(tree.find(`.${classes.titleLeft}`)).toHaveLength(0); - expect(tree.find(`.${classes.titleRight}`)).toHaveLength(1); - }); + it('can get focus if sorting is allow', () => { + const tree = mount(( + + )); - it('should have correct offset when grouping by click is allowed column align is right', () => { - const tree = mount(( - - )); + expect(tree.find(SortingControl).exists()) + .toBeTruthy(); + }); + + it('should handle the "Enter" and "Space" keys down', () => { + const changeSortingDirection = jest.fn(); + const tree = mount(( + + )); + const SortLabel = tree.find(TableSortLabel); + + SortLabel.simulate('keydown', { keyCode: ENTER_KEY_CODE }); + expect(changeSortingDirection) + .toHaveBeenCalled(); + + changeSortingDirection.mockClear(); + SortLabel.simulate('keydown', { keyCode: SPACE_KEY_CODE }); + expect(changeSortingDirection) + .toHaveBeenCalled(); + + changeSortingDirection.mockClear(); + SortLabel.simulate('keydown', { keyCode: 51 }); + expect(changeSortingDirection) + .not.toHaveBeenCalled(); + }); + + it('should keep other sorting parameters on sorting change when the "Shift" key is pressed', () => { + const changeSortingDirection = jest.fn(); + const tree = mount(( + + )); + + tree.find(TableSortLabel).simulate('keydown', { keyCode: ENTER_KEY_CODE, shiftKey: true }); + expect(changeSortingDirection) + .toHaveBeenCalledWith({ keepOther: true, cancel: undefined }); + }); + + it('should handle the "Ctrl" key with sorting', () => { + const changeSortingDirection = jest.fn(); + const tree = mount(( + + )); - expect(tree.find(`.${classes.titleLeftOffset}`)).toHaveLength(0); - expect(tree.find(`.${classes.titleRightOffset}`)).toHaveLength(1); - expect(tree.find(`.${classes.titleLeft}`)).toHaveLength(0); - expect(tree.find(`.${classes.titleRight}`)).toHaveLength(1); + tree.find(TableSortLabel).simulate('keydown', { keyCode: ENTER_KEY_CODE, ctrlKey: true }); + expect(changeSortingDirection) + .toHaveBeenCalledWith({ keepOther: true, cancel: true }); + }); }); }); diff --git a/packages/dx-react-grid-material-ui/src/templates/table-header-cell/sorting-control.jsx b/packages/dx-react-grid-material-ui/src/templates/table-header-cell/sorting-control.jsx index 55120cd4c2..acf79a773e 100644 --- a/packages/dx-react-grid-material-ui/src/templates/table-header-cell/sorting-control.jsx +++ b/packages/dx-react-grid-material-ui/src/templates/table-header-cell/sorting-control.jsx @@ -1,57 +1,61 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { TableSortLabel } from 'material-ui'; + +import { TableSortLabel, Tooltip } from 'material-ui'; import { withStyles } from 'material-ui/styles'; const styles = theme => ({ - sortingControl: { - cursor: 'pointer', - display: 'inline-block', - paddingTop: theme.spacing.unit / 2, - }, - sortingTitle: { - lineHeight: '18px', - display: 'inline-block', - verticalAlign: 'top', - textOverflow: 'ellipsis', + tooltipRoot: { + display: 'block', + whiteSpace: 'nowrap', overflow: 'hidden', + textOverflow: 'ellipsis', + }, + sortLabelRoot: { + height: theme.spacing.unit * 3, + }, + sortLabelActive: { + color: 'inherit', }, }); const SortingControlBase = ({ - align, sortingDirection, columnTitle, classes, -}) => - (align === 'right' ? ( - - {!!sortingDirection && } - - {columnTitle} - - - ) : ( - - - {columnTitle} - - - - )); + align, sortingDirection, columnTitle, onClick, classes, text, +}) => ( + + + {columnTitle} + + +); SortingControlBase.propTypes = { align: PropTypes.string.isRequired, sortingDirection: PropTypes.oneOf(['asc', 'desc']), columnTitle: PropTypes.string.isRequired, classes: PropTypes.object.isRequired, + onClick: PropTypes.func.isRequired, + text: PropTypes.string, }; SortingControlBase.defaultProps = { sortingDirection: null, + text: 'Sort', }; export const SortingControl = withStyles(styles, { name: 'SortingControl' })(SortingControlBase); diff --git a/packages/dx-react-grid/docs/reference/table-header-row.md b/packages/dx-react-grid/docs/reference/table-header-row.md index 50259596d2..1061bf735e 100644 --- a/packages/dx-react-grid/docs/reference/table-header-row.md +++ b/packages/dx-react-grid/docs/reference/table-header-row.md @@ -24,6 +24,7 @@ allowSorting | boolean | false | If true, it allows an end-user to change sortin allowDragging | boolean | false | If true, it allows an end-user to drag a column by the header cell. Requires the [DragDropContext](drag-drop-context.md) dependency. allowGroupingByClick | boolean | false | If true, it renders a component that toggles a column's grouping state. Requires the [GroupingState](grouping-state.md) dependency. allowResizing | boolean | false | If true, it allows an end-user to change a column width. Requires the [TableColumnResizing](table-column-resizing.md) dependency. +messages | object | | An object that specifies [localization messages](#localization-messages). ## Interfaces @@ -33,7 +34,7 @@ A value with the [Column](grid.md#column) shape extended by the following fields Field | Type | Description ------|------|------------ -title? | string | Specifies a table column title. +title? | string | Specifies a table column's title. ### HeaderCellArgs @@ -45,14 +46,23 @@ Field | Type | Description ------|------|------------ column | [Column](#column) | A column object. allowSorting | boolean | If true, an end-user can change sorting by a column. -sortingDirection? | 'asc' | 'desc' | Specifies the column sort order. -changeSortingDirection | ({ keepOther: boolean, cancel: boolean }) | Changes column sort direction. Keeps the existing sorting if `keepOther` is set to `true`. Cancels sorting by the current column if `cancel` is set to true. +sortingDirection? | 'asc' | 'desc' | Specifies the column's sorting order. +changeSortingDirection | ({ keepOther: boolean, cancel: boolean }) | Changes column sorting direction. Keeps the existing sorting if `keepOther` is set to `true`. Cancels sorting by the current column if `cancel` is set to true. allowGroupingByClick | boolean | If true, a component that toggles a column's grouping state is rendered. groupByColumn | () => void | Toggles grouping for a column. allowDragging | boolean | If true, an end-user can start dragging a column by the header cell. dragPayload | any | A data object that identifies the corresponding column in the drag-and-drop context. changeColumnWidth | ({ shift: number }) => void | Changes the column width. A shift is added to the original column width value. -changeDraftColumnWidth | ({ shift: number }) => void | Changes the draft column width. A shift is added to the original column width value. If a shift is `null`, the draft width for the column is cleared. +changeDraftColumnWidth | ({ shift: number }) => void | Changes the draft column's width. A shift is added to the original column width value. If a shift is `null`, the draft width for the column is cleared. +getMessage | ([messageKey](#localization-messages): string) => string | Returns the text displayed in sorting controls within the sorting table header cell. + +## Localization Messages + +An object with the following shape: + +Field | Type | Default | Description +------|------|---------|------------ +sortingHint? | string | 'Sort' | Specifies the 'Sort' hint's text. Available in the "@devexpress/dx-react-grid-material-ui" package. ## Plugin Developer Reference diff --git a/packages/dx-react-grid/src/plugins/table-header-row.jsx b/packages/dx-react-grid/src/plugins/table-header-row.jsx index c1620a1d82..a6dde949b2 100644 --- a/packages/dx-react-grid/src/plugins/table-header-row.jsx +++ b/packages/dx-react-grid/src/plugins/table-header-row.jsx @@ -9,11 +9,12 @@ import { tableRowsWithHeading, isHeadingTableCell, isHeadingTableRow, + getMessagesFormatter, } from '@devexpress/dx-grid-core'; const getHeaderTableCellTemplateArgs = ( { - allowSorting, allowDragging, allowGroupingByClick, allowResizing, ...params + allowSorting, allowDragging, allowGroupingByClick, allowResizing, getMessage, ...params }, { sorting, columns, grouping }, { @@ -26,6 +27,7 @@ const getHeaderTableCellTemplateArgs = ( const result = { ...params, + getMessage, allowSorting: allowSorting && sorting !== undefined, allowGroupingByClick: allowGroupingByClick && groupingSupported, allowDragging: allowDragging && (!grouping || groupingSupported), @@ -63,7 +65,9 @@ export class TableHeaderRow extends React.PureComponent { allowResizing, headerCellTemplate, headerRowTemplate, + messages, } = this.props; + const getMessage = getMessagesFormatter(messages); return ( ({ tableRowsWithHeading: jest.fn(), isHeadingTableCell: jest.fn(), isHeadingTableRow: jest.fn(), + getMessagesFormatter: jest.fn(), })); const defaultDeps = { @@ -54,6 +56,7 @@ describe('TableHeaderRow', () => { tableRowsWithHeading.mockImplementation(() => 'tableRowsWithHeading'); isHeadingTableCell.mockImplementation(() => false); isHeadingTableRow.mockImplementation(() => false); + getMessagesFormatter.mockImplementation(messages => key => (messages[key] || key)); }); afterEach(() => { jest.resetAllMocks(); @@ -123,6 +126,31 @@ describe('TableHeaderRow', () => { .toBeCalledWith(defaultDeps.template.tableViewRow); }); + it('should pass correct getMessage prop to TableHeaderRowTemplate', () => { + isHeadingTableCell.mockImplementation(() => true); + const headerCellTemplate = jest.fn(() => null); + const deps = { + plugins: ['SortingState'], + }; + mount(( + + {pluginDepsToComponents(defaultDeps, deps)} + + + )); + + const { getMessage } = headerCellTemplate.mock.calls[0][0]; + + expect(getMessage('sortingHint')).toBe('test'); + }); + describe('resizing', () => { it('should require TableColumnResizing plugin', () => { expect(() => { diff --git a/packages/dx-testing/index.js b/packages/dx-testing/index.js index 43b14bdb7c..0871f19b49 100644 --- a/packages/dx-testing/index.js +++ b/packages/dx-testing/index.js @@ -10,8 +10,8 @@ module.exports = { const errorMessage = args[0]; if (!config.ignore - || - !config.ignore.filter(message => errorMessage.includes(message)).length) { + || + !config.ignore.filter(message => errorMessage.includes(message)).length) { throw new Error(format(...args).replace(/^Error: (?:Warning: )?/, '')); } };