Line data Source code
1 : /*
2 : SPDX-License-Identifier: MIT
3 :
4 : Copyright (c) 2019 Red Hat, Inc.
5 : */
6 :
7 : /* This is a copy of SimpleSelect.tsx from
8 :
9 : https://github.com/patternfly/patternfly-react/blob/v5/packages/react-templates/src/components/Select/SimpleSelect.tsx
10 :
11 : We don't use it directly from the @patternfly/react-templates node
12 : module since we might want to add features to it, and also to
13 : isolate us from gratuitous upstream changes.
14 :
15 : Our changes:
16 :
17 : - The selection is controlled from the outside and not maintained
18 : as internal state. This is how things should work with React.
19 :
20 : - The selected value can be any JavaScript value, not just a string
21 : or number. Normally, the (stringified) value is used as a React
22 : key. If your values are not suitable for this because they are
23 : neither strings nor numbers, you need to provide an explicit key.
24 :
25 : { key: 12, value: { param: "boot", value: 12 }, content: _("Boot number 12") }
26 :
27 : - Support for dividers.
28 :
29 : { decorator: "divider", key: "..." }
30 :
31 : - Support for headers.
32 :
33 : { decorator: "header", content: _("Also available"), key: "..." }
34 :
35 : */
36 :
37 : /* eslint-disable */
38 :
39 58 : import React from 'react';
40 : import {
41 : Select,
42 : SelectList,
43 : SelectOption,
44 : SelectOptionProps,
45 : SelectProps,
46 : } from '@patternfly/react-core/dist/esm/components/Select';
47 : import { Divider } from '@patternfly/react-core/dist/esm/components/Divider';
48 : import { MenuToggle, MenuToggleElement, MenuToggleProps } from '@patternfly/react-core/dist/esm/components/MenuToggle';
49 :
50 : import "cockpit-components-select.scss";
51 :
52 : export interface SimpleSelectDividerOption {
53 : decorator: "divider";
54 :
55 : key: string | number;
56 : };
57 :
58 : export interface SimpleSelectHeaderOption {
59 : decorator: "header";
60 :
61 : key: string | number;
62 : content: React.ReactNode;
63 : };
64 :
65 : export interface SimpleSelectMenuOption<T> extends Omit<SelectOptionProps, 'content'> {
66 : decorator?: undefined;
67 :
68 : /** Content of the select option. */
69 : content: React.ReactNode;
70 : /** Value of the select option. */
71 : value: T;
72 : }
73 :
74 : export type SimpleSelectOption<T> = SimpleSelectMenuOption<T> |
75 : SimpleSelectDividerOption |
76 : SimpleSelectHeaderOption;
77 :
78 : export interface SimpleSelectProps<T> extends Omit<SelectProps, 'toggle' | 'onSelect'> {
79 : /** Initial options of the select. */
80 : options: SimpleSelectOption<T>[];
81 : /** Selected option */
82 : selected: T;
83 : /** Callback triggered on selection. */
84 : onSelect: (selection: T) => void;
85 : /** Callback triggered when the select opens or closes. */
86 : onToggle?: (nextIsOpen: boolean) => void;
87 : /** Flag indicating the select should be disabled. */
88 : isDisabled?: boolean;
89 : /** Content of the toggle. Defaults to the selected option. */
90 : toggleContent?: React.ReactNode;
91 : /** Placeholder text for the select input. */
92 : placeholder?: string;
93 : /** Width of the toggle. */
94 : toggleWidth?: string;
95 : /** Additional props passed to the toggle. */
96 : toggleProps?: MenuToggleProps;
97 : }
98 :
99 27 : export function SimpleSelect<T>({
100 27 : options,
101 27 : selected,
102 27 : isDisabled = false,
103 27 : onSelect,
104 27 : onToggle,
105 27 : toggleContent,
106 27 : toggleWidth,
107 27 : toggleProps,
108 27 : placeholder = '',
109 27 : ...props
110 27 : }: SimpleSelectProps<T>) {
111 27 : const [isOpen, setIsOpen] = React.useState(false);
112 :
113 27 : const simpleSelectOptions = options.map(option => {
114 27 : if (option.decorator == "divider")
115 4 : return <Divider key={option.key} component="li" />;
116 :
117 27 : if (option.decorator == "header")
118 27 : return (
119 2 : <SelectOption key={option.key}
120 2 : isDisabled
121 2 : className="ct-select-header">
122 2 : {option.content}
123 2 : </SelectOption>
124 : );
125 :
126 27 : const { content, value, key, ...props } = option;
127 27 : return (
128 11 : <SelectOption value={value} key={key !== undefined ? key : `${value}`} {...props}>
129 27 : {content}
130 27 : </SelectOption>
131 : );
132 27 : });
133 :
134 11 : const onToggleClick = () => {
135 0 : onToggle && onToggle(!isOpen);
136 11 : setIsOpen(!isOpen);
137 11 : };
138 :
139 10 : const _onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, value: T | undefined) => {
140 10 : if (value) {
141 10 : onSelect(value);
142 10 : }
143 0 : onToggle && onToggle(true);
144 10 : setIsOpen(false);
145 10 : };
146 :
147 27 : let content: React.ReactNode = placeholder;
148 27 : if (toggleContent)
149 2 : content = toggleContent;
150 27 : else if (selected)
151 27 : content = options.find((o): o is SimpleSelectMenuOption<T> => !o.decorator && o.value == selected)?.content;
152 :
153 27 : const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
154 27 : <MenuToggle
155 27 : ref={toggleRef}
156 27 : onClick={onToggleClick}
157 27 : isExpanded={isOpen}
158 27 : isDisabled={isDisabled}
159 27 : style={
160 27 : {
161 27 : width: toggleWidth
162 27 : } as React.CSSProperties
163 : }
164 27 : {...toggleProps}
165 : >
166 27 : {content}
167 27 : </MenuToggle>
168 : );
169 :
170 27 : return (
171 27 : <Select
172 27 : isOpen={isOpen}
173 27 : selected={selected}
174 27 : onSelect={_onSelect}
175 0 : onOpenChange={(isOpen) => {
176 0 : onToggle && onToggle(isOpen);
177 0 : setIsOpen(isOpen);
178 0 : }}
179 27 : toggle={toggle}
180 27 : shouldFocusToggleOnSelect
181 27 : {...props}
182 : >
183 27 : <SelectList>{simpleSelectOptions}</SelectList>
184 27 : </Select>
185 : );
186 27 : };
187 :
188 58 : SimpleSelect.displayName = 'SimpleSelect';
|