CEC-4564: add visual nesting to CAN signal control (#406)

* add TrieSelect

* setup menu button

* CEC-4564: add trie select component

* add visual nesting

* remove unused imports
This commit is contained in:
Tristan Timblin
2023-08-02 15:44:08 -04:00
committed by GitHub
parent c118f676ee
commit 27ffdf01b0
7 changed files with 852 additions and 936 deletions

View File

@@ -1,10 +1,10 @@
import React, { useState } from "react";
import React from "react";
import {
Box,
Button,
Checkbox,
Chip,
Collapse,
Divider,
FormControlLabel,
List,
ListItem,
@@ -41,50 +41,19 @@ const TrieSelectList = ({
classification,
options,
}) => {
const { selected, setSelected, remove } = useTrieSelect();
const [open, setOpen] = useState(false);
const { selected, remove } = useTrieSelect();
const classes = useStyles();
const trie = new Trie(options);
const handleExpand = () => {
setOpen(open => !open);
};
const handleSelectAll = () => {
setSelected((selected) => {
if (selected.length === 0) {
return options;
}
return [];
})
}
return (
<>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Button onClick={handleExpand} variant="contained" color="primary" disabled={options.length === 0}>
{label}
</Button>
<FormControlLabel
label={`Select All ${options.length}`}
labelPlacement="start"
control={
<Checkbox
checked={selected.length === options.length}
onClick={handleSelectAll}
/>
}
<List>
<TrieSelectLevel
node={trie.getRoot()}
classification={classification}
label={label}
/>
</Box>
<Collapse in={open}>
<List>
<TrieSelectLevel
node={trie.getRoot()}
classification={classification}
/>
</List>
</Collapse>
</List>
<ul className={classes.chipList}>
{selected.map((signal) => {
return (
@@ -102,13 +71,18 @@ const TrieSelectLevel = ({
prefix = "",
node,
children,
classification
classification,
label,
level = -1,
}) => {
const classes = useStyles();
const { selected, add, remove } = useTrieSelect();
const [open, setOpen] = React.useState(false);
const completeChildren = Object.values(node.children).filter(child => child.isComplete);
const hasCompleteChildren = completeChildren.length > 0;
const descendantCount = `${node.count} ${classification}`;
const isParentOfMultiple = completeChildren.length > 1;
const isAdoptiveParentOfMultiple = completeChildren.length <= 1 && node.count > 1;
const handleExpand = () => {
setOpen(open => !open);
@@ -140,7 +114,9 @@ const TrieSelectLevel = ({
return Array.from(result);
}
const listItems = (
const indent = (level) => `${12 * level}px`;
const listItems = (level) => (
<>
{children}
{Object.values(node.children).map((child) => {
@@ -152,13 +128,16 @@ const TrieSelectLevel = ({
node={child}
key={fullName}
classification={classification}
level={level}
>
{child.isComplete && (
<ListItem className={classes.whiteBackground}>
<ListItemIcon>
<Checkbox checked={isChecked} onClick={() => handleCheck([fullName], isChecked)} />
</ListItemIcon>
<ListItemText primary={fullName} />
<Box sx={{ paddingLeft: indent(level), display: "flex", alignItems: "center" }}>
<ListItemIcon>
<Checkbox checked={isChecked} onClick={() => handleCheck([fullName], isChecked)} />
</ListItemIcon>
<ListItemText primary={fullName} />
</Box>
</ListItem>
)}
</TrieSelectLevel>
@@ -167,15 +146,15 @@ const TrieSelectLevel = ({
</>
);
const isParentOfMultiple = completeChildren.length > 1;
const isAdoptiveParentOfMultiple = completeChildren.length <= 1 && node.count > 1;
if (isParentOfMultiple || isAdoptiveParentOfMultiple) {
const allDescendants = getWords(node, prefix);
const isSelectAll = allDescendants.every(descendant => selected.includes(descendant));
return (
<>
<ListItem onClick={handleExpand} divider>
<ListItemText primary={prefix} secondary={descendantCount} />
<ListItem onClick={handleExpand} divider className={classes.defaultBackground}>
<Box sx={{ paddingLeft: indent(level) }}>
<ListItemText primary={prefix === "" ? label : prefix} secondary={descendantCount} />
</Box>
<ListItemSecondaryAction>
<Box sx={{ display: "flex", alignItems: "center", gap: "16px" }}>
<FormControlLabel
@@ -188,18 +167,19 @@ const TrieSelectLevel = ({
/>
}
/>
{open ? <ExpandLess /> : <ExpandMore />}
{open ? (<ExpandLess />) : (<ExpandMore />)}
</Box>
</ListItemSecondaryAction>
</ListItem>
<Collapse in={open}>
<List>
{listItems}
{listItems(level + 1)}
</List>
{hasCompleteChildren && <Divider />}
</Collapse>
</>
)
}
return listItems;
return listItems(level);
}

View File

@@ -38,7 +38,7 @@ describe("TrieSelect", () => {
it("properly passes payload to callback", async () => {
const mockCallback = jest.fn();
const { getByText } = render(
const { getAllByText } = render(
<TrieSelect
label={"The input label"}
classification="Signal"
@@ -47,7 +47,7 @@ describe("TrieSelect", () => {
/>
);
const selectAll = getByText("Select All 8");
const selectAll = getAllByText("Select All")[0];
userEvent.click(selectAll);
expect(mockCallback).toHaveBeenCalledWith(options);
});

View File

@@ -1 +1 @@
export { TrieSelect } from "./TrieSelect";
export * from "./TrieSelect";