Merge pull request #654 from Koroeskohr/translate-sortabletable

Translate SortableTable to Typescript
This commit is contained in:
Patrik Oldsberg
2020-04-29 16:42:59 +02:00
committed by GitHub
3 changed files with 158 additions and 188 deletions
+1 -2
View File
@@ -53,8 +53,7 @@
"react-helmet": "5.2.1",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"react-sparklines": "^1.7.0",
"recompose": "0.30.0"
"react-sparklines": "^1.7.0"
},
"files": [
"dist"
@@ -14,9 +14,7 @@
* limitations under the License.
*/
import React from 'react';
import { pure } from 'recompose';
import PropTypes from 'prop-types';
import React, { FC, CSSProperties, memo, useState, useEffect } from 'react';
import {
Table,
TableBody,
@@ -28,11 +26,45 @@ import {
Tooltip,
} from '@material-ui/core';
type Column = {
id: string;
label: string;
numeric?: boolean;
disablePadding: boolean;
style: CSSProperties;
// FIXME (@Koroeskohr)
sortValue: (obj: object) => any;
};
// XXX (@Koroeskohr): no idea what I did but this typechecks. Need answer for CellContentProps
type Row = { [name in string]: any };
type SortHandler = (
event: React.MouseEvent<Element, MouseEvent>,
property: string,
) => void;
type Order = 'asc' | 'desc';
type EnhancedTableHeadProps = {
columns: Column[];
onRequestSort: SortHandler;
order: Order;
orderBy: string;
};
/**
* Table header which supports sorting ascending and desc
*/
const EnhancedTableHead = ({ columns, onRequestSort, order, orderBy }) => {
const createSortHandler = property => event => {
const EnhancedTableHead: FC<EnhancedTableHeadProps> = ({
columns,
onRequestSort,
order,
orderBy,
}) => {
const createSortHandler = (property: string) => (
event: React.MouseEvent<HTMLElement, MouseEvent>,
) => {
onRequestSort(event, property);
};
@@ -68,25 +100,15 @@ const EnhancedTableHead = ({ columns, onRequestSort, order, orderBy }) => {
);
};
EnhancedTableHead.propTypes = {
columns: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
numeric: PropTypes.bool,
disablePadding: PropTypes.bool,
style: PropTypes.object,
}),
).isRequired,
onRequestSort: PropTypes.func.isRequired,
order: PropTypes.string.isRequired,
orderBy: PropTypes.string.isRequired,
type CellContentProps = {
// XXX (@Koroeskohr): what am I supposed to use here
data: any | any[];
};
/**
* CellContent can be an array or a string
*/
const CellContent = ({ data }) => {
const CellContent: FC<CellContentProps> = ({ data }) => {
if (Array.isArray(data)) {
return data.map((item, index) => (
<span key={index}>
@@ -98,11 +120,12 @@ const CellContent = ({ data }) => {
return data;
};
CellContent.propTypes = {
data: PropTypes.any.isRequired,
type DataTableCellProps = {
column: Column;
row: Row;
};
const DataTableCell = ({ column, row }) => {
const DataTableCell: FC<DataTableCellProps> = ({ column, row }) => {
return (
<TableCell
align={column.numeric ? 'right' : 'left'}
@@ -115,8 +138,20 @@ const DataTableCell = ({ column, row }) => {
};
const noop = () => {};
const DataTableRow = pure(({ row, columns, handleRowClick, style }) => {
const onClick = event => (handleRowClick || noop)(event, row.id);
type DataTableRowProps = {
row: Row;
columns: Column[];
handleRowClick?: (event: React.MouseEvent, rowId: string) => void;
style?: React.CSSProperties;
};
const _DataTableRow: FC<DataTableRowProps> = ({
row,
columns,
handleRowClick,
style,
}) => {
const onClick: React.MouseEventHandler = event =>
(handleRowClick || noop)(event, row.id);
return (
<TableRow onClick={onClick} key={row.id} style={style}>
{columns.map(column => (
@@ -124,17 +159,18 @@ const DataTableRow = pure(({ row, columns, handleRowClick, style }) => {
))}
</TableRow>
);
});
};
const DataTableRow = memo(_DataTableRow);
/**
* Table with sorting capabilites automatic rendering of cells
* Note that the objects in props.data needs have an id property
* The columns array defines which columns from the data to show.
*
* @param {Array[Object]} data A list of data entries, where object properties must
* @param data A list of data entries, where object properties must
* be strictly equal to column ids.
*
* @param {Array[Object]} columns A list of columns with the following shape:
* @param columns A list of columns with the following shape:
* {
* // The column identifier must be strictly equal the relevant data entry
* // key:
@@ -154,17 +190,17 @@ const DataTableRow = pure(({ row, columns, handleRowClick, style }) => {
* sortValue: (Object) => Any
* }
*
* @param {String} orderBy The column ID initially used for sorting
* @param orderBy The column ID initially used for sorting
*
* @param {String} [dataVersion] A version identifier for the data which *must*
* @param dataVersion A version identifier for the data which *must*
* be updated when the contents of the data changes. This can be used for
* components where the same SortableTable element will be used to display
* variable sets of data.
*
* @param {Array[Object]} [footerData] A list of data entries to be placed in
* @param footerData A list of data entries to be placed in
* the table footer, which will not be sorted.
*
* @param {(String, Event) => Void} [onRowClicked] Get notified when a user clicks
* @param onRowClicked Get notified when a user clicks
* on the row. The handler will receive the row id as the first argument, and
* the synthetic click event as the second argument.
*
@@ -188,49 +224,38 @@ const DataTableRow = pure(({ row, columns, handleRowClick, style }) => {
* ev.preventDefault();}}/>)
* }
*
* // XXX (@koroeskohr): supposedly this is leftover from your internal doc
* @deprecated use shared/components/DataGrid
*/
class SortableTable extends React.Component {
static propTypes = {
// TODO: figure out how to make id of the object requried while others are dynamic
data: PropTypes.arrayOf(PropTypes.object).isRequired,
orderBy: PropTypes.string.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onRowClicked: PropTypes.func,
dataVersion: PropTypes.string,
};
constructor(props) {
super(props);
this.handleRowClick = this.handleRowClick.bind(this);
type SortableTableProps = {
data: Row[];
footerData: Row[];
orderBy: string;
columns: Column[];
onRowClicked?: (
id: string,
event: React.MouseEvent<Element, MouseEvent>,
) => void;
dataVersion: string;
};
this.state = {
orderBy: props.orderBy,
order: 'asc',
data: props.data,
};
}
type TableState = {
orderBy: string;
order: Order;
data: Row[];
};
handleRequestSort = (event, property) => {
event.preventDefault();
const orderBy = property;
let order = 'desc';
if (this.state.orderBy === property && this.state.order === 'desc') {
order = 'asc';
}
this.updateData(this.state.data, orderBy, order);
};
const SortableTable: FC<SortableTableProps> = props => {
const [state, setState] = useState<TableState>({
orderBy: props.orderBy,
order: 'asc',
data: props.data,
});
handleRowClick = (event, id) => {
if (this.props.onRowClicked) {
this.props.onRowClicked(id, event);
}
};
updateData = (data, orderBy, order) => {
const sortValueFn = (
this.props.columns.filter(col => col.id === orderBy)[0] || {}
).sortValue;
const updateData = (data: Row[], orderBy: string, order: Order) => {
const sortValueFn = (props.columns.find(col => col.id === orderBy) || {})
.sortValue;
const sortedData = data.slice().sort((a, b) => {
const valueA = sortValueFn ? sortValueFn(a) : a[orderBy];
@@ -241,56 +266,70 @@ class SortableTable extends React.Component {
if (valueB === '' || valueB === null) return -inc;
return valueA < valueB ? -inc : inc;
});
this.setState({ data: sortedData, order, orderBy });
setState({ data: sortedData, order, orderBy });
};
UNSAFE_componentWillReceiveProps(props) {
if (props.dataVersion !== this.props.dataVersion) {
this.updateData(props.data, this.state.orderBy, this.state.order);
const handleRequestSort: SortHandler = (event, property) => {
event.preventDefault();
const orderBy = property;
let order: Order = 'desc';
if (state.orderBy === property && state.order === 'desc') {
order = 'asc';
}
}
updateData(state.data, orderBy, order);
};
render() {
const { data, order, orderBy } = this.state;
const { columns, dataVersion, footerData } = this.props;
let tableFoot = null;
if (footerData && footerData.length > 0) {
tableFoot = (
<TableFooter>
{footerData.map(row => (
<DataTableRow
key={row.id}
columns={columns}
row={row}
style={{ height: 'auto' }}
/>
))}
</TableFooter>
);
const handleRowClick = (event: React.MouseEvent, id: string): void => {
if (props.onRowClicked) {
props.onRowClicked(id, event);
}
return (
<Table>
<EnhancedTableHead
columns={columns}
onRequestSort={this.handleRequestSort}
order={order}
orderBy={orderBy}
/>
<TableBody key={dataVersion}>
{data.map(row => (
<DataTableRow
key={row.id}
columns={columns}
row={row}
handleRowClick={this.handleRowClick}
/>
))}
</TableBody>
{tableFoot}
</Table>
};
useEffect(() => {
const { data } = props;
const { orderBy, order } = state;
updateData(data, orderBy, order);
}, [props.dataVersion]);
const { data, order, orderBy } = state;
const { columns, dataVersion, footerData } = props;
let tableFoot = null;
if (footerData && footerData.length > 0) {
tableFoot = (
<TableFooter>
{footerData.map(row => (
<DataTableRow
key={row.id}
columns={columns}
row={row}
style={{ height: 'auto' }}
/>
))}
</TableFooter>
);
}
}
return (
<Table>
<EnhancedTableHead
columns={columns}
onRequestSort={handleRequestSort}
order={order}
orderBy={orderBy}
/>
<TableBody key={dataVersion}>
{data.map(row => (
<DataTableRow
key={row.id}
columns={columns}
row={row}
handleRowClick={handleRowClick}
/>
))}
</TableBody>
{tableFoot}
</Table>
);
};
export default SortableTable;
+6 -74
View File
@@ -5159,7 +5159,7 @@ arrify@^1.0.1:
resolved "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
asap@^2.0.0, asap@~2.0.3, asap@~2.0.6:
asap@^2.0.0, asap@~2.0.6:
version "2.0.6"
resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
@@ -6359,11 +6359,6 @@ chalk@^4.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
change-emitter@^0.1.2:
version "0.1.6"
resolved "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515"
integrity sha1-6LL+PX8at9aaMhma/5HqaTFAlRU=
character-entities-legacy@^1.0.0:
version "1.1.4"
resolved "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1"
@@ -7133,11 +7128,6 @@ core-js-pure@^3.0.0, core-js-pure@^3.0.1:
resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a"
integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw==
core-js@^1.0.0:
version "1.2.7"
resolved "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
core-js@^2.4.0, core-js@^2.5.0:
version "2.6.11"
resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c"
@@ -9254,19 +9244,6 @@ fb-watchman@^2.0.0:
dependencies:
bser "2.1.1"
fbjs@^0.8.1:
version "0.8.17"
resolved "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
dependencies:
core-js "^1.0.0"
isomorphic-fetch "^2.1.1"
loose-envify "^1.0.0"
object-assign "^4.1.0"
promise "^7.1.1"
setimmediate "^1.0.5"
ua-parser-js "^0.7.18"
fd-slicer@~1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
@@ -10479,11 +10456,6 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoist-non-react-statics@^2.3.1:
version "2.5.5"
resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
@@ -11593,7 +11565,7 @@ is-ssh@^1.3.0:
dependencies:
protocols "^1.1.0"
is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0:
is-stream@^1.0.0, is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
@@ -11706,14 +11678,6 @@ isobject@^4.0.0:
resolved "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0"
integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==
isomorphic-fetch@^2.1.1:
version "2.2.1"
resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
dependencies:
node-fetch "^1.0.1"
whatwg-fetch ">=0.10.0"
isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
@@ -14413,14 +14377,6 @@ node-fetch@2.6.0, node-fetch@^2.3.0, node-fetch@^2.5.0, node-fetch@^2.6.0:
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
node-fetch@^1.0.1:
version "1.7.3"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==
dependencies:
encoding "^0.1.11"
is-stream "^1.0.1"
node-forge@0.9.0:
version "0.9.0"
resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579"
@@ -16619,13 +16575,6 @@ promise.series@^0.2.0:
resolved "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz#2cc7ebe959fc3a6619c04ab4dbdc9e452d864bbd"
integrity sha1-LMfr6Vn8OmYZwEq029yeRS2GS70=
promise@^7.1.1:
version "7.3.1"
resolved "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
dependencies:
asap "~2.0.3"
promise@^8.0.3:
version "8.1.0"
resolved "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e"
@@ -17089,7 +17038,7 @@ react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-i
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-lifecycles-compat@^3.0.2, react-lifecycles-compat@^3.0.4:
react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
@@ -17527,18 +17476,6 @@ rechoir@^0.6.2:
dependencies:
resolve "^1.1.6"
recompose@0.30.0:
version "0.30.0"
resolved "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz#82773641b3927e8c7d24a0d87d65aeeba18aabd0"
integrity sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w==
dependencies:
"@babel/runtime" "^7.0.0"
change-emitter "^0.1.2"
fbjs "^0.8.1"
hoist-non-react-statics "^2.3.1"
react-lifecycles-compat "^3.0.2"
symbol-observable "^1.0.4"
recursive-readdir@2.2.2, recursive-readdir@^2.2.2:
version "2.2.2"
resolved "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f"
@@ -18445,7 +18382,7 @@ set-value@^2.0.0, set-value@^2.0.1:
is-plain-object "^2.0.3"
split-string "^3.0.1"
setimmediate@^1.0.4, setimmediate@^1.0.5:
setimmediate@^1.0.4:
version "1.0.5"
resolved "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
@@ -19444,7 +19381,7 @@ svgo@^1.0.0, svgo@^1.2.2:
unquote "~1.1.1"
util.promisify "~1.0.0"
symbol-observable@^1.0.4, symbol-observable@^1.1.0:
symbol-observable@^1.1.0:
version "1.2.0"
resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
@@ -20071,11 +20008,6 @@ typescript@^3.7.4, typescript@^3.8.3:
resolved "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061"
integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==
ua-parser-js@^0.7.18:
version "0.7.21"
resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777"
integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
@@ -20758,7 +20690,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
dependencies:
iconv-lite "0.4.24"
whatwg-fetch@3.0.0, whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0:
whatwg-fetch@3.0.0, whatwg-fetch@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==