Merge branch '14219-missing-tooltip-for-filters-icon'
[arvados-workbench2.git] / src / components / data-table-filters / data-table-filters.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import * as React from "react";
6 import {
7     WithStyles,
8     withStyles,
9     ButtonBase,
10     StyleRulesCallback,
11     Theme,
12     Popover,
13     List,
14     ListItem,
15     Checkbox,
16     ListItemText,
17     Button,
18     Card,
19     CardActions,
20     Typography,
21     CardContent,
22     Tooltip
23 } from "@material-ui/core";
24 import * as classnames from "classnames";
25 import { DefaultTransformOrigin } from "../popover/helpers";
26
27 export type CssRules = "root" | "icon" | "active" | "checkbox";
28
29 const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
30     root: {
31         cursor: "pointer",
32         display: "inline-flex",
33         justifyContent: "flex-start",
34         flexDirection: "inherit",
35         alignItems: "center",
36         "&:hover": {
37             color: theme.palette.text.primary,
38         },
39         "&:focus": {
40             color: theme.palette.text.primary,
41         },
42     },
43     active: {
44         color: theme.palette.text.primary,
45         '& $icon': {
46             opacity: 1,
47         },
48     },
49     icon: {
50         marginRight: 4,
51         marginLeft: 4,
52         opacity: 0.7,
53         userSelect: "none",
54         width: 16
55     },
56     checkbox: {
57         width: 24,
58         height: 24
59     }
60 });
61
62 export interface DataTableFilterItem {
63     name: string;
64     selected: boolean;
65 }
66
67 export interface DataTableFilterProps {
68     name: string;
69     filters: DataTableFilterItem[];
70     onChange?: (filters: DataTableFilterItem[]) => void;
71 }
72
73 interface DataTableFilterState {
74     anchorEl?: HTMLElement;
75     filters: DataTableFilterItem[];
76     prevFilters: DataTableFilterItem[];
77 }
78
79 export const DataTableFilters = withStyles(styles)(
80     class extends React.Component<DataTableFilterProps & WithStyles<CssRules>, DataTableFilterState> {
81         state: DataTableFilterState = {
82             anchorEl: undefined,
83             filters: [],
84             prevFilters: []
85         };
86         icon = React.createRef<HTMLElement>();
87
88         render() {
89             const { name, classes, children } = this.props;
90             const isActive = this.state.filters.some(f => f.selected);
91             return <>
92                 <Tooltip title='Filters'>
93                     <ButtonBase
94                         className={classnames([classes.root, { [classes.active]: isActive }])}
95                         component="span"
96                         onClick={this.open}
97                         disableRipple>
98                         {children}
99                         <i className={classnames(["fas fa-filter", classes.icon])}
100                             data-fa-transform="shrink-3"
101                             ref={this.icon} />
102                     </ButtonBase>
103                 </Tooltip>
104                 <Popover
105                     anchorEl={this.state.anchorEl}
106                     open={!!this.state.anchorEl}
107                     anchorOrigin={DefaultTransformOrigin}
108                     transformOrigin={DefaultTransformOrigin}
109                     onClose={this.cancel}>
110                     <Card>
111                         <CardContent>
112                             <Typography variant="caption">
113                                 {name}
114                             </Typography>
115                         </CardContent>
116                         <List dense>
117                             {this.state.filters.map((filter, index) =>
118                                 <ListItem
119                                     button
120                                     key={index}
121                                     onClick={this.toggleFilter(filter)}>
122                                     <Checkbox
123                                         disableRipple
124                                         color="primary"
125                                         checked={filter.selected}
126                                         className={classes.checkbox} />
127                                     <ListItemText>
128                                         {filter.name}
129                                     </ListItemText>
130                                 </ListItem>
131                             )}
132                         </List>
133                         <CardActions>
134                             <Button
135                                 color="primary"
136                                 variant="raised"
137                                 size="small"
138                                 onClick={this.submit}>
139                                 Ok
140                             </Button>
141                             <Button
142                                 color="primary"
143                                 variant="outlined"
144                                 size="small"
145                                 onClick={this.cancel}>
146                                 Cancel
147                             </Button>
148                         </CardActions >
149                     </Card>
150                 </Popover>
151             </>;
152         }
153
154         static getDerivedStateFromProps(props: DataTableFilterProps, state: DataTableFilterState): DataTableFilterState {
155             return props.filters !== state.prevFilters
156                 ? { ...state, filters: props.filters, prevFilters: props.filters }
157                 : state;
158         }
159
160         open = () => {
161             this.setState({ anchorEl: this.icon.current || undefined });
162         }
163
164         submit = () => {
165             const { onChange } = this.props;
166             if (onChange) {
167                 onChange(this.state.filters);
168             }
169             this.setState({ anchorEl: undefined });
170         }
171
172         cancel = () => {
173             this.setState(prev => ({
174                 ...prev,
175                 filters: prev.prevFilters,
176                 anchorEl: undefined
177             }));
178         }
179
180         toggleFilter = (toggledFilter: DataTableFilterItem) => () => {
181             this.setState(prev => ({
182                 ...prev,
183                 filters: prev.filters.map(filter =>
184                     filter === toggledFilter
185                         ? { ...filter, selected: !filter.selected }
186                         : filter)
187             }));
188         }
189     }
190 );