1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07: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:
Jaanus Sellin 2023-12-06 12:50:33 +02:00 committed by GitHub
parent 12f79f90bb
commit b8fabbd726
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 175 additions and 20 deletions

View File

@ -17,6 +17,7 @@ const setup = (initialState: FilterItem) => {
onChange: (value: FilterItem) => {
recordedChanges.push(value);
},
onChipClose: () => {},
state: initialState,
};

View File

@ -9,11 +9,16 @@ import {
StyledTextField,
} from './FilterItem.styles';
import { FilterItemChip } from './FilterItemChip/FilterItemChip';
import {
FeatureTogglesListFilters,
IFilterItem,
} from '../../feature/FeatureToggleList/FeatureToggleFilters/FeatureToggleFilters';
interface IFilterItemProps {
label: string;
options: Array<{ label: string; value: string }>;
onChange: (value: FilterItem) => void;
onChipClose: () => void;
state: FilterItem | null | undefined;
}
@ -29,6 +34,7 @@ export const FilterItem: FC<IFilterItemProps> = ({
label,
options,
onChange,
onChipClose,
state,
}) => {
const ref = useRef<HTMLDivElement>(null);
@ -52,6 +58,7 @@ export const FilterItem: FC<IFilterItemProps> = ({
const onDelete = () => {
onChange({ operator: 'IS', values: [] });
onClose();
onChipClose();
};
const handleToggle = (value: string) => () => {
@ -84,7 +91,6 @@ export const FilterItem: FC<IFilterItemProps> = ({
});
}
}, [state]);
return (
<>
<Box ref={ref}>

View File

@ -105,7 +105,7 @@ export const FilterItemChip: FC<IFilterItemChipProps> = ({
)}
/>
<ConditionallyRender
condition={Boolean(onDelete && hasSelectedOptions)}
condition={Boolean(onDelete)}
show={() => (
<StyledIconButton
aria-label='delete'

View File

@ -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;

View File

@ -1,11 +1,19 @@
import { VFC } from 'react';
import { Box } from '@mui/material';
import { useEffect, useState, VFC } from 'react';
import { Box, styled } from '@mui/material';
import { FilterItem } from 'component/common/FilterItem/FilterItem';
import useProjects from 'hooks/api/getters/useProjects/useProjects';
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 = {
project: FilterItem | null | undefined;
project?: FilterItem | null | undefined;
state?: FilterItem | null | undefined;
};
interface IFeatureToggleFiltersProps {
@ -13,29 +21,96 @@ interface IFeatureToggleFiltersProps {
onChange: (value: FeatureTogglesListFilters) => void;
}
export interface IFilterItem {
label: string;
options: {
label: string;
value: string;
}[];
filterKey: keyof FeatureTogglesListFilters;
enabled?: boolean;
}
export const FeatureToggleFilters: VFC<IFeatureToggleFiltersProps> = ({
state,
onChange,
}) => {
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 (
<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
condition={projectsOptions.length > 1}
show={() => (
<FilterItem
label='Project'
state={state.project}
options={projectsOptions}
onChange={(value) => onChange({ project: value })}
condition={availableFilters.some((filter) => !filter.enabled)}
show={
<AddFilterButton
availableFilters={availableFilters}
setAvailableFilters={setAvailableFilters}
/>
)}
}
/>
</Box>
</StyledBox>
);
};

View File

@ -81,6 +81,7 @@ export const FeatureToggleListTable: VFC = () => {
sortBy: withDefault(StringParam, 'createdAt'),
sortOrder: withDefault(StringParam, 'desc'),
project: FilterItemParam,
state: FilterItemParam,
};
const [tableState, setTableState] = usePersistentTableState(
'features-list-table',

View File

@ -37,7 +37,7 @@ export type FilterItem = {
const encodeFilterItem = (
filterItem: FilterItem | null | undefined,
): string | undefined => {
return filterItem && filterItem.values.length
return filterItem?.values.length
? `${filterItem.operator}:${filterItem.values.join(',')}`
: undefined;
};