1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-24 17:51:14 +02:00
unleash.unleash/frontend/src/component/feature/FeatureView/FeatureOverview/ManageTagsDialog/TagsInput.tsx
Thomas Heartman 3039fc3d59
fix: trying to create a tag that's too short gives errors (#7269)
1. Only suggest to create a tag value if the input is more than two
characters after trimming.
2. Ignore trailing and leading whitespace when considering which
autocomplete options to show
2024-06-05 08:47:50 +02:00

147 lines
4.5 KiB
TypeScript

import {
Autocomplete,
type AutocompleteProps,
Checkbox,
createFilterOptions,
type FilterOptionsState,
TextField,
} from '@mui/material';
import type React from 'react';
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
import CheckBoxIcon from '@mui/icons-material/CheckBox';
import IndeterminateCheckBoxIcon from '@mui/icons-material/IndeterminateCheckBox';
import type { ITag, ITagType } from 'interfaces/tags';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import Add from '@mui/icons-material/Add';
export type TagOption = {
title: string;
inputValue?: string;
};
interface ITagsInputProps {
options: TagOption[];
existingTags: ITag[];
tagType: ITagType;
selectedOptions: TagOption[];
indeterminateOptions?: TagOption[];
disabled?: boolean;
onChange: AutocompleteProps<TagOption, true, false, false>['onChange'];
}
const filter = createFilterOptions<TagOption>();
export const TagsInput = ({
options,
selectedOptions,
indeterminateOptions,
tagType,
existingTags,
disabled = false,
onChange,
}: ITagsInputProps) => {
const icon = <CheckBoxOutlineBlankIcon fontSize='small' />;
const getOptionLabel = (option: TagOption) => {
// Add "xxx" option created dynamically
if (option.inputValue) {
return option.inputValue;
}
// Regular option
return option.title;
};
const renderOption = (
props: JSX.IntrinsicAttributes &
React.ClassAttributes<HTMLLIElement> &
React.LiHTMLAttributes<HTMLLIElement>,
option: TagOption,
{ selected }: { selected: boolean },
) => {
const isIndeterminate =
indeterminateOptions?.some(
(indeterminateOption) =>
indeterminateOption.title === option.title,
) ?? false;
return (
<li {...props}>
<ConditionallyRender
condition={Boolean(option.inputValue)}
show={<Add sx={{ mr: (theme) => theme.spacing(0.5) }} />}
elseShow={
<Checkbox
icon={icon}
checkedIcon={<CheckBoxIcon fontSize='small' />}
indeterminateIcon={
<IndeterminateCheckBoxIcon fontSize='small' />
}
sx={{ mr: (theme) => theme.spacing(0.5) }}
checked={selected && !isIndeterminate}
indeterminate={isIndeterminate}
/>
}
/>
{option.title}
</li>
);
};
const filterOptions = (
options: TagOption[],
params: FilterOptionsState<TagOption>,
) => {
const inputValue = params.inputValue.trim();
const filtered = filter(options, {
...params,
inputValue,
});
// Suggest the creation of a new value
const isExisting = options.some(
(option) => inputValue === option.title,
);
if (inputValue.length >= 2 && !isExisting) {
filtered.push({
inputValue,
title: `Create new value "${inputValue}"`,
});
}
return filtered;
};
return (
<Autocomplete
multiple
id='checkboxes-tag'
sx={{ marginTop: (theme) => theme.spacing(2), width: 500 }}
disableCloseOnSelect
placeholder='Select Values'
options={options}
value={selectedOptions}
isOptionEqualToValue={(option, value) => {
if (value.inputValue && value.inputValue !== '') {
return option.title === value.inputValue;
} else {
return option.title === value.title;
}
}}
getOptionLabel={getOptionLabel}
renderOption={renderOption}
filterOptions={filterOptions}
ListboxProps={{ style: { maxHeight: 200, overflow: 'auto' } }}
onChange={onChange}
renderInput={(params) => (
<TextField
{...params}
label='Select values'
placeholder='Select values'
/>
)}
disabled={disabled}
/>
);
};