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