Skip to content

Commit

Permalink
common: New TypeaheadSelectWithHeaders
Browse files Browse the repository at this point in the history
This is uses the beta Patternfly template TypeaheadSelect instead of
the deprecated Select with "typeahead" variant.

The deprecated Select has performance problems with long grouped
lists, and also fails to match entries when the typeahead ends in the
character ")"...
  • Loading branch information
mvollmer committed Oct 25, 2024
1 parent fac7a38 commit cb84438
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 1 deletion.
2 changes: 1 addition & 1 deletion node_modules
Submodule node_modules updated 39777 files
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@patternfly/react-icons": "5.4.0",
"@patternfly/react-styles": "5.4.0",
"@patternfly/react-table": "5.4.1",
"@patternfly/react-templates": "^1.1.8",
"@patternfly/react-tokens": "5.4.0",
"@xterm/addon-canvas": "0.7.0",
"@xterm/xterm": "5.5.0",
Expand Down
89 changes: 89 additions & 0 deletions src/components/common/typeaheadSelectWithHeaders.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* This file is part of Cockpit.
*
* Copyright (C) 2024 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/

/* This is the Patternfly 5 TypeaheadSelect, with support for header
* entries. Headers are a more straightforward alternative to grouping
* with SelectGroup.
*
* Properties of TypeaheadSelectWithHeaders are the same as for
* TypeaheadSelect. The get headers, the "selectOptions" properties
* can contain entries like this:
*
* { header: "Header title" }
*
* These header entries are turned into regular, disabled entries that
* are styled to look like group headers.
*
* For laziness reasons, TypeaheadSelectWithHeaders does not allow to
* override the filterFunction, but you can specify a matchFunction
* that works on individual options.
*/

import React, { useMemo } from 'react';
import { TypeaheadSelect } from '@patternfly/react-templates/dist/esm/components/Select';

function defaultMatchFunction(filterValue, option) {
return option.content.toLowerCase().includes(filterValue.toLowerCase());
}

function transformOption(option, idx) {
if (option.header) {
return {
value: " header:" + idx,
content: "",
description: option.header,
isDisabled: true,
};
} else
return option;
}

function isHeaderOption(option) {
return option.value.startsWith(" header:");
}

export const TypeaheadSelectWithHeaders = ({
selectOptions,
filterFunction, // must be undefined
matchFunction,
...props
}) => {
const transformedSelectOptions = useMemo(() => selectOptions.map(transformOption),
[selectOptions]);

if (filterFunction)
console.warn("TypeaheadSelectWithHeaders does not support filterFunction, use matchFunction");

const headerFilterFunction = (filterValue, options) => {
// Filter by search term
const filtered = options.filter(o => (isHeaderOption(o) ||
(matchFunction || defaultMatchFunction)(filterValue, o)));
// Remove headers that have nothing following them.
const filtered2 = filtered.filter((o, i) => {
return !(isHeaderOption(o) && (i >= filtered.length - 1 || isHeaderOption(filtered[i + 1])));
});
return filtered2;
};

return (
<TypeaheadSelect selectOptions={transformedSelectOptions}
filterFunction={headerFilterFunction}
{...props} />
);
};

0 comments on commit cb84438

Please sign in to comment.