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