feat(core): select + checkboxtree style - handle changes
This commit is contained in:
@@ -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 => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user