mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-31 00:16:47 +01:00
feat: add new filter button with state (#5556)
[Screencast from 2023-12-05 16-59-28.webm](https://github.com/Unleash/unleash/assets/964450/793c771b-6246-4e28-8c13-920696a48bd5) --------- Co-authored-by: kwasniew <kwasniewski.mateusz@gmail.com>
This commit is contained in:
parent
12f79f90bb
commit
b8fabbd726
@ -17,6 +17,7 @@ const setup = (initialState: FilterItem) => {
|
|||||||
onChange: (value: FilterItem) => {
|
onChange: (value: FilterItem) => {
|
||||||
recordedChanges.push(value);
|
recordedChanges.push(value);
|
||||||
},
|
},
|
||||||
|
onChipClose: () => {},
|
||||||
state: initialState,
|
state: initialState,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,11 +9,16 @@ import {
|
|||||||
StyledTextField,
|
StyledTextField,
|
||||||
} from './FilterItem.styles';
|
} from './FilterItem.styles';
|
||||||
import { FilterItemChip } from './FilterItemChip/FilterItemChip';
|
import { FilterItemChip } from './FilterItemChip/FilterItemChip';
|
||||||
|
import {
|
||||||
|
FeatureTogglesListFilters,
|
||||||
|
IFilterItem,
|
||||||
|
} from '../../feature/FeatureToggleList/FeatureToggleFilters/FeatureToggleFilters';
|
||||||
|
|
||||||
interface IFilterItemProps {
|
interface IFilterItemProps {
|
||||||
label: string;
|
label: string;
|
||||||
options: Array<{ label: string; value: string }>;
|
options: Array<{ label: string; value: string }>;
|
||||||
onChange: (value: FilterItem) => void;
|
onChange: (value: FilterItem) => void;
|
||||||
|
onChipClose: () => void;
|
||||||
state: FilterItem | null | undefined;
|
state: FilterItem | null | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,6 +34,7 @@ export const FilterItem: FC<IFilterItemProps> = ({
|
|||||||
label,
|
label,
|
||||||
options,
|
options,
|
||||||
onChange,
|
onChange,
|
||||||
|
onChipClose,
|
||||||
state,
|
state,
|
||||||
}) => {
|
}) => {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
@ -52,6 +58,7 @@ export const FilterItem: FC<IFilterItemProps> = ({
|
|||||||
const onDelete = () => {
|
const onDelete = () => {
|
||||||
onChange({ operator: 'IS', values: [] });
|
onChange({ operator: 'IS', values: [] });
|
||||||
onClose();
|
onClose();
|
||||||
|
onChipClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggle = (value: string) => () => {
|
const handleToggle = (value: string) => () => {
|
||||||
@ -84,7 +91,6 @@ export const FilterItem: FC<IFilterItemProps> = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [state]);
|
}, [state]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box ref={ref}>
|
<Box ref={ref}>
|
||||||
|
@ -105,7 +105,7 @@ export const FilterItemChip: FC<IFilterItemChipProps> = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(onDelete && hasSelectedOptions)}
|
condition={Boolean(onDelete)}
|
||||||
show={() => (
|
show={() => (
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
aria-label='delete'
|
aria-label='delete'
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import Menu from '@mui/material/Menu';
|
||||||
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
|
import { IFilterItem } from './FeatureToggleFilters';
|
||||||
|
import { Box, styled } from '@mui/material';
|
||||||
|
import { Add } from '@mui/icons-material';
|
||||||
|
|
||||||
|
const StyledButton = styled(Button)(({ theme }) => ({
|
||||||
|
margin: theme.spacing(-1, 0, -1, 0),
|
||||||
|
padding: theme.spacing(1.25),
|
||||||
|
}));
|
||||||
|
interface IAddFilterButtonProps {
|
||||||
|
availableFilters: IFilterItem[];
|
||||||
|
setAvailableFilters: (filters: IFilterItem[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddFilterButton = ({
|
||||||
|
availableFilters,
|
||||||
|
setAvailableFilters,
|
||||||
|
}: IAddFilterButtonProps) => {
|
||||||
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
|
|
||||||
|
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
const handleClose = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClick = (label: string) => {
|
||||||
|
const filters = availableFilters.map((filter) =>
|
||||||
|
filter.label === label
|
||||||
|
? {
|
||||||
|
...filter,
|
||||||
|
enabled: true,
|
||||||
|
}
|
||||||
|
: filter,
|
||||||
|
);
|
||||||
|
setAvailableFilters(filters);
|
||||||
|
handleClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<StyledButton onClick={handleClick} startIcon={<Add />}>
|
||||||
|
Add Filter
|
||||||
|
</StyledButton>
|
||||||
|
<Menu
|
||||||
|
id='simple-menu'
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
keepMounted
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={handleClose}
|
||||||
|
>
|
||||||
|
{availableFilters.map(
|
||||||
|
(filter) =>
|
||||||
|
!filter.enabled && (
|
||||||
|
<MenuItem
|
||||||
|
key={filter.label}
|
||||||
|
onClick={() => onClick(filter.label)}
|
||||||
|
>
|
||||||
|
{filter.label}
|
||||||
|
</MenuItem>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddFilterButton;
|
@ -1,11 +1,19 @@
|
|||||||
import { VFC } from 'react';
|
import { useEffect, useState, VFC } from 'react';
|
||||||
import { Box } from '@mui/material';
|
import { Box, styled } from '@mui/material';
|
||||||
import { FilterItem } from 'component/common/FilterItem/FilterItem';
|
import { FilterItem } from 'component/common/FilterItem/FilterItem';
|
||||||
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import AddFilterButton from './AddFilterButton';
|
||||||
|
|
||||||
|
const StyledBox = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
padding: theme.spacing(2, 3),
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
export type FeatureTogglesListFilters = {
|
export type FeatureTogglesListFilters = {
|
||||||
project: FilterItem | null | undefined;
|
project?: FilterItem | null | undefined;
|
||||||
|
state?: FilterItem | null | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IFeatureToggleFiltersProps {
|
interface IFeatureToggleFiltersProps {
|
||||||
@ -13,29 +21,96 @@ interface IFeatureToggleFiltersProps {
|
|||||||
onChange: (value: FeatureTogglesListFilters) => void;
|
onChange: (value: FeatureTogglesListFilters) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IFilterItem {
|
||||||
|
label: string;
|
||||||
|
options: {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}[];
|
||||||
|
filterKey: keyof FeatureTogglesListFilters;
|
||||||
|
enabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export const FeatureToggleFilters: VFC<IFeatureToggleFiltersProps> = ({
|
export const FeatureToggleFilters: VFC<IFeatureToggleFiltersProps> = ({
|
||||||
state,
|
state,
|
||||||
onChange,
|
onChange,
|
||||||
}) => {
|
}) => {
|
||||||
const { projects } = useProjects();
|
const { projects } = useProjects();
|
||||||
const projectsOptions = (projects || []).map((project) => ({
|
|
||||||
label: project.name,
|
|
||||||
value: project.id,
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
const stateOptions = [
|
||||||
|
{
|
||||||
|
label: 'Active',
|
||||||
|
value: 'active',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Stale',
|
||||||
|
value: 'stale',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const defaultFilterItems: IFilterItem[] = [
|
||||||
|
{
|
||||||
|
label: 'State',
|
||||||
|
options: stateOptions,
|
||||||
|
filterKey: 'state',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const [availableFilters, setAvailableFilters] =
|
||||||
|
useState<IFilterItem[]>(defaultFilterItems);
|
||||||
|
const removeFilter = (label: string) => {
|
||||||
|
const filters = availableFilters.map((filter) =>
|
||||||
|
filter.label === label
|
||||||
|
? {
|
||||||
|
...filter,
|
||||||
|
enabled: false,
|
||||||
|
}
|
||||||
|
: filter,
|
||||||
|
);
|
||||||
|
setAvailableFilters(filters);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const projectsOptions = (projects || []).map((project) => ({
|
||||||
|
label: project.name,
|
||||||
|
value: project.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const newFilterItems = [
|
||||||
|
...defaultFilterItems,
|
||||||
|
{
|
||||||
|
label: 'Project',
|
||||||
|
options: projectsOptions,
|
||||||
|
filterKey: 'project',
|
||||||
|
} as const,
|
||||||
|
];
|
||||||
|
|
||||||
|
setAvailableFilters(newFilterItems);
|
||||||
|
}, [JSON.stringify(projects)]);
|
||||||
return (
|
return (
|
||||||
<Box sx={(theme) => ({ padding: theme.spacing(2, 3) })}>
|
<StyledBox>
|
||||||
|
{availableFilters.map(
|
||||||
|
(filter) =>
|
||||||
|
filter.enabled && (
|
||||||
|
<FilterItem
|
||||||
|
key={filter.label}
|
||||||
|
label={filter.label}
|
||||||
|
state={state[filter.filterKey]}
|
||||||
|
options={filter.options}
|
||||||
|
onChange={(value) =>
|
||||||
|
onChange({ [filter.filterKey]: value })
|
||||||
|
}
|
||||||
|
onChipClose={() => removeFilter(filter.label)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)}
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={projectsOptions.length > 1}
|
condition={availableFilters.some((filter) => !filter.enabled)}
|
||||||
show={() => (
|
show={
|
||||||
<FilterItem
|
<AddFilterButton
|
||||||
label='Project'
|
availableFilters={availableFilters}
|
||||||
state={state.project}
|
setAvailableFilters={setAvailableFilters}
|
||||||
options={projectsOptions}
|
|
||||||
onChange={(value) => onChange({ project: value })}
|
|
||||||
/>
|
/>
|
||||||
)}
|
}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</StyledBox>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -81,6 +81,7 @@ export const FeatureToggleListTable: VFC = () => {
|
|||||||
sortBy: withDefault(StringParam, 'createdAt'),
|
sortBy: withDefault(StringParam, 'createdAt'),
|
||||||
sortOrder: withDefault(StringParam, 'desc'),
|
sortOrder: withDefault(StringParam, 'desc'),
|
||||||
project: FilterItemParam,
|
project: FilterItemParam,
|
||||||
|
state: FilterItemParam,
|
||||||
};
|
};
|
||||||
const [tableState, setTableState] = usePersistentTableState(
|
const [tableState, setTableState] = usePersistentTableState(
|
||||||
'features-list-table',
|
'features-list-table',
|
||||||
|
@ -37,7 +37,7 @@ export type FilterItem = {
|
|||||||
const encodeFilterItem = (
|
const encodeFilterItem = (
|
||||||
filterItem: FilterItem | null | undefined,
|
filterItem: FilterItem | null | undefined,
|
||||||
): string | undefined => {
|
): string | undefined => {
|
||||||
return filterItem && filterItem.values.length
|
return filterItem?.values.length
|
||||||
? `${filterItem.operator}:${filterItem.values.join(',')}`
|
? `${filterItem.operator}:${filterItem.values.join(',')}`
|
||||||
: undefined;
|
: undefined;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user