feat(core): select + checkboxtree style - handle changes

This commit is contained in:
Rémi DOREAU
2020-09-14 23:27:22 +02:00
parent b565c93537
commit a87d622a24
4 changed files with 194 additions and 155 deletions
@@ -65,5 +65,9 @@ export default {
};
export const Default = () => (
<CheckboxTree label="default" subCategories={CHECKBOX_TREE_ITEMS} />
<CheckboxTree
onChange={el => {}}
label="default"
subCategories={CHECKBOX_TREE_ITEMS}
/>
);
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useReducer } from 'react';
import React, { useEffect, useReducer } from 'react';
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';
import {
List,
@@ -22,7 +22,7 @@ import {
Checkbox,
ListItemText,
Collapse,
Typography
Typography,
} from '@material-ui/core';
import ExpandLess from '@material-ui/icons/ExpandLess';
import ExpandMore from '@material-ui/icons/ExpandMore';
@@ -38,17 +38,17 @@ const useStyles = makeStyles((theme: Theme) =>
minWidth: 10,
maxWidth: 360,
backgroundColor: 'transparent',
"&:hover": {
backgroundColor: "transparent"
}
'&:hover': {
backgroundColor: 'transparent',
},
},
nested: {
paddingLeft: theme.spacing(5),
height: '32px'
height: '32px',
},
listItemIcon: {
minWidth: 10
}
minWidth: 10,
},
}),
);
@@ -76,9 +76,10 @@ type Option = {
isChecked?: boolean;
};
type Props = {
export type CheckboxTreeProps = {
subCategories: SubCategory[];
label: string;
onChange: (arg: any) => any;
};
/* REDUCER */
@@ -168,7 +169,7 @@ const indexer = (
[el.label]: {
label: el.label,
isChecked: el.isChecked || false,
isOpen: true,
isOpen: false,
options: indexer(el.options),
},
};
@@ -179,7 +180,8 @@ const indexer = (
};
}, {});
export const CheckboxTree = (props: Props) => {
export const CheckboxTree = (props: CheckboxTreeProps) => {
const { onChange } = props;
const classes = useStyles();
const [state, dispatch] = useReducer(reducer, indexer(props.subCategories));
@@ -188,78 +190,99 @@ export const CheckboxTree = (props: Props) => {
event.stopPropagation();
dispatch({ type: 'openCategory', payload: value });
};
const handleChange = () => {
const values = Object.values(state).map(category => ({
category: category.label,
selected: Object.values(category.options)
.filter(option => option.isChecked)
.map(option => option.value),
}));
onChange(values);
};
return (
<div>
<Typography variant="button">{props.label}</Typography>
<List className={classes.root}>
{Object.values(state).map(item => {
const labelId = `checkbox-list-label-${item?.label}`;
<Typography variant="button">{props.label}</Typography>
<List className={classes.root}>
{Object.values(state).map(item => {
const labelId = `checkbox-list-label-${item?.label}`;
return (
<div key={item.label}>
<ListItem
dense
button
onClick={() =>
dispatch({
type: 'checkCategory',
payload: item.label,
})
}
>
<ListItemIcon className={classes.listItemIcon}>
<Checkbox
color="primary"
edge="start"
checked={item.isChecked}
tabIndex={-1}
disableRipple
inputProps={{ 'aria-labelledby': labelId }}
/>
</ListItemIcon>
<ListItemText id={labelId} primary={item.label} />
{item.options?.length ? (
<ExpandLess onClick={event => handleOpen(event, item.label)} />
) : (
<ExpandMore onClick={event => handleOpen(event, item.label)} />
)}
</ListItem>
<Collapse in={item.isOpen} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{Object.values(item.options).map(option => (
<ListItem
button
key={option.label}
className={classes.nested}
onClick={() =>
dispatch({
type: 'checkOption',
payload: {
subCategoryLabel: item.label,
optionLabel: option.label,
},
})
}
>
<ListItemIcon className={classes.listItemIcon}>
<Checkbox
color="primary"
edge="start"
checked={option.isChecked}
tabIndex={-1}
disableRipple
inputProps={{ 'aria-labelledby': labelId }}
return (
<div key={item.label}>
<ListItem
dense
button
onClick={async () => {
await dispatch({
type: 'checkCategory',
payload: item.label,
});
handleChange();
}}
>
<ListItemIcon className={classes.listItemIcon}>
<Checkbox
color="primary"
edge="start"
checked={item.isChecked}
tabIndex={-1}
disableRipple
inputProps={{ 'aria-labelledby': labelId }}
/>
</ListItemIcon>
<ListItemText id={labelId} primary={item.label} />
{Object.values(item.options).length ? (
<>
{item.isOpen ? (
<ExpandLess
onClick={event => handleOpen(event, item.label)}
/>
</ListItemIcon>
<ListItemText primary={option.label} />
</ListItem>
))}
</List>
</Collapse>
</div>
);
})}
</List>
) : (
<ExpandMore
onClick={event => handleOpen(event, item.label)}
/>
)}
</>
) : null}
</ListItem>
<Collapse in={item.isOpen} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{Object.values(item.options).map(option => (
<ListItem
button
key={option.label}
className={classes.nested}
onClick={async () => {
await dispatch({
type: 'checkOption',
payload: {
subCategoryLabel: item.label,
optionLabel: option.label,
},
});
handleChange();
}}
>
<ListItemIcon className={classes.listItemIcon}>
<Checkbox
color="primary"
edge="start"
checked={option.isChecked}
tabIndex={-1}
disableRipple
inputProps={{ 'aria-labelledby': labelId }}
/>
</ListItemIcon>
<ListItemText primary={option.label} />
</ListItem>
))}
</List>
</Collapse>
</div>
);
})}
</List>
</div>
);
};
@@ -38,7 +38,12 @@ const SELECT_ITEMS = [
];
export const Default = () => (
<Select placeholder="All results" label="Default" items={SELECT_ITEMS} />
<Select
onChange={el => {}}
placeholder="All results"
label="Default"
items={SELECT_ITEMS}
/>
);
export const Multiple = () => (
@@ -47,5 +52,6 @@ export const Multiple = () => (
label="Multiple"
items={SELECT_ITEMS}
multiple
onChange={el => {}}
/>
);
+80 -74
View File
@@ -63,8 +63,8 @@ const BootstrapInput = withStyles((theme: Theme) =>
const useStyles = makeStyles((theme: Theme) =>
createStyles({
formControl: {
margin: theme.spacing(1),
minWidth: 315,
margin: `${theme.spacing(1)} 0px`,
maxWidth: 300,
},
label: {
transform: 'initial',
@@ -84,6 +84,10 @@ const useStyles = makeStyles((theme: Theme) =>
margin: 2,
},
checkbox: {},
root: {
display: 'flex',
flexDirection: 'column',
},
}),
);
@@ -92,15 +96,16 @@ type Item = {
value: string | number;
};
type Props = {
export type SelectProps = {
multiple?: boolean;
items: Item[];
label: string;
placeholder?: string;
onChange: (arg: any) => any;
};
export const SelectComponent = (props: Props) => {
const { multiple, items, label, placeholder } = props;
export const SelectComponent = (props: SelectProps) => {
const { multiple, items, label, placeholder, onChange } = props;
const classes = useStyles();
const [value, setValue] = useState<any[] | string | number>(
multiple ? [] : '',
@@ -108,7 +113,8 @@ export const SelectComponent = (props: Props) => {
const [canOpen, setCanOpen] = React.useState(false);
const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
setValue(event.target.value as string);
setValue(event.target.value as any);
onChange(event.target.value);
};
const selectHandleOnOpen = () => {
@@ -120,74 +126,74 @@ export const SelectComponent = (props: Props) => {
};
return (
<FormControl className={classes.formControl}>
<InputLabel className={classes.label} disableAnimation id="select-label">
{label}
</InputLabel>
<Select
id="select"
value={value}
displayEmpty
multiple={multiple}
onChange={handleChange}
onClick={selectHandleOnOpen}
open={canOpen}
input={<BootstrapInput />}
renderValue={selected =>
multiple && (value as any[]).length !== 0 ? (
<div className={classes.chips}>
{(selected as string[]).map(selectedValue => (
<Chip
key={items.find(el => el.value === selectedValue)?.label}
label={items.find(el => el.value === selectedValue)?.label}
clickable
onDelete={handleDelete(selectedValue)}
className={classes.chip}
/>
))}
</div>
) : (
<Typography>
{(value as any[]).length === 0
? placeholder || ''
: items.find(el => el.value === selected)?.label}
</Typography>
)
}
IconComponent={() =>
!canOpen ? <ClosedDropdown /> : <OpenedDropdown />
}
MenuProps={{
anchorOrigin: {
vertical: 'bottom',
horizontal: 'left',
},
transformOrigin: {
vertical: 'top',
horizontal: 'left',
},
getContentAnchorEl: null,
}}
>
{placeholder && (
<MenuItem value="" disabled>
{placeholder}
</MenuItem>
)}
{items &&
items.map(item => (
<MenuItem key={item.value} value={item.value}>
{multiple && (
<Checkbox
color="primary"
checked={(value as any[]).includes(item.value) || false}
className={classes.checkbox}
/>
)}
{item.label}
<div className={classes.root}>
<Typography variant="button">{label}</Typography>
<FormControl className={classes.formControl}>
<Select
id="select"
value={value}
displayEmpty
multiple={multiple}
onChange={handleChange}
onClick={selectHandleOnOpen}
open={canOpen}
input={<BootstrapInput />}
renderValue={selected =>
multiple && (value as any[]).length !== 0 ? (
<div className={classes.chips}>
{(selected as string[]).map(selectedValue => (
<Chip
key={items.find(el => el.value === selectedValue)?.value}
label={items.find(el => el.value === selectedValue)?.label}
clickable
onDelete={handleDelete(selectedValue)}
className={classes.chip}
/>
))}
</div>
) : (
<Typography>
{(value as any[]).length === 0
? placeholder || ''
: items.find(el => el.value === selected)?.label}
</Typography>
)
}
IconComponent={() =>
!canOpen ? <ClosedDropdown /> : <OpenedDropdown />
}
MenuProps={{
anchorOrigin: {
vertical: 'bottom',
horizontal: 'left',
},
transformOrigin: {
vertical: 'top',
horizontal: 'left',
},
getContentAnchorEl: null,
}}
>
{placeholder && (
<MenuItem value="" disabled>
{placeholder}
</MenuItem>
))}
</Select>
</FormControl>
)}
{items &&
items.map(item => (
<MenuItem key={item.value} value={item.value}>
{multiple && (
<Checkbox
color="primary"
checked={(value as any[]).includes(item.value) || false}
className={classes.checkbox}
/>
)}
{item.label}
</MenuItem>
))}
</Select>
</FormControl>
</div>
);
};