diff --git a/README.md b/README.md index e5a429a6..937efb3a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.com/jbetancur/react-data-table-component.svg?branch=master)]( ) [![npm version](https://badge.fury.io/js/react-data-table-component.svg)](https://badge.fury.io/js/react-data-table-component) [![codecov](https://codecov.io/gh/jbetancur/react-data-table-component/branch/master/graph/badge.svg)](https://codecov.io/gh/jbetancur/react-data-table-component) [![Storybook](https://cdn.jsdelivr.net/gh/storybookjs/brand@master/badge/badge-storybook.svg)](https://jbetancur.github.io/react-data-table-component) +[![Build Status](https://travis-ci.com/jbetancur/react-data-table-component.svg?branch=master)]( ) [![npm version](https://badge.fury.io/js/react-data-table-component.svg)](https://badge.fury.io/js/react-data-table-component) [![codecov](https://codecov.io/gh/jbetancur/react-data-table-component/branch/master/graph/badge.svg)](https://codecov.io/gh/jbetancur/react-data-table-component) [![Storybook](https://cdn.jsdelivr.net/gh/storybookjs/brand@master/badge/badge-storybook.svg)](https://jbetancur.github.io/react-data-table-component) @@ -34,6 +34,7 @@ - [Using Custom Checkboxes and Indeterminate State](#using-custom-checkboxes-and-indeterminate-state) - [Custom Cells](#custom-cells) - [Expandable Rows](#expandable-rows) + - [Custom Sort Function](#custom-sort-function) - [UI Library Integration](#ui-library-integration) - [Optimizing for Performance and Caveats](#optimizing-for-performance-and-caveats) - [Passing non-primitive props (objects, arrays and functions)](#passing-non-primitive-props-objects-arrays-and-functions) @@ -112,7 +113,7 @@ Nothing new here - we are using an array of object literals and properties to de | name | string, component or number | no | the display name of our Column e.g. 'Name' | | selector | string or (row, index) => {} | no | a data set property in dot notation. e.g.
`property1.nested1.nested2`
`property1.items[0].nested2`
or as a function e.g.
`row => row.timestamp`. A `selector` is required anytime you want to display data but can be ommitted if your column does not require showing data (e.g. an actions column) | | sortable | bool | no | if the column is sortable.

**Note:** `selector` is required for the column to sort | -| sortFunction | function | no | custom sorting function e.g. `(rowA, rowB) => rowA.myIndex - rowB.myIndex` (see [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)) | +| sortFunction | function | no | by default RDT uses lodash `lodash.orderBy`, however, you can override the default behavior by passing in a custom sort function. [defining a custom sort function](#-custom-sort-function) | | format | (row, index) => {} | no | apply formatting to the selector e.g. `row => moment(row.timestamp).format('lll')` without changing the actual selector value. | | cell | (row, index, column, id) => {} | no | for ultimate control use `cell` to render your own custom component!
e.g `row =>

{row.title}

`.

if you are using properties such as: `onRowClicked`, `onRowDoubleClicked`, `expandOnRowClicked` or `expandOnRowDoubleClicked` then those click events will be ignored when clicking on your custom cell. To allow `RowClicked` events you can add `data-tag="allowRowEvents"` to your custom cell component elements. If your custom cell component is more complex and has nested elements you want to add `data-tag="allowRowEvents"` to the innermost element or on the elements you want to propagate the click event to.
e.g `row =>

{row.title}

`

**Note:** that using `cell` **negates `format`**. | | grow | number | no | [flex-grow](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-grow) of the column. This is useful if you want a column to take up more width than its relatives (without having to set widths explicitly). this will be affected by other columns where you have explicitly set widths | @@ -697,6 +698,37 @@ class MyComponent extends Component { }; ``` +## Custom Sort Function + +By default RDT uses lodash.orderBy, however, if you wish to override the internal sorting you can pass in your own sorting algorithm. The callback signature will give you access to RDT internal fields. Here is an example of how to use `Array.sort`; + +- `rows` your data rows +- `selector` is the function you defined on your column.selector. We're going to call this function and pass in the row data in the example below. +- `direction` RDT's current sorting position. + +```js +const customSort = (rows, selector, direction) => { + return rows.sort((rowA, rowB) => { + // use the selector function to resolve your field names by passing the sort comparitors + const aField = selector(rowA) + const bField = selector(rowB) + + let comparison = 0; + + if (aField > bField) { + comparison = 1; + } else if (aField < bField) { + comparison = -1; + } + + return direction === 'desc' ? comparison * -1 : comparison; + }); +}; + +... + +``` + ## UI Library Integration React Data Table Component makes it easy to incorporate ui components from other libraries for overriding things like the sort icon, select checkbox. diff --git a/package.json b/package.json index 24de0daa..3c3a481b 100644 --- a/package.json +++ b/package.json @@ -41,31 +41,31 @@ "devDependencies": { "@material-ui/core": "^4.11.3", "@material-ui/icons": "^4.11.2", - "@storybook/addon-a11y": "^6.1.15", - "@storybook/addon-actions": "^6.1.15", - "@storybook/addon-console": "^1.2.2", - "@storybook/addon-links": "^6.1.15", - "@storybook/addon-storysource": "^6.1.15", + "@storybook/addon-a11y": "^6.1.18", + "@storybook/addon-actions": "^6.1.18", + "@storybook/addon-console": "^1.2.3", + "@storybook/addon-links": "^6.1.18", + "@storybook/addon-storysource": "^6.1.18", "@storybook/react": "^6.1.15", - "@testing-library/react": "^11.2.3", + "@testing-library/react": "^11.2.5", "@types/faker": "^5.1.5", "@types/jest": "^26.0.20", "@types/lodash-es": "^4.17.4", "@types/lodash.orderby": "^4.6.6", - "@types/node": "^14.14.22", - "@types/react": "^17.0.0", - "@types/react-dom": "^17.0.0", + "@types/node": "^14.14.31", + "@types/react": "^17.0.2", + "@types/react-dom": "^17.0.1", "@types/styled-components": "^5.1.7", - "@typescript-eslint/eslint-plugin": "^4.14.1", - "@typescript-eslint/parser": "^4.14.1", + "@typescript-eslint/eslint-plugin": "^4.15.1", + "@typescript-eslint/parser": "^4.15.1", "axios": "^0.21.1", "babel-eslint": "^10.1.0", "codecov": "^3.8.1", - "eslint": "^7.18.0", - "eslint-config-prettier": "^7.2.0", + "eslint": "^7.20.0", + "eslint-config-prettier": "^8.0.0", "eslint-config-react-app": "^6.0.0", "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jest": "^24.1.3", + "eslint-plugin-jest": "^24.1.5", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-prettier": "^3.3.1", "eslint-plugin-react": "^7.22.0", @@ -74,7 +74,7 @@ "jest": "^26.6.3", "jest-styled-components": "^7.0.3", "jest-watch-typeahead": "^0.6.1", - "lodash-es": "^4.17.20", + "lodash-es": "^4.17.21", "memoize-one": "^5.1.1", "moment": "^2.29.1", "prettier": "^2.2.1", @@ -82,19 +82,19 @@ "react-app-polyfill": "^2.0.0", "react-dom": "^17.0.1", "rimraf": "^3.0.2", - "rollup": "^2.38.0", + "rollup": "^2.39.0", "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-terser": "^7.0.2", - "rollup-plugin-typescript2": "^0.29.0", + "rollup-plugin-typescript2": "^0.30.0", "rollup-plugin-visualizer": "^4.2.0", "styled-components": "^5.0.1", - "stylelint": "^13.9.0", + "stylelint": "^13.11.0", "stylelint-config-recommended": "^3.0.0", "stylelint-config-styled-components": "^0.1.1", "stylelint-processor-styled-components": "^1.10.0", - "ts-jest": "^26.4.4", - "typescript": "^4.1.3" + "ts-jest": "^26.5.1", + "typescript": "^4.1.5" }, "dependencies": { "deepmerge": "^4.2.2", diff --git a/src/DataTable/DataTable.tsx b/src/DataTable/DataTable.tsx index 21713b43..26409d6c 100644 --- a/src/DataTable/DataTable.tsx +++ b/src/DataTable/DataTable.tsx @@ -332,12 +332,7 @@ function DataTable(props: TableProps): JSX.Element { {showTableHead() && ( - + {selectableRows && (showSelectAll ? ( @@ -359,6 +354,7 @@ function DataTable(props: TableProps): JSX.Element { ` `; interface ColumnSortableProps { - sortable: boolean | undefined; + disabled: boolean; sortActive: boolean; + sortable: boolean | undefined; } -const ColumnSortable = styled.div` - display: inline-flex; - align-items: center; - height: 100%; - line-height: 1; - user-select: none; +const sortableCSS = css` ${({ theme, sortActive }) => (sortActive ? theme.headCells.activeSortStyle : theme.headCells.inactiveSortStyle)}; span.__rdt_custom_sort_icon__ { @@ -43,19 +39,38 @@ const ColumnSortable = styled.div` } &:hover { - ${({ sortable }) => sortable && 'cursor: pointer'}; - ${({ sortable, theme }) => sortable && theme.headCells.activeStyle}; + cursor: pointer; + ${({ theme }) => theme.headCells.activeStyle}; span, span.__rdt_custom_sort_icon__ * { - ${({ sortActive, sortable }) => !sortActive && sortable && 'opacity: 1'}; + ${({ sortActive }) => !sortActive && 'opacity: 1'}; } } `; +const ColumnSortable = styled.div` + align-items: center; + height: 100%; + line-height: 1; + outline: none; + user-select: none; + display: inline-flex; + overflow: hidden; + ${({ disabled, sortable }) => sortable && !disabled && sortableCSS}; +`; + +const ColumnText = styled.div` + overflow: hidden; + font-weight: 500; + white-space: nowrap; + text-overflow: ellipsis; +`; + type TableColProps = { rows: T[]; column: TableColumn; + disabled: boolean; sortIcon?: React.ReactNode; pagination: boolean; paginationServer: boolean; @@ -71,6 +86,7 @@ type TableColProps = { function TableCol({ rows, column, + disabled, selectedColumn, sortDirection, sortFunction, @@ -103,16 +119,19 @@ function TableCol({ direction = sortDirection === 'asc' ? 'desc' : 'asc'; } - let sortedRows = sort(rows, column.selector, direction, sortFunction); + let sortedRows = rows; + + if (!sortServer) { + sortedRows = sort(rows, column.selector, direction, sortFunction); - // declare this as a const since ts throws strict null check on a ternary line 108 - // sortFn is still checked for undefined or null - const sortFn = column.sortFunction; + // colCustomSortFn is still checked for undefined or null + const colCustomSortFn = column.sortFunction; - if (sortFn) { - const customSortFunction = direction === 'asc' ? sortFn : (a: T, b: T) => sortFn(a, b) * -1; + if (colCustomSortFn) { + const customSortFunction = direction === 'asc' ? colCustomSortFn : (a: T, b: T) => colCustomSortFn(a, b) * -1; - sortedRows = [...rows].sort(customSortFunction); + sortedRows = [...rows].sort(customSortFunction); + } } onSort({ @@ -169,16 +188,17 @@ function TableCol({ role="columnheader" tabIndex={0} className="rdt_TableCol_Sortable" - onClick={handleSortChange} - onKeyPress={handleKeyPress} + onClick={!disabled ? handleSortChange : undefined} + onKeyPress={!disabled ? handleKeyPress : undefined} + sortActive={!disabled && sortActive} sortable={column.sortable} - sortActive={sortActive} + disabled={disabled} > - {customSortIconRight && renderCustomSortIcon()} - {nativeSortIconRight && renderNativeSortIcon(sortActive)} -
{column.name}
- {customSortIconLeft && renderCustomSortIcon()} - {nativeSortIconLeft && renderNativeSortIcon(sortActive)} + {!disabled && customSortIconRight && renderCustomSortIcon()} + {!disabled && nativeSortIconRight && renderNativeSortIcon(sortActive)} + {column.name} + {!disabled && customSortIconLeft && renderCustomSortIcon()} + {!disabled && nativeSortIconLeft && renderNativeSortIcon(sortActive)} )} diff --git a/src/DataTable/TableHeadRow.tsx b/src/DataTable/TableHeadRow.tsx index c5d297a0..bb33523c 100644 --- a/src/DataTable/TableHeadRow.tsx +++ b/src/DataTable/TableHeadRow.tsx @@ -1,8 +1,4 @@ -import styled, { css } from 'styled-components'; - -const disabledCSS = css` - pointer-events: none; -`; +import styled from 'styled-components'; const TableHeadRow = styled.div<{ dense?: boolean; @@ -13,7 +9,6 @@ const TableHeadRow = styled.div<{ width: 100%; ${({ theme }) => theme.headRow.style}; ${({ dense, theme }) => dense && theme.headRow.denseStyle}; - ${({ disabled }) => disabled && disabledCSS}; `; export default TableHeadRow; diff --git a/src/DataTable/__tests__/__snapshots__/DataTable.test.tsx.snap b/src/DataTable/__tests__/__snapshots__/DataTable.test.tsx.snap index 1b813613..dab7d46b 100644 --- a/src/DataTable/__tests__/__snapshots__/DataTable.test.tsx.snap +++ b/src/DataTable/__tests__/__snapshots__/DataTable.test.tsx.snap @@ -62,7 +62,7 @@ exports[`DataTable::Header header should not display with no title 1`] = ` padding-right: 16px; } -.c10 { +.c11 { position: relative; display: -webkit-box; display: -webkit-flex; @@ -94,17 +94,17 @@ exports[`DataTable::Header header should not display with no title 1`] = ` min-width: 100px; } -.c11 { +.c12 { font-weight: 400; } -.c11 div:first-child { +.c12 div:first-child { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.c9 { +.c10 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -124,65 +124,39 @@ exports[`DataTable::Header header should not display with no title 1`] = ` min-height: 48px; } -.c9:not(:last-of-type) { +.c10:not(:last-of-type) { border-bottom-style: solid; border-bottom-width: 1px; border-bottom-color: rgba(0,0,0,.12); } .c7 { - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; align-items: center; height: 100%; line-height: 1; + outline: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + overflow: hidden; } -.c7:focus { - outline: none; - color: rgba(0,0,0,.54); -} - -.c7:hover { - color: rgba(0,0,0,.54); -} - -.c7 span.__rdt_custom_sort_icon__ i, -.c7 span.__rdt_custom_sort_icon__ svg { - opacity: 0; - color: inherit; - font-size: 18px !important; - height: 18px !important; - width: 18px !important; - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - -webkit-transform-style: preserve-3d; - -ms-transform-style: preserve-3d; - transform-style: preserve-3d; - -webkit-transition-duration: 125ms; - transition-duration: 125ms; - -webkit-transition-property: -webkit-transform; - -webkit-transition-property: transform; - transition-property: transform; -} - -.c7 span.__rdt_custom_sort_icon__.asc i, -.c7 span.__rdt_custom_sort_icon__.asc svg { - -webkit-transform: rotate(180deg); - -ms-transform: rotate(180deg); - transform: rotate(180deg); +.c8 { + overflow: hidden; + font-weight: 500; + white-space: nowrap; + text-overflow: ellipsis; } -.c8 { +.c9 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -234,7 +208,9 @@ exports[`DataTable::Header header should not display with no title 1`] = ` role="columnheader" tabindex="0" > -
+
Test
@@ -242,17 +218,17 @@ exports[`DataTable::Header header should not display with no title 1`] = `
-
+
Test
@@ -1401,17 +1353,17 @@ exports[`DataTable::Header should render without a header if noHeader is true 1`
-
+
Test
@@ -1987,17 +1915,17 @@ exports[`DataTable::Pagination should change the page position when using pagina