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 133 : 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 28 : export function SimpleSelect<T>({
100 28 : options,
101 28 : selected,
102 28 : isDisabled = false,
103 28 : onSelect,
104 28 : onToggle,
105 28 : toggleContent,
106 28 : toggleWidth,
107 28 : toggleProps,
108 28 : placeholder = '',
109 28 : ...props
110 28 : }: SimpleSelectProps<T>) {
111 28 : const [isOpen, setIsOpen] = React.useState(false);
112 :
113 28 : const simpleSelectOptions = options.map(option => {
114 28 : if (option.decorator == "divider")
115 5 : return <Divider key={option.key} component="li" />;
116 :
117 28 : if (option.decorator == "header")
118 28 : return (
119 3 : <SelectOption key={option.key}
120 3 : isDisabled
121 3 : className="ct-select-header">
122 3 : {option.content}
123 3 : </SelectOption>
124 : );
125 :
126 28 : const { content, value, key, ...props } = option;
127 28 : return (
128 12 : <SelectOption value={value} key={key !== undefined ? key : `${value}`} {...props}>
129 28 : {content}
130 28 : </SelectOption>
131 : );
132 28 : });
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 28 : let content: React.ReactNode = placeholder;
148 28 : if (toggleContent)
149 3 : content = toggleContent;
150 28 : else if (selected)
151 28 : content = options.find((o): o is SimpleSelectMenuOption<T> => !o.decorator && o.value == selected)?.content;
152 :
153 28 : const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
154 28 : <MenuToggle
155 28 : ref={toggleRef}
156 28 : onClick={onToggleClick}
157 28 : isExpanded={isOpen}
158 28 : isDisabled={isDisabled}
159 28 : style={
160 28 : {
161 28 : width: toggleWidth
162 28 : } as React.CSSProperties
163 : }
164 28 : {...toggleProps}
165 : >
166 28 : {content}
167 28 : </MenuToggle>
168 : );
169 :
170 28 : return (
171 28 : <Select
172 28 : isOpen={isOpen}
173 28 : selected={selected}
174 28 : onSelect={_onSelect}
175 0 : onOpenChange={(isOpen) => {
176 0 : onToggle && onToggle(isOpen);
177 0 : setIsOpen(isOpen);
178 0 : }}
179 28 : toggle={toggle}
180 28 : shouldFocusToggleOnSelect
181 28 : {...props}
182 : >
183 28 : <SelectList>{simpleSelectOptions}</SelectList>
184 28 : </Select>
185 : );
186 28 : };
187 :
188 133 : SimpleSelect.displayName = 'SimpleSelect';
|