SPDX-License-Identifier: MIT
Copyright (c) 2019 Red Hat, Inc.
*/
https://github.com/patternfly/patternfly-react/blob/v5/packages/react-templates/src/components/Select/SimpleSelect.tsx
We don't use it directly from the @patternfly/react-templates node
module since we might want to add features to it, and also to
isolate us from gratuitous upstream changes.
Our changes:
- The selection is controlled from the outside and not maintained
as internal state. This is how things should work with React.
- The selected value can be any JavaScript value, not just a string
or number. Normally, the (stringified) value is used as a React
key. If your values are not suitable for this because they are
neither strings nor numbers, you need to provide an explicit key.
{ key: 12, value: { param: "boot", value: 12 }, content: _("Boot number 12") }
- Support for dividers.
{ decorator: "divider", key: "..." }
- Support for headers.
{ decorator: "header", content: _("Also available"), key: "..." }
*/
import React from 'react';
import {
Select,
SelectList,
SelectOption,
SelectOptionProps,
SelectProps,
} from '@patternfly/react-core/dist/esm/components/Select';
import { Divider } from '@patternfly/react-core/dist/esm/components/Divider';
import { MenuToggle, MenuToggleElement, MenuToggleProps } from '@patternfly/react-core/dist/esm/components/MenuToggle';
import "cockpit-components-select.scss";
export interface SimpleSelectDividerOption {
decorator: "divider";
key: string | number;
};
export interface SimpleSelectHeaderOption {
decorator: "header";
key: string | number;
content: React.ReactNode;
};
export interface SimpleSelectMenuOption<T> extends Omit<SelectOptionProps, 'content'> {
decorator?: undefined;
content: React.ReactNode;
value: T;
}
export type SimpleSelectOption<T> = SimpleSelectMenuOption<T> |
SimpleSelectDividerOption |
SimpleSelectHeaderOption;
export interface SimpleSelectProps<T> extends Omit<SelectProps, 'toggle' | 'onSelect'> {
options: SimpleSelectOption<T>[];
selected: T;
onSelect: (selection: T) => void;
onToggle?: (nextIsOpen: boolean) => void;
isDisabled?: boolean;
toggleContent?: React.ReactNode;
placeholder?: string;
toggleWidth?: string;
toggleProps?: MenuToggleProps;
}
export function SimpleSelect<T>({
options,
selected,
isDisabled = false,
onSelect,
onToggle,
toggleContent,
toggleWidth,
toggleProps,
placeholder = '',
...props
}: SimpleSelectProps<T>) {
const [isOpen, setIsOpen] = React.useState(false);
const simpleSelectOptions = options.map(option => {
if (option.decorator === "divider")
return <Divider key={option.key} component="li" />;
if (option.decorator === "header")
return (
<SelectOption key={option.key}
isDisabled
className="ct-select-header">
{option.content}
</SelectOption>
);
const { content, value, key, ...props } = option;
return (
<SelectOption value={value} key={key !== undefined ? key : `${value}`} {...props}>
{content}
</SelectOption>
);
});
const onToggleClick = () => {
onToggle && onToggle(!isOpen);
setIsOpen(!isOpen);
};
const _onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, value: T | undefined) => {
if (value) {
onSelect(value);
}
onToggle && onToggle(true);
setIsOpen(false);
};
let content: React.ReactNode = placeholder;
if (toggleContent)
content = toggleContent;
else if (selected)
content = options.find((o): o is SimpleSelectMenuOption<T> => !o.decorator && o.value === selected)?.content;
const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
<MenuToggle
ref={toggleRef}
onClick={onToggleClick}
isExpanded={isOpen}
isDisabled={isDisabled}
style={
{
width: toggleWidth
} as React.CSSProperties
}
{...toggleProps}
>
{content}
</MenuToggle>
);
return (
<Select
isOpen={isOpen}
selected={selected}
onSelect={_onSelect}
onOpenChange={(isOpen) => {
onToggle && onToggle(isOpen);
setIsOpen(isOpen);
}}
toggle={toggle}
shouldFocusToggleOnSelect
{...props}
>
<SelectList>{simpleSelectOptions}</SelectList>
</Select>
);
};
SimpleSelect.displayName = 'SimpleSelect';