diff --git a/ui/webui/src/components/storage/InstallationMethod.jsx b/ui/webui/src/components/storage/InstallationMethod.jsx
index 16b413ab9bc2..f009e4926e1d 100644
--- a/ui/webui/src/components/storage/InstallationMethod.jsx
+++ b/ui/webui/src/components/storage/InstallationMethod.jsx
@@ -21,18 +21,22 @@ import {
Alert,
AlertActionCloseButton,
Button,
+ Chip,
+ ChipGroup,
Flex,
FlexItem,
Form,
FormGroup,
- Title
-} from "@patternfly/react-core";
-import {
+ MenuToggle,
Select,
+ SelectList,
SelectOption,
- SelectVariant,
-} from "@patternfly/react-core/deprecated";
-import { SyncAltIcon, WrenchIcon } from "@patternfly/react-icons";
+ TextInputGroup,
+ TextInputGroupMain,
+ TextInputGroupUtilities,
+ Title,
+} from "@patternfly/react-core";
+import { SyncAltIcon, TimesIcon, WrenchIcon } from "@patternfly/react-icons";
import { InstallationScenario } from "./InstallationScenario.jsx";
@@ -86,10 +90,196 @@ const containEqualDisks = (disks1, disks2) => {
return disks1Str === disks2Str;
};
+const LocalDisksSelect = ({ deviceData, diskSelection, idPrefix, setSelectedDisks }) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const [inputValue, setInputValue] = useState("");
+ const [focusedItemIndex, setFocusedItemIndex] = useState(null);
+ const textInputRef = useRef();
+
+ let selectOptions = diskSelection.usableDisks
+ .map(disk => ({
+ description: deviceData[disk]?.description.v,
+ name: disk,
+ size: cockpit.format_bytes(deviceData[disk]?.total.v),
+ value: disk,
+ }))
+ .filter(option =>
+ String(option.name)
+ .toLowerCase()
+ .includes(inputValue.toLowerCase()) ||
+ String(option.description)
+ .toLowerCase()
+ .includes(inputValue.toLowerCase())
+ );
+
+ if (selectOptions.length === 0) {
+ selectOptions = [
+ { children: _("No results found"), value: "no results" }
+ ];
+ }
+
+ const onSelect = (selectedDisk) => {
+ if (diskSelection.selectedDisks.includes(selectedDisk)) {
+ setSelectedDisks({ drives: diskSelection.selectedDisks.filter(disk => disk !== selectedDisk) });
+ } else {
+ setSelectedDisks({ drives: [...diskSelection.selectedDisks, selectedDisk] });
+ }
+ textInputRef.current?.focus();
+ };
+
+ const clearSelection = () => {
+ setSelectedDisks({ drives: [] });
+ };
+
+ const handleMenuArrowKeys = (key) => {
+ let indexToFocus;
+
+ if (isOpen) {
+ if (key === "ArrowUp") {
+ // When no index is set or at the first index, focus to the last, otherwise decrement focus index
+ if (focusedItemIndex === null || focusedItemIndex === 0) {
+ indexToFocus = selectOptions.length - 1;
+ } else {
+ indexToFocus = focusedItemIndex - 1;
+ }
+ }
+
+ if (key === "ArrowDown") {
+ // When no index is set or at the last index, focus to the first, otherwise increment focus index
+ if (focusedItemIndex === null || focusedItemIndex === selectOptions.length - 1) {
+ indexToFocus = 0;
+ } else {
+ indexToFocus = focusedItemIndex + 1;
+ }
+ }
+
+ setFocusedItemIndex(indexToFocus);
+ }
+ };
+
+ const onInputKeyDown = (event) => {
+ const enabledMenuItems = selectOptions.filter((menuItem) => !menuItem.isDisabled);
+ const [firstMenuItem] = enabledMenuItems;
+ const focusedItem = focusedItemIndex ? enabledMenuItems[focusedItemIndex] : firstMenuItem;
+
+ switch (event.key) {
+ // Select the first available option
+ case "Enter":
+ if (!isOpen) {
+ setIsOpen((prevIsOpen) => !prevIsOpen);
+ } else if (focusedItem.name !== "no results") {
+ onSelect(focusedItem.name);
+ }
+ break;
+ case "Tab":
+ case "Escape":
+ setIsOpen(false);
+ break;
+ case "ArrowUp":
+ case "ArrowDown":
+ event.preventDefault();
+ handleMenuArrowKeys(event.key);
+ break;
+ }
+ };
+
+ const onToggleClick = () => {
+ setIsOpen(!isOpen);
+ };
+
+ const onTextInputChange = (_event, value) => {
+ setInputValue(value);
+ };
+
+ const toggle = (toggleRef) => (
+
+
+
+
+ {diskSelection.selectedDisks.map((selection, index) => (
+ {
+ ev.stopPropagation();
+ onSelect(selection);
+ }}
+ >
+ {selection}
+
+ ))}
+
+
+
+ {diskSelection.selectedDisks.length > 0 && (
+
+ )}
+
+
+
+ );
+
+ return (
+
+ );
+};
+
const InstallationDestination = ({ deviceData, diskSelection, dispatch, idPrefix, isBootIso, setIsFormValid, onCritFail }) => {
const [isRescanningDisks, setIsRescanningDisks] = useState(false);
const [equalDisksNotify, setEqualDisksNotify] = useState(false);
- const [isOpen, setIsOpen] = useState(false);
const refUsableDisks = useRef();
debug("DiskSelector: deviceData: ", JSON.stringify(Object.keys(deviceData)), ", diskSelection: ", JSON.stringify(diskSelection));
@@ -162,63 +352,13 @@ const InstallationDestination = ({ deviceData, diskSelection, dispatch, idPrefix
);
- const onSelect = (event, selection) => {
- const selectedDisk = selection.name;
-
- if (diskSelection.selectedDisks.includes(selectedDisk)) {
- setSelectedDisks({ drives: diskSelection.selectedDisks.filter(disk => disk !== selectedDisk) });
- } else {
- setSelectedDisks({ drives: [...diskSelection.selectedDisks, selectedDisk] });
- }
- };
-
- const clearSelection = () => {
- setSelectedDisks({ drives: [] });
- };
-
const localDisksSelect = (
-
+
);
const equalDisks = refUsableDisks.current && containEqualDisks(refUsableDisks.current, diskSelection.usableDisks);
@@ -293,7 +433,11 @@ export const InstallationMethod = ({
}) => {
return (
-