//
// SPDX-License-Identifier: AGPL-3.0
-import * as React from "react";
+import React, { useEffect } from 'react';
import {
WithStyles,
withStyles,
CardActions,
Typography,
CardContent,
- Tooltip
-} from "@material-ui/core";
-import * as classnames from "classnames";
-import { DefaultTransformOrigin } from "~/components/popover/helpers";
-import { createTree } from '~/models/tree';
-import { DataTableFilters, DataTableFiltersTree } from "./data-table-filters-tree";
-import { getNodeDescendants } from '~/models/tree';
+ Tooltip,
+ IconButton,
+} from '@material-ui/core';
+import classnames from 'classnames';
+import { DefaultTransformOrigin } from 'components/popover/helpers';
+import { createTree } from 'models/tree';
+import { DataTableFilters, DataTableFiltersTree } from './data-table-filters-tree';
+import { getNodeDescendants } from 'models/tree';
+import debounce from 'lodash/debounce';
-export type CssRules = "root" | "icon" | "active" | "checkbox";
+export type CssRules = 'root' | 'icon' | 'iconButton' | 'active' | 'checkbox';
const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
root: {
- cursor: "pointer",
- display: "inline-flex",
- justifyContent: "flex-start",
- flexDirection: "inherit",
- alignItems: "center",
- "&:hover": {
+ cursor: 'pointer',
+ display: 'inline-flex',
+ justifyContent: 'flex-start',
+ flexDirection: 'inherit',
+ alignItems: 'center',
+ '&:hover': {
color: theme.palette.text.primary,
},
- "&:focus": {
+ '&:focus': {
color: theme.palette.text.primary,
},
},
active: {
color: theme.palette.text.primary,
- '& $icon': {
+ '& $iconButton': {
opacity: 1,
},
},
icon: {
- marginRight: 4,
- marginLeft: 4,
+ fontSize: 12,
+ userSelect: 'none',
+ width: 16,
+ height: 15,
+ marginTop: 1,
+ },
+ iconButton: {
+ color: theme.palette.text.primary,
opacity: 0.7,
- userSelect: "none",
- width: 16
},
checkbox: {
width: 24,
- height: 24
- }
+ height: 24,
+ },
});
+enum SelectionMode {
+ ALL = 'all',
+ NONE = 'none',
+}
+
export interface DataTableFilterProps {
name: string;
filters: DataTableFilters;
onChange?: (filters: DataTableFilters) => void;
+
+ /**
+ * When set to true, only one filter can be selected at a time.
+ */
+ mutuallyExclusive?: boolean;
+
+ /**
+ * By default `all` filters selection means that label should be grayed out.
+ * Use `none` when label is supposed to be grayed out when no filter is selected.
+ */
+ defaultSelection?: SelectionMode;
}
interface DataTableFilterState {
icon = React.createRef<HTMLElement>();
render() {
- const { name, classes, children } = this.props;
- const isActive = getNodeDescendants('')(this.state.filters).some(f => f.selected);
- return <>
- <Tooltip title='Filters'>
- <ButtonBase
- className={classnames([classes.root, { [classes.active]: isActive }])}
- component="span"
- onClick={this.open}
- disableRipple>
- {children}
- <i className={classnames(["fas fa-filter", classes.icon])}
- data-fa-transform="shrink-3"
- ref={this.icon} />
- </ButtonBase>
- </Tooltip>
- <Popover
- anchorEl={this.state.anchorEl}
- open={!!this.state.anchorEl}
- anchorOrigin={DefaultTransformOrigin}
- transformOrigin={DefaultTransformOrigin}
- onClose={this.cancel}>
- <Card>
- <CardContent>
- <Typography variant="caption">
- {name}
- </Typography>
- </CardContent>
- <DataTableFiltersTree
- filters={this.state.filters}
- onChange={filters => this.setState({ filters })} />
- <CardActions>
- <Button
- color="primary"
- variant="raised"
- size="small"
- onClick={this.submit}>
- Ok
- </Button>
- <Button
- color="primary"
- variant="outlined"
- size="small"
- onClick={this.cancel}>
- Cancel
- </Button>
- </CardActions >
- </Card>
- </Popover>
- </>;
+ const { name, classes, defaultSelection = SelectionMode.ALL, children } = this.props;
+ const isActive = getNodeDescendants('')(this.state.filters).some((f) => (defaultSelection === SelectionMode.ALL ? !f.selected : f.selected));
+ return (
+ <>
+ <Tooltip disableFocusListener title='Filters'>
+ <ButtonBase className={classnames([classes.root, { [classes.active]: isActive }])} component='span' onClick={this.open} disableRipple>
+ {children}
+ <IconButton component='span' classes={{ root: classes.iconButton }} tabIndex={-1}>
+ <i className={classnames(['fas fa-filter', classes.icon])} data-fa-transform='shrink-3' ref={this.icon} />
+ </IconButton>
+ </ButtonBase>
+ </Tooltip>
+ <Popover
+ anchorEl={this.state.anchorEl}
+ open={!!this.state.anchorEl}
+ anchorOrigin={DefaultTransformOrigin}
+ transformOrigin={DefaultTransformOrigin}
+ onClose={this.close}
+ >
+ <Card>
+ <CardContent>
+ <Typography variant='caption'>{name}</Typography>
+ </CardContent>
+ <DataTableFiltersTree filters={this.state.filters} mutuallyExclusive={this.props.mutuallyExclusive} onChange={this.onChange} />
+ <>
+ {this.props.mutuallyExclusive || (
+ <CardActions>
+ <Button color='primary' variant='outlined' size='small' onClick={this.close}>
+ Close
+ </Button>
+ </CardActions>
+ )}
+ </>
+ </Card>
+ </Popover>
+ <this.MountHandler />
+ </>
+ );
}
static getDerivedStateFromProps(props: DataTableFilterProps, state: DataTableFilterState): DataTableFilterState {
- return props.filters !== state.prevFilters
- ? { ...state, filters: props.filters, prevFilters: props.filters }
- : state;
+ return props.filters !== state.prevFilters ? { ...state, filters: props.filters, prevFilters: props.filters } : state;
}
open = () => {
this.setState({ anchorEl: this.icon.current || undefined });
- }
+ };
- submit = () => {
+ onChange = (filters) => {
+ this.setState({ filters });
+ if (this.props.mutuallyExclusive) {
+ // Mutually exclusive filters apply immediately
+ const { onChange } = this.props;
+ if (onChange) {
+ onChange(filters);
+ }
+ this.close();
+ } else {
+ // Non-mutually exclusive filters are debounced
+ this.submit();
+ }
+ };
+
+ submit = debounce(() => {
const { onChange } = this.props;
if (onChange) {
onChange(this.state.filters);
}
- this.setState({ anchorEl: undefined });
- }
+ }, 1000);
- cancel = () => {
- this.setState(prev => ({
+ MountHandler = () => {
+ useEffect(() => {
+ return () => {
+ this.submit.cancel();
+ };
+ }, []);
+ return null;
+ };
+
+ close = () => {
+ this.setState((prev) => ({
...prev,
- filters: prev.prevFilters,
- anchorEl: undefined
+ anchorEl: undefined,
}));
- }
-
- setFilters = (filters: DataTableFilters) => {
- this.setState({ filters });
- }
-
+ };
}
);