1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import React, { useEffect } from "react";
20 } from "@material-ui/core";
21 import classnames from "classnames";
22 import { DefaultTransformOrigin } from "components/popover/helpers";
23 import { createTree } from 'models/tree';
24 import { DataTableFilters, DataTableFiltersTree } from "./data-table-filters-tree";
25 import { getNodeDescendants } from 'models/tree';
26 import debounce from "lodash/debounce";
28 export type CssRules = "root" | "icon" | "iconButton" | "active" | "checkbox";
30 const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
33 display: "inline-flex",
34 justifyContent: "flex-start",
35 flexDirection: "inherit",
38 color: theme.palette.text.primary,
41 color: theme.palette.text.primary,
45 color: theme.palette.text.primary,
58 color: theme.palette.text.primary,
72 export interface DataTableFilterProps {
74 filters: DataTableFilters;
75 onChange?: (filters: DataTableFilters) => void;
78 * When set to true, only one filter can be selected at a time.
80 mutuallyExclusive?: boolean;
83 * By default `all` filters selection means that label should be grayed out.
84 * Use `none` when label is supposed to be grayed out when no filter is selected.
86 defaultSelection?: SelectionMode;
89 interface DataTableFilterState {
90 anchorEl?: HTMLElement;
91 filters: DataTableFilters;
92 prevFilters: DataTableFilters;
95 export const DataTableFiltersPopover = withStyles(styles)(
96 class extends React.Component<DataTableFilterProps & WithStyles<CssRules>, DataTableFilterState> {
97 state: DataTableFilterState = {
99 filters: createTree(),
100 prevFilters: createTree(),
102 icon = React.createRef<HTMLElement>();
105 const { name, classes, defaultSelection = SelectionMode.ALL, children } = this.props;
106 const isActive = getNodeDescendants('')(this.state.filters)
107 .some(f => defaultSelection === SelectionMode.ALL
112 <Tooltip disableFocusListener title='Filters'>
114 className={classnames([classes.root, { [classes.active]: isActive }])}
119 <IconButton component='span' classes={{ root: classes.iconButton }} tabIndex={-1}>
120 <i className={classnames(["fas fa-filter", classes.icon])}
121 data-fa-transform="shrink-3"
127 anchorEl={this.state.anchorEl}
128 open={!!this.state.anchorEl}
129 anchorOrigin={DefaultTransformOrigin}
130 transformOrigin={DefaultTransformOrigin}
131 onClose={this.close}>
134 <Typography variant="caption">
138 <DataTableFiltersTree
139 filters={this.state.filters}
140 mutuallyExclusive={this.props.mutuallyExclusive}
141 onChange={this.onChange} />
142 {this.props.mutuallyExclusive ||
148 onClick={this.close}>
155 <this.MountHandler />
159 static getDerivedStateFromProps(props: DataTableFilterProps, state: DataTableFilterState): DataTableFilterState {
160 return props.filters !== state.prevFilters
161 ? { ...state, filters: props.filters, prevFilters: props.filters }
166 this.setState({ anchorEl: this.icon.current || undefined });
169 onChange = (filters) => {
170 this.setState({ filters });
171 if (this.props.mutuallyExclusive) {
172 // Mutually exclusive filters apply immediately
173 const { onChange } = this.props;
179 // Non-mutually exclusive filters are debounced
184 submit = debounce (() => {
185 const { onChange } = this.props;
187 onChange(this.state.filters);
191 MountHandler = () => {
194 this.submit.cancel();
201 this.setState(prev => ({