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:
parent
3b1592b329
commit
e96f981816
@ -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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user