Wizard: Switch kernel name dropdown to typeahead with custom options

This replaces previously used single dropdown with a typeahead that allow creating a custom option.
This commit is contained in:
regexowl 2025-01-20 11:31:41 +01:00 committed by Klara Simickova
parent 86add0ee38
commit addd933451
2 changed files with 149 additions and 33 deletions

View file

@ -1,13 +1,18 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { FormGroup } from '@patternfly/react-core';
import {
Button,
MenuToggle,
MenuToggleElement,
Select,
SelectList,
SelectOption,
TextInputGroup,
TextInputGroupMain,
TextInputGroupUtilities,
} from '@patternfly/react-core/dist/esm';
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
import {
@ -15,56 +20,120 @@ import {
selectKernel,
} from '../../../../../store/wizardSlice';
const kernelOptions = ['kernel', 'kernel-debug'];
let kernelOptions = ['kernel', 'kernel-debug'];
const KernelName = () => {
const dispatch = useAppDispatch();
const kernel = useAppSelector(selectKernel);
const kernel = useAppSelector(selectKernel).name;
const [isOpen, setIsOpen] = useState(false);
const [inputValue, setInputValue] = useState<string>('');
const [filterValue, setFilterValue] = useState<string>('');
const [selectOptions, setSelectOptions] = useState<string[]>(kernelOptions);
const onToggle = () => {
useEffect(() => {
let filteredKernelPkgs = kernelOptions;
if (filterValue) {
filteredKernelPkgs = kernelOptions.filter((kernel: string) =>
String(kernel).toLowerCase().includes(filterValue.toLowerCase())
);
if (!filteredKernelPkgs.some((kernel) => kernel === filterValue)) {
filteredKernelPkgs = [
...filteredKernelPkgs,
`Custom kernel package "${filterValue}"`,
];
}
if (!isOpen) {
setIsOpen(true);
}
}
setSelectOptions(filteredKernelPkgs);
// This useEffect hook should run *only* on when the filter value changes.
// eslint's exhaustive-deps rule does not support this use.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [filterValue]);
const onToggle = (isOpen: boolean) => {
setIsOpen(!isOpen);
};
const onSelect = (_event: React.MouseEvent, value: string) => {
if (value === 'default') {
dispatch(changeKernelName(''));
} else {
dispatch(changeKernelName(value));
const onInputClick = () => {
if (!isOpen) {
setIsOpen(true);
} else if (!inputValue) {
setIsOpen(false);
}
setIsOpen(false);
};
const defaultOption = () => {
return (
<SelectOption key="default" value="default">
Default kernel package
</SelectOption>
);
const onSelect = (_event: React.MouseEvent, value: string) => {
if (value) {
if (/custom kernel package/i.test(value)) {
if (!kernelOptions.some((kernel) => kernel === filterValue)) {
kernelOptions = [...kernelOptions, filterValue];
}
dispatch(changeKernelName(filterValue));
setFilterValue('');
setIsOpen(false);
} else {
setInputValue(value);
setFilterValue('');
dispatch(changeKernelName(value));
setIsOpen(false);
}
}
};
const prepareSelectOptions = () => {
const kernelSelectOptions = kernelOptions.map((option) => (
<SelectOption key={option} value={option}>
{option}
</SelectOption>
));
const onTextInputChange = (_event: React.FormEvent, value: string) => {
setInputValue(value);
setFilterValue(value);
if (kernel.name) {
return [defaultOption()].concat(kernelSelectOptions);
} else return kernelSelectOptions;
if (value !== kernel) {
dispatch(changeKernelName(''));
}
};
const onToggleClick = () => {
setIsOpen(!isOpen);
};
const onClearButtonClick = () => {
setInputValue('');
setFilterValue('');
dispatch(changeKernelName(''));
};
const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
<MenuToggle
ref={toggleRef}
onClick={onToggle}
variant="typeahead"
onClick={onToggleClick}
isExpanded={isOpen}
isFullWidth
data-testid="kernel-name-dropdown"
>
{kernel.name}
<TextInputGroup isPlain>
<TextInputGroupMain
value={kernel ? kernel : inputValue}
onClick={onInputClick}
onChange={onTextInputChange}
autoComplete="off"
placeholder="Select kernel package"
isExpanded={isOpen}
/>
{kernel && (
<TextInputGroupUtilities>
<Button
variant="plain"
onClick={onClearButtonClick}
aria-label="Clear input"
>
<TimesIcon />
</Button>
</TextInputGroupUtilities>
)}
</TextInputGroup>
</MenuToggle>
);
@ -73,13 +142,19 @@ const KernelName = () => {
<Select
isScrollable
isOpen={isOpen}
selected={kernel.name}
selected={kernel}
onSelect={onSelect}
onOpenChange={onToggle}
toggle={toggle}
shouldFocusFirstItemOnOpen={false}
>
<SelectList>{prepareSelectOptions()}</SelectList>
<SelectList>
{selectOptions.map((option) => (
<SelectOption key={option} value={option}>
{option}
</SelectOption>
))}
</SelectList>
</Select>
</FormGroup>
);