From fd79729916cf6ddf27063b1865d39a36abb9e038 Mon Sep 17 00:00:00 2001 From: Michal Klobukowski Date: Wed, 20 Jun 2018 12:59:11 +0200 Subject: [PATCH] Create data table filter component Feature #13633 Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski --- package.json | 4 +- .../data-table-filters/data-table-filters.tsx | 189 ++++++++++++++++++ src/components/data-table/data-column.ts | 4 + src/components/data-table/data-table.tsx | 22 +- .../data-explorer/data-explorer.tsx | 16 ++ yarn.lock | 20 +- 6 files changed, 244 insertions(+), 11 deletions(-) create mode 100644 src/components/data-table-filters/data-table-filters.tsx diff --git a/package.json b/package.json index fda2ead62e..1f08e6cb85 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@material-ui/icons": "1.1.0", "@types/lodash": "4.14.109", "axios": "0.18.0", + "classnames": "^2.2.6", "lodash": "4.17.10", "react": "16.4.1", "react-dom": "16.4.1", @@ -27,11 +28,12 @@ "lint": "tslint src/** -t verbose" }, "devDependencies": { + "@types/classnames": "^2.2.4", "@types/enzyme": "3.1.10", "@types/enzyme-adapter-react-16": "1.0.2", "@types/jest": "23.1.0", "@types/node": "10.3.3", - "@types/react": "16.3.18", + "@types/react": "16.4.1", "@types/react-dom": "16.0.6", "@types/react-redux": "6.0.2", "@types/react-router": "4.0.26", diff --git a/src/components/data-table-filters/data-table-filters.tsx b/src/components/data-table-filters/data-table-filters.tsx new file mode 100644 index 0000000000..cf0260b165 --- /dev/null +++ b/src/components/data-table-filters/data-table-filters.tsx @@ -0,0 +1,189 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import * as React from "react"; +import { + WithStyles, + withStyles, + ButtonBase, + StyleRulesCallback, + Theme, + Popover, + List, + ListItem, + Checkbox, + ListItemText, + Button, + Card, + CardActions, + Typography, + CardContent +} from "@material-ui/core"; +import * as classnames from "classnames"; +import { DefaultTransformOrigin } from "../popover/helpers"; + +export interface DataTableFilterItem { + name: string; + selected: boolean; +} + +export interface DataTableFilterProps { + name: string; + filters: DataTableFilterItem[]; + onChange?: (filters: DataTableFilterItem[]) => void; +} + +interface DataTableFilterState { + anchorEl?: HTMLElement; + filters: DataTableFilterItem[]; + prevFilters: DataTableFilterItem[]; +} + +class DataTableFilter extends React.Component, DataTableFilterState> { + state: DataTableFilterState = { + anchorEl: undefined, + filters: [], + prevFilters: [] + }; + icon = React.createRef(); + + render() { + const { name, classes, children } = this.props; + return <> + !selected).length > 0 }])} + component="span" + onClick={this.open} + disableRipple> + {children} + + + + + + + {name} + + + + {this.state.filters.map((filter, index) => + + + + {filter.name} + + + )} + + + + + + + + ; + } + + static getDerivedStateFromProps(props: DataTableFilterProps, state: DataTableFilterState): DataTableFilterState { + return props.filters !== state.prevFilters + ? { ...state, filters: props.filters, prevFilters: props.filters } + : state; + } + + open = () => { + this.setState({ anchorEl: this.icon.current || undefined }); + } + + submit = () => { + const { onChange } = this.props; + if (onChange) { + onChange(this.state.filters); + } + this.setState({ anchorEl: undefined }); + } + + cancel = () => { + this.setState(prev => ({ + ...prev, + filters: prev.prevFilters, + anchorEl: undefined + })); + } + + toggleFilter = (toggledFilter: DataTableFilterItem) => () => { + this.setState(prev => ({ + ...prev, + filters: prev.filters.map(filter => + filter === toggledFilter + ? { ...filter, selected: !filter.selected } + : filter) + })); + } +} + + +export type CssRules = "root" | "icon" | "active" | "checkbox"; + +const styles: StyleRulesCallback = (theme: Theme) => ({ + root: { + cursor: "pointer", + display: "inline-flex", + justifyContent: "flex-start", + flexDirection: "inherit", + alignItems: "center", + "&:hover": { + color: theme.palette.text.primary, + }, + "&:focus": { + color: theme.palette.text.primary, + }, + }, + active: { + color: theme.palette.text.primary, + '& $icon': { + opacity: 1, + }, + }, + icon: { + marginRight: 4, + marginLeft: 4, + opacity: 0.7, + userSelect: "none", + width: 16 + }, + checkbox: { + width: 24, + height: 24 + } +}); + +export default withStyles(styles)(DataTableFilter); diff --git a/src/components/data-table/data-column.ts b/src/components/data-table/data-column.ts index f3d9576dd6..7ac568a25b 100644 --- a/src/components/data-table/data-column.ts +++ b/src/components/data-table/data-column.ts @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: AGPL-3.0 +import { DataTableFilterItem } from "../data-table-filters/data-table-filters"; + export interface DataColumn { name: string; selected: boolean; @@ -9,6 +11,8 @@ export interface DataColumn { key?: React.Key; sortDirection?: SortDirection; onSortToggle?: () => void; + filters?: DataTableFilterItem[]; + onFiltersChange?: (filters: DataTableFilterItem[]) => void; render: (item: T) => React.ReactElement; renderHeader?: () => React.ReactElement | null; } diff --git a/src/components/data-table/data-table.tsx b/src/components/data-table/data-table.tsx index 43efdd0ada..26f6faf5dd 100644 --- a/src/components/data-table/data-table.tsx +++ b/src/components/data-table/data-table.tsx @@ -5,6 +5,7 @@ import * as React from 'react'; import { Table, TableBody, TableRow, TableCell, TableHead, TableSortLabel, StyleRulesCallback, Theme, WithStyles, withStyles, Typography } from '@material-ui/core'; import { DataColumn } from './data-column'; +import DataTableFilters from "../data-table-filters/data-table-filters"; export type DataColumns = Array>; @@ -25,16 +26,23 @@ class DataTable extends React.Component & WithStyles {columns .filter(column => column.selected) - .map(({ name, renderHeader, key, sortDirection, onSortToggle }, index) => + .map(({ name, renderHeader, key, sortDirection, onSortToggle, filters, onFiltersChange }, index) => {renderHeader ? renderHeader() : - onSortToggle && onSortToggle()}> - {name} - } + filters ? + + {name} + : + onSortToggle && onSortToggle()}> + {name} + } )} diff --git a/src/views-components/data-explorer/data-explorer.tsx b/src/views-components/data-explorer/data-explorer.tsx index ffb21f93fb..dab686ee46 100644 --- a/src/views-components/data-explorer/data-explorer.tsx +++ b/src/views-components/data-explorer/data-explorer.tsx @@ -49,10 +49,26 @@ class DataExplorer extends React.Component }, { name: "Status", selected: true, + onFiltersChange: console.log, + filters: [{ + name: "In progress", + selected: true + }, { + name: "Complete", + selected: true + }], render: item => renderStatus(item.status) }, { name: "Type", selected: true, + onFiltersChange: console.log, + filters: [{ + name: "Collection", + selected: true + }, { + name: "Group", + selected: true + }], render: item => renderType(item.type) }, { name: "Owner", diff --git a/yarn.lock b/yarn.lock index eee6c8604b..05a6ef11e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -65,6 +65,10 @@ version "0.22.7" resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.7.tgz#4a92eafedfb2b9f4437d3a4410006d81114c66ce" +"@types/classnames@^2.2.4": + version "2.2.4" + resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.4.tgz#d3ee9ebf714aa34006707b8f4a58fd46b642305a" + "@types/enzyme-adapter-react-16@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@types/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.2.tgz#15ae37c64d6221a6f4b3a4aacc357cf773859de4" @@ -145,12 +149,18 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@16.3.18": +"@types/react@*": version "16.3.18" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.3.18.tgz#bf195aed4d77dc86f06e4c9bb760214a3b822b8d" dependencies: csstype "^2.2.0" +"@types/react@16.4.1": + version "16.4.1" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.4.1.tgz#c53bbfb4a78933db587da085ac60dbf5fcf73f8f" + dependencies: + csstype "^2.2.0" + "@types/redux-devtools@3.0.44": version "3.0.44" resolved "https://registry.yarnpkg.com/@types/redux-devtools/-/redux-devtools-3.0.44.tgz#2781b87067b8aec3102d4cb4a478feb340df5259" @@ -1559,7 +1569,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.2.5: +classnames@^2.2.5, classnames@^2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" @@ -2007,10 +2017,14 @@ cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": dependencies: cssom "0.3.x" -csstype@^2.0.0, csstype@^2.2.0, csstype@^2.5.2: +csstype@^2.0.0, csstype@^2.5.2: version "2.5.3" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.3.tgz#2504152e6e1cc59b32098b7f5d6a63f16294c1f7" +csstype@^2.2.0: + version "2.5.5" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.5.tgz#4125484a3d42189a863943f23b9e4b80fedfa106" + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" -- 2.39.5