![Jon Koops](/assets/img/avatar_default.png)
Closes #21345 Closes #21344 Signed-off-by: Jon Koops <jonkoops@gmail.com> Co-authored-by: Erik Jan de Wit <erikjan.dewit@gmail.com> Co-authored-by: Mark Franceschelli <mfrances@redhat.com> Co-authored-by: Hynek Mlnařík <hmlnarik@redhat.com> Co-authored-by: Agnieszka Gancarczyk <agancarc@redhat.com>
91 lines
2.6 KiB
TypeScript
91 lines
2.6 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
|
|
import { TreeView, TreeViewDataItem } from "@patternfly/react-core";
|
|
|
|
type CheckableTreeViewProps = {
|
|
data: TreeViewDataItem[];
|
|
onSelect: (items: TreeViewDataItem[]) => void;
|
|
};
|
|
|
|
export const CheckableTreeView = ({
|
|
data,
|
|
onSelect,
|
|
}: CheckableTreeViewProps) => {
|
|
const [state, setState] = useState<{
|
|
options: TreeViewDataItem[];
|
|
checkedItems: TreeViewDataItem[];
|
|
}>({ options: [], checkedItems: [] });
|
|
|
|
useEffect(() => {
|
|
onSelect(state.checkedItems.filter((i) => i.checkProps?.checked === true));
|
|
}, [state]);
|
|
|
|
useEffect(() => {
|
|
setState({ options: data, checkedItems: [] });
|
|
}, [data]);
|
|
|
|
const flattenTree = (tree: TreeViewDataItem[]) => {
|
|
let result: TreeViewDataItem[] = [];
|
|
tree.forEach((item) => {
|
|
result.push(item);
|
|
if (item.children) {
|
|
result = result.concat(flattenTree(item.children));
|
|
}
|
|
});
|
|
return result;
|
|
};
|
|
|
|
const onCheck = (evt: React.ChangeEvent, treeViewItem: TreeViewDataItem) => {
|
|
const checked = (evt.target as HTMLInputElement).checked;
|
|
const flatCheckedItems = flattenTree([treeViewItem]);
|
|
|
|
setState((prevState) => {
|
|
return {
|
|
options: prevState.options,
|
|
checkedItems: checked
|
|
? prevState.checkedItems.concat(
|
|
flatCheckedItems.filter(
|
|
(item) => !prevState.checkedItems.some((i) => i.id === item.id),
|
|
),
|
|
)
|
|
: prevState.checkedItems.filter(
|
|
(item) => !flatCheckedItems.some((i) => i.id === item.id),
|
|
),
|
|
};
|
|
});
|
|
};
|
|
|
|
const isChecked = (item: TreeViewDataItem) =>
|
|
state.checkedItems.some((i) => i.id === item.id);
|
|
|
|
const areSomeDescendantsChecked = (dataItem: TreeViewDataItem): boolean =>
|
|
dataItem.children
|
|
? dataItem.children.some((child) => areSomeDescendantsChecked(child))
|
|
: isChecked(dataItem);
|
|
|
|
const mapTree = (item: TreeViewDataItem): TreeViewDataItem => {
|
|
const hasCheck = isChecked(item);
|
|
// Reset checked properties to be updated
|
|
item.checkProps!.checked = false;
|
|
|
|
if (hasCheck) {
|
|
item.checkProps!.checked = true;
|
|
} else {
|
|
const hasPartialCheck = areSomeDescendantsChecked(item);
|
|
if (hasPartialCheck) {
|
|
item.checkProps!.checked = null;
|
|
}
|
|
}
|
|
|
|
if (item.children) {
|
|
return {
|
|
...item,
|
|
children: item.children.map((child) => mapTree(child)),
|
|
};
|
|
}
|
|
return item;
|
|
};
|
|
|
|
const mapped = state.options.map((item) => mapTree(item));
|
|
return <TreeView data={mapped} onCheck={onCheck} hasCheckboxes />;
|
|
};
|