/* * Copyright (C) 2025 Red Hat, Inc. * SPDX-License-Identifier: LGPL-2.1-or-later */ import cockpit from "cockpit"; import React, { useLayoutEffect, useRef, useState } from "react"; import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js"; import { Menu, MenuContent, MenuGroup, MenuItem, MenuList } from "@patternfly/react-core/dist/esm/components/Menu/index.js"; import { TextInputGroup, TextInputGroupMain, TextInputGroupUtilities } from "@patternfly/react-core/dist/esm/components/TextInputGroup/index.js"; import { SearchIcon } from "@patternfly/react-icons/dist/esm/icons/search-icon"; import { TimesIcon } from "@patternfly/react-icons/dist/esm/icons/times-icon"; import { warn as loggerWarn } from "../../helpers/log.js"; import "./MenuSearch.scss"; const _ = cockpit.gettext; const renderOptions = (options, scrollRef, selection, search) => { return options.map(option => { // Support both single selection (string) and multiple selection (array) let isSelected = false; if (selection) { if (Array.isArray(selection)) { isSelected = selection.includes(option.itemId); } else { isSelected = option.itemId === selection; } } switch (option.itemType) { case "menu-item": if (search && option.onSearch(search) === false) { return null; // Skip items that do not match the search criteria } return ( {option.itemLang ? (
{option.itemText}
) : option.itemText}
); case "menu-group": { const itemChildren = renderOptions(option.itemChildren, scrollRef, selection, search); if (itemChildren.filter((item) => item !== null).length === 0) { return null; // Skip empty groups } return ( {itemChildren} ); } default: loggerWarn(`Unknown item type: ${option.itemType}`); return null; } }); }; export const MenuSearch = ({ ariaLabelSearch, handleOnSelect, menuType, options, screenId, selection }) => { const [search, setSearch] = useState(""); const prefix = screenId + "-" + menuType; const scrollRef = useRef(null); const didScroll = useRef(false); // Scroll only once when the menu is opened and the selected item is available. useLayoutEffect(() => { if (scrollRef.current && !didScroll.current) { scrollRef.current.scrollIntoView({ block: "center" }); didScroll.current = true; } }); const menuListContent = renderOptions(options, scrollRef, selection, search); return ( <> } value={search} onChange={(event) => setSearch(event.target.value)} aria-label={ariaLabelSearch} /> {search && ( )} {menuListContent.filter((item) => item !== null).length > 0 ? ( menuListContent ) : ( {_("No results found")} )} ); };