1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-05 17:53:12 +02:00

refactor: LabelFilterItem "select all" functionality (#10610)

This commit is contained in:
Tymoteusz Czech 2025-09-04 10:03:21 +02:00 committed by GitHub
parent 3b1592b329
commit e96f981816
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 106 additions and 100 deletions

View File

@ -3,17 +3,12 @@ import {
Autocomplete, Autocomplete,
Checkbox, Checkbox,
Chip, Chip,
FormControlLabel,
styled,
TextField, TextField,
Typography, Typography,
} from '@mui/material'; } from '@mui/material';
import { METRIC_LABELS_SELECT_ALL } from 'component/impact-metrics/hooks/useImpactMetricsState';
import type { FC } from 'react'; import type { FC } from 'react';
const StyledSelectAllLabel = styled('span')(({ theme }) => ({
fontSize: theme.fontSizes.smallBody,
}));
type LabelFilterItemProps = { type LabelFilterItemProps = {
labelKey: string; labelKey: string;
options: string[]; options: string[];
@ -28,106 +23,111 @@ export const LabelFilterItem: FC<LabelFilterItemProps> = ({
value, value,
onChange, onChange,
}) => { }) => {
const isAllSelected = value.includes('*'); const isAllSelected = value.includes(METRIC_LABELS_SELECT_ALL);
const autocompleteId = `autocomplete-${labelKey}`; const autocompleteId = `autocomplete-${labelKey}`;
const selectAllId = `select-all-${labelKey}`;
const isTruncated = options.length >= 1_000; const isTruncated = options.length >= 1_000;
return ( const optionsWithSelectAll = [METRIC_LABELS_SELECT_ALL, ...options];
<>
<FormControlLabel
sx={(theme) => ({
marginLeft: theme.spacing(0),
})}
control={
<Checkbox
id={selectAllId}
size='small'
checked={isAllSelected}
onChange={(e) =>
onChange(e.target.checked ? ['*'] : [])
}
inputProps={{
'aria-describedby': autocompleteId,
'aria-label': `Select all ${labelKey} options`,
}}
/>
}
label={<StyledSelectAllLabel>Select all</StyledSelectAllLabel>}
/>
<Autocomplete
multiple
id={autocompleteId}
options={options}
value={isAllSelected ? options : value}
onChange={(_, newValues) => {
onChange(newValues);
}}
disabled={isAllSelected}
renderTags={(value, getTagProps) => {
const overflowCount = 5;
const displayedValues = value.slice(-overflowCount);
const remainingCount = value.length - overflowCount;
return ( return (
<> <Autocomplete
{displayedValues.map((option, index) => { multiple
const { key, ...chipProps } = getTagProps({ disableCloseOnSelect
index, id={autocompleteId}
}); options={optionsWithSelectAll}
return ( value={isAllSelected ? options : value}
<Chip getOptionLabel={(option) =>
{...chipProps} option === METRIC_LABELS_SELECT_ALL ? '(Select all)' : option
key={key} }
label={option} onChange={(_, newValues, reason, details) => {
size='small' if (details?.option === METRIC_LABELS_SELECT_ALL) {
/> onChange(isAllSelected ? [] : [METRIC_LABELS_SELECT_ALL]);
); return;
})} }
{remainingCount > 0 ? ( onChange(
<Typography newValues.filter((v) => v !== METRIC_LABELS_SELECT_ALL),
component='span' );
sx={{ color: 'text.secondary' }} }}
> renderOption={(props, option, { selected }) => (
{' '} <li {...props}>
(+{remainingCount}) <Checkbox
</Typography> size='small'
) : null} checked={
</> option === METRIC_LABELS_SELECT_ALL
); ? isAllSelected
}} : selected
renderInput={(params) => ( }
style={{ marginRight: 8 }}
/>
{option === METRIC_LABELS_SELECT_ALL ? (
<Typography
component='span'
sx={{ color: 'text.secondary' }}
>
Select all
</Typography>
) : (
option
)}
</li>
)}
renderTags={(value, getTagProps) => {
const overflowCount = 5;
const displayedValues = value.slice(-overflowCount);
const remainingCount = value.length - overflowCount;
return (
<> <>
<TextField {displayedValues.map((option, index) => {
{...params} const { key, ...chipProps } = getTagProps({
label={labelKey} index,
placeholder={ });
isAllSelected ? undefined : 'Select values…' return (
} <Chip
variant='outlined' {...chipProps}
size='small' key={key}
inputProps={{ label={option}
...params.inputProps, size='small'
'aria-describedby': isAllSelected />
? `${selectAllId}-description` );
: undefined, })}
}} {remainingCount > 0 ? (
/> <Typography
{isTruncated && ( component='span'
<Alert sx={{ color: 'text.secondary' }}
severity='warning'
sx={(theme) => ({
padding: theme.spacing(1, 2),
marginTop: theme.spacing(1),
})}
> >
Maximum of 1000 values loaded due to {' '}
performance. (+{remainingCount})
</Alert> </Typography>
)} ) : null}
</> </>
)} );
/> }}
</> renderInput={(params) => (
<>
<TextField
{...params}
label={labelKey}
placeholder={
isAllSelected ? undefined : 'Select values…'
}
variant='outlined'
size='small'
inputProps={{ ...params.inputProps }}
/>
{isTruncated && (
<Alert
severity='warning'
sx={(theme) => ({
padding: theme.spacing(1, 2),
marginTop: theme.spacing(1),
})}
>
Maximum of 1000 values loaded due to performance.
</Alert>
)}
</>
)}
/>
); );
}; };

View File

@ -3,6 +3,12 @@ import { useImpactMetricsSettings } from 'hooks/api/getters/useImpactMetricsSett
import { useImpactMetricsSettingsApi } from 'hooks/api/actions/useImpactMetricsSettingsApi/useImpactMetricsSettingsApi.js'; import { useImpactMetricsSettingsApi } from 'hooks/api/actions/useImpactMetricsSettingsApi/useImpactMetricsSettingsApi.js';
import type { ChartConfig, ImpactMetricsState, LayoutItem } from '../types.ts'; import type { ChartConfig, ImpactMetricsState, LayoutItem } from '../types.ts';
/**
* "Select all" represents all current and future labels.
* Asterisk (*) is sent to the backend. This will create a different query then sending every current label.
*/
export const METRIC_LABELS_SELECT_ALL = '*';
export const useImpactMetricsState = () => { export const useImpactMetricsState = () => {
const { const {
settings, settings,