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:
parent
b55d6f46d0
commit
c0d7be040d
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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'}`,
|
||||
|
Loading…
Reference in New Issue
Block a user