1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-01 01:18:10 +02:00

feat: make env selector filterable (#9340)

Makes the env selector on the flag page act the same way as the env
selector on the new project page or any of the filterable buttons in the
new project/flag dialogs.

Also slightly changes the styles of the existing dropdown lists to bring
them in line with the new env selector (more padding, full-width
highlights).

Selector:


![image](https://github.com/user-attachments/assets/83875aa3-f9d1-4763-b8eb-75f7dc493b13)


Project/flag creation:
Before:

![image](https://github.com/user-attachments/assets/97926ec8-64a0-4d08-900b-0acd5709ef92)


After:


![image](https://github.com/user-attachments/assets/2616615f-3382-4183-a048-5ea4defc8fb2)

## Technical notes

I was a little unsure how best to share the padding/spacing styles
between the search field and popover at first (as was requested by UX).
The easiest way (and most compliant with how we do it today) was to
define the spacing in a variable and move the relevant components into
the same file.

However, I actually think that using a CSS variable (e.g.
`--popover-spacing`) would be "better" here, but we don't really use
them much, so I've left that out for now. That said, if you agree, I'd
be more than happy to use that instead 🙋🏼
This commit is contained in:
Thomas Heartman 2025-02-21 12:20:43 +01:00 committed by GitHub
parent 87a207024c
commit 1db97882c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 68 additions and 42 deletions

View File

@ -1,18 +1,4 @@
import { Popover, styled } from '@mui/material';
export const StyledDropdown = styled('div')(({ theme }) => ({
padding: theme.spacing(2),
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(1),
maxHeight: '70vh',
}));
export const StyledPopover = styled(Popover)(({ theme }) => ({
'& .MuiPaper-root': {
borderRadius: `${theme.shape.borderRadiusMedium}px`,
},
}));
import { styled } from '@mui/material';
export const ButtonLabel = styled('span', {
shouldForwardProp: (prop) => prop !== 'labelWidth',

View File

@ -1,14 +1,10 @@
import { v4 as uuidv4 } from 'uuid';
import { type FC, type ReactNode, useRef, type PropsWithChildren } from 'react';
import { Box, Button } from '@mui/material';
import {
StyledDropdown,
StyledPopover,
ButtonLabel,
StyledTooltipContent,
} from './ConfigButton.styles';
import { ButtonLabel, StyledTooltipContent } from './ConfigButton.styles';
import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver';
import { ScreenReaderOnly } from 'component/common/ScreenReaderOnly/ScreenReaderOnly';
import { StyledPopover } from './shared.styles';
export type ConfigButtonProps = {
button: {
@ -94,13 +90,12 @@ export const ConfigButton: FC<PropsWithChildren<ConfigButtonProps>> = ({
vertical: 'top',
horizontal: 'left',
}}
aria-describedby={descriptionId}
>
<ScreenReaderOnly>
<p id={descriptionId}>{description}</p>
</ScreenReaderOnly>
<StyledDropdown aria-describedby={descriptionId}>
{children}
</StyledDropdown>
{children}
</StyledPopover>
</>
);

View File

@ -1,8 +1,10 @@
import { Checkbox, ListItem, styled } from '@mui/material';
export const StyledListItem = styled(ListItem)(({ theme }) => ({
paddingLeft: theme.spacing(1),
paddingLeft: theme.spacing(2),
paddingBlock: theme.spacing(1),
cursor: 'pointer',
'&:hover, &:focus': {
backgroundColor: theme.palette.action.hover,
outline: 'none',

View File

@ -1,4 +1,4 @@
import { TextField, styled } from '@mui/material';
import { Popover, TextField, styled } from '@mui/material';
const visuallyHiddenStyles = {
border: 0,
@ -12,11 +12,27 @@ const visuallyHiddenStyles = {
whiteSpace: 'nowrap',
};
const dropdownPadding = 1.5;
export const StyledPopover = styled(Popover)(({ theme }) => ({
'& .MuiPaper-root': {
borderRadius: `${theme.shape.borderRadiusMedium}px`,
paddingInline: 0,
paddingTop: theme.spacing(dropdownPadding),
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(1),
maxHeight: '70vh',
},
}));
export const StyledDropdownSearch = styled(TextField, {
shouldForwardProp: (prop) => prop !== 'hideLabel',
})<{ hideLabel?: boolean }>(({ theme, hideLabel }) => ({
paddingInline: theme.spacing(dropdownPadding),
'& .MuiInputBase-root': {
padding: theme.spacing(0, 1.5),
paddingInline: theme.spacing(1.5),
borderRadius: `${theme.shape.borderRadiusMedium}px`,
},
'& .MuiInputBase-input': {

View File

@ -1,8 +1,10 @@
import { Button, Checkbox, Menu, MenuItem, styled } from '@mui/material';
import { Button, styled } from '@mui/material';
import { useState, type FC } from 'react';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import { DropdownList } from 'component/common/DialogFormTemplate/ConfigButtons/DropdownList';
import { StyledPopover } from 'component/common/DialogFormTemplate/ConfigButtons/shared.styles';
type EnvironmentVisibilityMenuProps = {
environments: Array<{ name: string }>;
@ -33,6 +35,18 @@ export const EnvironmentVisibilityMenu: FC<EnvironmentVisibilityMenuProps> = ({
setAnchorEl(null);
};
const allEnvironments = environments.map((environment) => environment.name);
const selectedOptions = new Set(
allEnvironments.filter(
(environment) => !hiddenEnvironments.includes(environment),
),
);
const handleToggle = (value: string) => {
onChange(value);
};
return (
<StyledContainer>
<Button
@ -40,30 +54,43 @@ export const EnvironmentVisibilityMenu: FC<EnvironmentVisibilityMenuProps> = ({
endIcon={isOpen ? <ExpandLessIcon /> : <ExpandMoreIcon />}
variant='outlined'
id={buttonId}
aria-controls={isOpen ? menuId : undefined}
aria-controls={menuId}
aria-haspopup='true'
aria-expanded={isOpen ? 'true' : undefined}
data-loading
>
Hide/show environments
</Button>
<Menu
<StyledPopover
id={menuId}
open={Boolean(anchorEl)}
anchorEl={anchorEl}
open={isOpen}
onClose={handleClose}
MenuListProps={{ 'aria-labelledby': buttonId }}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
>
{environments.map(({ name }) => (
<MenuItem key={name} onClick={() => onChange(name)}>
<Checkbox
onChange={() => onChange(name)}
checked={!hiddenEnvironments?.includes(name)}
/>
{name}
</MenuItem>
))}
</Menu>
<DropdownList
multiselect={{
selectedOptions,
}}
onChange={handleToggle}
options={allEnvironments.map((env) => ({
label: env,
value: env,
}))}
search={{
label: 'Filter environments',
placeholder: 'Filter environments',
}}
/>
</StyledPopover>
</StyledContainer>
);
};