1
0
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:
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) => { onChange: (value: FilterItem) => {
recordedChanges.push(value); recordedChanges.push(value);
}, },
onChipClose: () => {},
state: initialState, state: initialState,
}; };

View File

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

View File

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

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 { 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>
); );
}; };

View File

@ -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',

View File

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