1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

fix: make config dropdown list generic over values (#7676)

This PR makes the config dropdown list generic over its values, so that
you can pass stuff that isn't strings.

It also updates the existing impression data button to use booleans
instead.
This commit is contained in:
Thomas Heartman 2024-07-26 11:38:29 +02:00 committed by GitHub
parent b55d6f46d0
commit c0d7be040d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 42 additions and 47 deletions

View File

@ -1,18 +1,16 @@
import Search from '@mui/icons-material/Search';
import { type FC, useRef, useState } from 'react';
import { useRef, useState } from 'react';
import { InputAdornment, List, ListItemText } from '@mui/material';
import { StyledDropdownSearch } from './shared.styles';
import { StyledCheckbox, StyledListItem } from './DropdownList.styles';
const useSelectionManagement = (
handleToggle: (value: string) => () => void,
) => {
function useSelectionManagement<T>(handleToggle: (value: T) => () => void) {
const listRefs = useRef<Array<HTMLInputElement | HTMLLIElement | null>>([]);
const handleSelection = (
event: React.KeyboardEvent,
index: number,
filteredOptions: { label: string; value: string }[],
filteredOptions: { label: string; value: T }[],
) => {
// we have to be careful not to prevent other keys e.g tab
if (event.key === 'ArrowDown' && index < listRefs.current.length - 1) {
@ -45,32 +43,32 @@ const useSelectionManagement = (
};
return { listRefs, handleSelection };
};
}
export type DropdownListProps = {
options: Array<{ label: string; value: string }>;
onChange: (value: string) => void;
export type DropdownListProps<T> = {
options: Array<{ label: string; value: T }>;
onChange: (value: T) => void;
search: {
label: string;
placeholder: string;
};
multiselect?: { selectedOptions: Set<string> };
multiselect?: { selectedOptions: Set<T> };
};
export const DropdownList: FC<DropdownListProps> = ({
export function DropdownList<T = string>({
options,
onChange,
search,
multiselect,
}) => {
}: DropdownListProps<T>) {
const [searchText, setSearchText] = useState('');
const onSelection = (selected: string) => {
const onSelection = (selected: T) => {
onChange(selected);
};
const { listRefs, handleSelection } = useSelectionManagement(
(selected: string) => () => onSelection(selected),
(selected: T) => () => onSelection(selected),
);
const filteredOptions = options?.filter((option) =>
@ -109,7 +107,7 @@ export const DropdownList: FC<DropdownListProps> = ({
return (
<StyledListItem
aria-describedby={labelId}
key={option.value}
key={`${option.label}@index`}
dense
disablePadding
tabIndex={0}
@ -148,4 +146,4 @@ export const DropdownList: FC<DropdownListProps> = ({
</List>
</>
);
};
}

View File

@ -1,24 +1,24 @@
import { type FC, useState } from 'react';
import { useState } from 'react';
import { ConfigButton, type ConfigButtonProps } from './ConfigButton';
import { DropdownList, type DropdownListProps } from './DropdownList';
type MultiSelectConfigButtonProps = Pick<
type MultiSelectConfigButtonProps<T> = Pick<
ConfigButtonProps,
'button' | 'onOpen' | 'onClose' | 'description' | 'tooltip'
> &
Pick<DropdownListProps, 'search' | 'options'> & {
selectedOptions: Set<string>;
onChange: (values: Set<string>) => void;
Pick<DropdownListProps<T>, 'search' | 'options'> & {
selectedOptions: Set<T>;
onChange: (values: Set<T>) => void;
};
export const MultiSelectConfigButton: FC<MultiSelectConfigButtonProps> = ({
export function MultiSelectConfigButton<T = string>({
selectedOptions,
onChange,
...rest
}) => {
}: MultiSelectConfigButtonProps<T>) {
const [anchorEl, setAnchorEl] = useState<HTMLDivElement | null>();
const handleToggle = (value: string) => {
const handleToggle = (value: T) => {
if (selectedOptions.has(value)) {
selectedOptions.delete(value);
} else {
@ -30,7 +30,7 @@ export const MultiSelectConfigButton: FC<MultiSelectConfigButtonProps> = ({
return (
<ConfigButton {...rest} anchorEl={anchorEl} setAnchorEl={setAnchorEl}>
<DropdownList
<DropdownList<T>
multiselect={{
selectedOptions,
}}
@ -39,4 +39,4 @@ export const MultiSelectConfigButton: FC<MultiSelectConfigButtonProps> = ({
/>
</ConfigButton>
);
};
}

View File

@ -1,21 +1,21 @@
import { type FC, useState } from 'react';
import { useState } from 'react';
import { ConfigButton, type ConfigButtonProps } from './ConfigButton';
import { DropdownList, type DropdownListProps } from './DropdownList';
type SingleSelectConfigButtonProps = Pick<
type SingleSelectConfigButtonProps<T> = Pick<
ConfigButtonProps,
'button' | 'onOpen' | 'onClose' | 'description' | 'tooltip'
> &
Pick<DropdownListProps, 'search' | 'onChange' | 'options'>;
Pick<DropdownListProps<T>, 'search' | 'onChange' | 'options'>;
export const SingleSelectConfigButton: FC<SingleSelectConfigButtonProps> = ({
export function SingleSelectConfigButton<T = string>({
onChange,
...props
}) => {
}: SingleSelectConfigButtonProps<T>) {
const [anchorEl, setAnchorEl] = useState<HTMLDivElement | null>();
const [recentlyClosed, setRecentlyClosed] = useState(false);
const handleChange = (value: any) => {
const handleChange = (value: T) => {
onChange(value);
setAnchorEl(null);
props.onClose?.();
@ -34,7 +34,7 @@ export const SingleSelectConfigButton: FC<SingleSelectConfigButtonProps> = ({
anchorEl={anchorEl}
setAnchorEl={setAnchorEl}
>
<DropdownList {...props} onChange={handleChange} />
<DropdownList<T> {...props} onChange={handleChange} />
</ConfigButton>
);
};
}

View File

@ -17,7 +17,7 @@ const useFeatureForm = (
const { validateFeatureToggleName } = useFeatureApi();
const toggleQueryName = params.get('name');
const [type, setType] = useState(initialType);
const [tags, setTags] = useState<Set<string>>(new Set());
const [tags, setTags] = useState<Set<ITag>>(new Set());
const [name, setName] = useState(toggleQueryName || initialName);
const [project, setProject] = useState(projectId || initialProject);
const [description, setDescription] = useState(initialDescription);
@ -50,11 +50,7 @@ const useFeatureForm = (
}, [initialImpressionData]);
const getTogglePayload = () => {
const splitTags: ITag[] = Array.from(tags).map((tag) => {
const [type, value] = tag.split(':');
return { type, value };
});
const tagsPayload = tags.size > 0 ? { tags: splitTags } : {};
const tagsPayload = tags.size > 0 ? { tags: Array.from(tags) } : {};
return {
type,
name,

View File

@ -35,6 +35,7 @@ import useAllTags from 'hooks/api/getters/useAllTags/useAllTags';
import Label from '@mui/icons-material/Label';
import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
import { MultiSelectConfigButton } from 'component/common/DialogFormTemplate/ConfigButtons/MultiSelectConfigButton';
import type { ITag } from 'interfaces/tags';
interface ICreateFeatureDialogProps {
open: boolean;
@ -272,7 +273,7 @@ export const CreateFeatureDialog = ({
/>
}
/>
<MultiSelectConfigButton
<MultiSelectConfigButton<ITag>
tooltip={{
header: 'Select tags',
}}
@ -280,7 +281,7 @@ export const CreateFeatureDialog = ({
selectedOptions={tags}
options={allTags.map((tag) => ({
label: `${tag.type}:${tag.value}`,
value: `${tag.type}:${tag.value}`,
value: tag,
}))}
onChange={setTags}
button={{
@ -334,7 +335,7 @@ export const CreateFeatureDialog = ({
onClose={clearDocumentationOverride}
/>
<SingleSelectConfigButton
<SingleSelectConfigButton<boolean>
tooltip={{
header: 'Enable or disable impression data',
}}
@ -342,11 +343,11 @@ export const CreateFeatureDialog = ({
configButtonData.impressionData.text
}
options={[
{ label: 'On', value: 'true' },
{ label: 'Off', value: 'false' },
{ label: 'On', value: true },
{ label: 'Off', value: false },
]}
onChange={(value: string) => {
setImpressionData(value === 'true');
onChange={(value: boolean) => {
setImpressionData(value);
}}
button={{
label: `Impression data ${impressionData ? 'on' : 'off'}`,