//
// SPDX-License-Identifier: AGPL-3.0
-import * as React from "react";
-import { Tree, toggleNodeSelection, getNode, initTreeNode, getNodeChildrenIds } from '~/models/tree';
-import { Tree as TreeComponent, TreeItem, TreeItemStatus } from '~/components/tree/tree';
+import React from "react";
+import { Tree, toggleNodeSelection, getNode, initTreeNode, getNodeChildrenIds, selectNode, deselectNodes } from 'models/tree';
+import { Tree as TreeComponent, TreeItem, TreeItemStatus } from 'components/tree/tree';
import { noop, map } from "lodash/fp";
-import { toggleNodeCollapse } from '~/models/tree';
-import { countNodes, countChildren } from '~/models/tree';
+import { toggleNodeCollapse } from 'models/tree';
+import { countNodes, countChildren } from 'models/tree';
export interface DataTableFilterItem {
name: string;
export interface DataTableFilterProps {
filters: DataTableFilters;
onChange?: (filters: DataTableFilters) => void;
+
+ /**
+ * When set to true, only one filter can be selected at a time.
+ */
+ mutuallyExclusive?: boolean;
}
export class DataTableFiltersTree extends React.Component<DataTableFilterProps> {
levelIndentation={hasSubfilters ? 20 : 0}
itemRightPadding={20}
items={filtersToTree(filters)}
- render={renderItem}
+ render={this.props.mutuallyExclusive ? renderRadioItem : renderItem}
showSelection
+ useRadioButtons={this.props.mutuallyExclusive}
disableRipple
onContextMenu={noop}
- toggleItemActive={noop}
+ toggleItemActive={
+ this.props.mutuallyExclusive
+ ? this.toggleRadioButtonFilter
+ : this.toggleFilter
+ }
toggleItemOpen={this.toggleOpen}
- toggleItemSelection={this.toggleFilter}
/>;
}
+ /**
+ * Handler for when a tree item is toggled via a radio button.
+ * Ensures mutual exclusivity among filter tree items.
+ */
+ toggleRadioButtonFilter = (_: any, item: TreeItem<DataTableFilterItem>) => {
+ const { onChange = noop } = this.props;
+
+ // If the filter is already selected, do nothing.
+ if (item.selected) { return; }
+
+ // Otherwise select this node and deselect the others
+ const filters = selectNode(item.id)(this.props.filters);
+ const toDeselect = Object.keys(this.props.filters).filter((id) => (id !== item.id));
+ onChange(deselectNodes(toDeselect)(filters));
+ }
+
toggleFilter = (_: React.MouseEvent, item: TreeItem<DataTableFilterItem>) => {
const { onChange = noop } = this.props;
onChange(toggleNodeSelection(item.id)(this.props.filters));
}
const renderItem = (item: TreeItem<DataTableFilterItem>) =>
- <span>{item.data.name}</span>;
+ <span>
+ {item.data.name}
+ {item.initialState !== item.selected ? <>
+ *
+ </> : null}
+ </span>;
+
+const renderRadioItem = (item: TreeItem<DataTableFilterItem>) =>
+ <span>
+ {item.data.name}
+ </span>;
const filterToTreeItem = (filters: DataTableFilters) =>
(id: string): TreeItem<any> => {
const node = getNode(id)(filters) || initTreeNode({ id: '', value: 'InvalidNode' });
const items = getNodeChildrenIds(node.id)(filters)
.map(filterToTreeItem(filters));
+ const isIndeterminate = !node.selected && items.some(i => i.selected || i.indeterminate);
return {
active: node.active,
items: items.length > 0 ? items : undefined,
open: node.expanded,
selected: node.selected,
+ initialState: node.initialState,
+ indeterminate: isIndeterminate,
status: TreeItemStatus.LOADED,
};
};