@splunk/react-ui 5.7.0 → 5.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Accordion.js +6 -6
- package/Box.js +83 -34
- package/CHANGELOG.md +34 -0
- package/CollapsiblePanel.js +11 -11
- package/ComboBox.js +31 -27
- package/ControlGroup.js +92 -91
- package/DefinitionList.js +9 -9
- package/Drawer.d.ts +2 -0
- package/Drawer.js +679 -0
- package/DualListbox.js +1 -1
- package/JSONTree.js +73 -72
- package/Link.js +2 -2
- package/MIGRATION.md +10 -0
- package/Menu.js +338 -240
- package/Modal.js +127 -109
- package/Multiselect.js +437 -351
- package/Paginator.js +14 -12
- package/Popover.js +4 -1
- package/README.md +11 -0
- package/RadioBar.js +1 -1
- package/Search.js +103 -88
- package/Select.js +42 -40
- package/SelectBase.js +374 -328
- package/SidePanel.js +346 -167
- package/SlidingPanels.js +11 -11
- package/StepBar.js +7 -7
- package/Switch.js +5 -5
- package/Text.js +24 -24
- package/TextArea.js +7 -7
- package/TransitionOpen.js +204 -185
- package/docs-llm/Accordion.md +267 -0
- package/docs-llm/Anchor Menu.md +115 -0
- package/docs-llm/Anchor.md +54 -0
- package/docs-llm/AnimationToggle.md +254 -0
- package/docs-llm/Avatar.md +298 -0
- package/docs-llm/Badge.md +212 -0
- package/docs-llm/Breadcrumbs.md +306 -0
- package/docs-llm/Button Group.md +53 -0
- package/docs-llm/Button.md +361 -0
- package/docs-llm/Card Layout.md +286 -0
- package/docs-llm/Card.md +619 -0
- package/docs-llm/Checkbox.md +218 -0
- package/docs-llm/Chip.md +291 -0
- package/docs-llm/Clickable.md +160 -0
- package/docs-llm/Code.md +292 -0
- package/docs-llm/Collapsible Panel.md +744 -0
- package/docs-llm/Color.md +253 -0
- package/docs-llm/Column Layout.md +391 -0
- package/docs-llm/Combo Box.md +540 -0
- package/docs-llm/Control Group.md +594 -0
- package/docs-llm/Date.md +270 -0
- package/docs-llm/Definition List.md +278 -0
- package/docs-llm/Divider.md +216 -0
- package/docs-llm/Drawer.md +414 -0
- package/docs-llm/Dropdown.md +472 -0
- package/docs-llm/Dual Listbox.md +325 -0
- package/docs-llm/File.md +653 -0
- package/docs-llm/Form Rows.md +374 -0
- package/docs-llm/Heading.md +179 -0
- package/docs-llm/Image.md +109 -0
- package/docs-llm/JSON Tree.md +260 -0
- package/docs-llm/Layer.md +74 -0
- package/docs-llm/Layout.md +50 -0
- package/docs-llm/Link.md +318 -0
- package/docs-llm/List.md +189 -0
- package/docs-llm/Markdown.md +179 -0
- package/docs-llm/Menu.md +735 -0
- package/docs-llm/Message Bar.md +236 -0
- package/docs-llm/Message.md +248 -0
- package/docs-llm/Modal.md +443 -0
- package/docs-llm/Monogram.md +159 -0
- package/docs-llm/Multiselect.md +937 -0
- package/docs-llm/Number.md +298 -0
- package/docs-llm/Paginator.md +395 -0
- package/docs-llm/Paragraph.md +148 -0
- package/docs-llm/Phone Number.md +254 -0
- package/docs-llm/Popover.md +166 -0
- package/docs-llm/Progress.md +141 -0
- package/docs-llm/Radio Bar.md +303 -0
- package/docs-llm/Radio List.md +350 -0
- package/docs-llm/Resize.md +362 -0
- package/docs-llm/Screen Reader Content.md +73 -0
- package/docs-llm/Scroll Container Context.md +155 -0
- package/docs-llm/Scroll.md +152 -0
- package/docs-llm/Search.md +381 -0
- package/docs-llm/Select.md +985 -0
- package/docs-llm/Side Panel.md +777 -0
- package/docs-llm/Slider.md +339 -0
- package/docs-llm/Sliding Panels.md +340 -0
- package/docs-llm/Split Button.md +295 -0
- package/docs-llm/Static Content.md +90 -0
- package/docs-llm/Step Bar.md +292 -0
- package/docs-llm/Switch.md +268 -0
- package/docs-llm/Tab Bar.md +439 -0
- package/docs-llm/Tab Layout.md +398 -0
- package/docs-llm/Table.md +2642 -0
- package/docs-llm/Text Area.md +253 -0
- package/docs-llm/Text.md +339 -0
- package/docs-llm/Tooltip.md +325 -0
- package/docs-llm/Transition Open.md +406 -0
- package/docs-llm/Tree.md +586 -0
- package/docs-llm/Typography.md +125 -0
- package/docs-llm/Wait Spinner.md +121 -0
- package/docs-llm/llms.txt +97 -0
- package/package.json +6 -5
- package/types/src/Box/Box.d.ts +2 -10
- package/types/src/Drawer/Body.d.ts +17 -0
- package/types/src/Drawer/Drawer.d.ts +114 -0
- package/types/src/Drawer/DrawerContext.d.ts +11 -0
- package/types/src/Drawer/Footer.d.ts +25 -0
- package/types/src/Drawer/Header.d.ts +41 -0
- package/types/src/Drawer/docs/examples/Basic.d.ts +6 -0
- package/types/src/Drawer/docs/examples/ContainerPosition.d.ts +7 -0
- package/types/src/Drawer/docs/examples/InitialFocus.d.ts +9 -0
- package/types/src/Drawer/docs/examples/InlinePosition.d.ts +7 -0
- package/types/src/Drawer/docs/examples/PagePosition.d.ts +7 -0
- package/types/src/Drawer/index.d.ts +2 -0
- package/types/src/JSONTree/JSONTree.d.ts +12 -5
- package/types/src/JSONTree/renderTreeItems.d.ts +2 -1
- package/types/src/Menu/Item.d.ts +2 -1
- package/types/src/Menu/docs/examples/SelectableCheckbox.d.ts +7 -0
- package/types/src/Modal/Modal.d.ts +1 -2
- package/types/src/Select/Option.d.ts +6 -3
- package/types/src/Select/Select.d.ts +8 -5
- package/types/src/Select/docs/examples/Dimmed.d.ts +7 -0
- package/types/src/SelectBase/OptionBase.d.ts +6 -3
- package/types/src/SelectBase/SelectBase.d.ts +8 -3
- package/types/src/SidePanel/SidePanel.d.ts +43 -2
- package/types/src/SidePanel/docs/examples/DockLayout.d.ts +17 -0
- package/types/src/SidePanel/docs/examples/InitialFocus.d.ts +9 -0
- package/types/src/TransitionOpen/TransitionOpen.d.ts +29 -4
- package/types/src/useKeyPress/index.d.ts +9 -2
- package/types/src/useOnClickOutside/index.d.ts +2 -0
- package/types/src/useOnClickOutside/useOnClickOutside.d.ts +4 -0
- package/useKeyPress.js +23 -18
- package/useOnClickOutside.d.ts +2 -0
- package/useOnClickOutside.js +79 -0
- package/types/src/RadioList/docs/examples/Row.d.ts +0 -6
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
# Combo Box
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
> Image: Illustration of a combo box component.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
<Message appearance="fill" type="info">
|
|
11
|
+
<div>All data entry components should be wrapped in a <Link to="ControlGroup">Control Group</Link> to provide a label, error states, and help or error text, ensuring an accessible experience for all users.</div>
|
|
12
|
+
</Message>
|
|
13
|
+
|
|
14
|
+
## When to use this component
|
|
15
|
+
- User needs to select from a large list of options.
|
|
16
|
+
- User needs to search and filter options within a list.
|
|
17
|
+
- Users need the flexibility to enter custom values in addition to selecting options from a list.
|
|
18
|
+
- Users know what they’re looking for and can enter enough characters to refine their search.
|
|
19
|
+
|
|
20
|
+
## When to use another component
|
|
21
|
+
- Use a Radio List or Switch(Checkbox) for a small list of predefined options.
|
|
22
|
+
- Use a Select when the list of options is short, well-defined, and clearly distinct from each other. If the user needs to browse through values, a Select is more appropriate
|
|
23
|
+
- User needs to select multiple options or needs to create a custom value in addition to selecting multiple options from a list, use Multiselect.
|
|
24
|
+
- Use a Text for free-form input.
|
|
25
|
+
|
|
26
|
+
```mermaid
|
|
27
|
+
graph TD
|
|
28
|
+
accDescr: Decision tree that guides on when to use the ComboBox component or something else
|
|
29
|
+
A(Are there less than 8 options?) -- Yes --- B(Radio or Checkbox group)
|
|
30
|
+
A -- No --- C(Well-defined list of options?)
|
|
31
|
+
C -- Yes --- D(Select)
|
|
32
|
+
C -- No --- E(Do users need to select multiple options?)
|
|
33
|
+
E -- No --- F(Combo Box)
|
|
34
|
+
E -- Yes --- G(Multiselect)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Check out
|
|
38
|
+
- [Radio List][1]
|
|
39
|
+
- [Switch] [2]
|
|
40
|
+
- [Select][3]
|
|
41
|
+
- [Multiselect][4]
|
|
42
|
+
- [Text][5]
|
|
43
|
+
|
|
44
|
+
## Behaviors
|
|
45
|
+
|
|
46
|
+
### Custom values
|
|
47
|
+
Allow users to enter custom values not present in the predefined list.
|
|
48
|
+
> Image: Image showing an examples of the Combo box component with a custom value, Custom chart, typed in the input box.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
### Matching
|
|
52
|
+
Assist users in finding options quickly by providing matches and/or recommendations
|
|
53
|
+
> Image: Image showing a Combo box component in a focus state with the word chart typed in the input box, resulting in the list of options being filtered to only show options that have the word chart in the title.
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
### Make large lists easier to search
|
|
59
|
+
The list of selections is complex enough to require searching and filtering capabilities. If the list is not extensive enough to justify a combo box, consider using a different selection method.
|
|
60
|
+
> Image: Two examples of Combo boxes. The first example with heart eyes emoji has a combo box labeled
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
## Content guidelines
|
|
65
|
+
### Keep menu items concise
|
|
66
|
+
Menu items should be clear and concise. Avoid menu items that wrap to multiple lines. Instead, use shorter text or increase the menu width when space allows.
|
|
67
|
+
> Image: Two examples of Combo boxes, both are labeled
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
### Truncation
|
|
71
|
+
Use a width that accommodates the majority of options when possible.
|
|
72
|
+
> Image: Two examples of Combo boxes, both are labeled
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
[1]: ./RadioList
|
|
76
|
+
[2]: ./Switch
|
|
77
|
+
[3]: ./Select
|
|
78
|
+
[4]: ./Multiselect
|
|
79
|
+
[5]: ./Text
|
|
80
|
+
|
|
81
|
+
## Examples
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
### Uncontrolled
|
|
85
|
+
|
|
86
|
+
This is a basic example in which data management and filtering are handled internally.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import React from 'react';
|
|
90
|
+
|
|
91
|
+
import ComboBox from '@splunk/react-ui/ComboBox';
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
function Uncontrolled() {
|
|
95
|
+
return (
|
|
96
|
+
<ComboBox inline>
|
|
97
|
+
<ComboBox.Option value="Line Chart" description="Recommended" />
|
|
98
|
+
<ComboBox.Option value="Area Chart" />
|
|
99
|
+
<ComboBox.Option value="Column Chart" />
|
|
100
|
+
<ComboBox.Option value="Bar Chart" />
|
|
101
|
+
<ComboBox.Option value="Pie Chart" />
|
|
102
|
+
<ComboBox.Option value="Scatter Chart" />
|
|
103
|
+
<ComboBox.Option value="Bubble Chart" />
|
|
104
|
+
</ComboBox>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default Uncontrolled;
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
### Headings
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import React from 'react';
|
|
117
|
+
|
|
118
|
+
import ComboBox from '@splunk/react-ui/ComboBox';
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
export default function Headings() {
|
|
122
|
+
return (
|
|
123
|
+
<ComboBox inline>
|
|
124
|
+
<ComboBox.Option value="Events" />
|
|
125
|
+
<ComboBox.Option value="Statistics Table" />
|
|
126
|
+
<ComboBox.Heading>Chart</ComboBox.Heading>
|
|
127
|
+
<ComboBox.Option value="Line Chart" />
|
|
128
|
+
<ComboBox.Option value="Area Chart" />
|
|
129
|
+
<ComboBox.Option value="Column Chart" />
|
|
130
|
+
<ComboBox.Option value="Bar Chart" />
|
|
131
|
+
<ComboBox.Option value="Pie Chart" />
|
|
132
|
+
<ComboBox.Option value="Scatter Chart" />
|
|
133
|
+
<ComboBox.Option value="Bubble Chart" />
|
|
134
|
+
<ComboBox.Heading>Value</ComboBox.Heading>
|
|
135
|
+
<ComboBox.Option value="Single Value" />
|
|
136
|
+
<ComboBox.Option value="Radial Gauge" />
|
|
137
|
+
<ComboBox.Option value="Filler Gauge" />
|
|
138
|
+
<ComboBox.Option value="Marker Gauge" />
|
|
139
|
+
<ComboBox.Heading>Map</ComboBox.Heading>
|
|
140
|
+
<ComboBox.Option value="Cluster Map" />
|
|
141
|
+
<ComboBox.Option value="Choropleth Map" />
|
|
142
|
+
</ComboBox>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
### Controlled Value
|
|
150
|
+
|
|
151
|
+
Typically, the value is controlled.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import React, { useState } from 'react';
|
|
155
|
+
|
|
156
|
+
import ComboBox, { ComboBoxChangeHandler } from '@splunk/react-ui/ComboBox';
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
function Controlled() {
|
|
160
|
+
const [value, setValue] = useState('');
|
|
161
|
+
|
|
162
|
+
const handleChange: ComboBoxChangeHandler = (e, { value: comboBoxValue }) => {
|
|
163
|
+
setValue(comboBoxValue);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<ComboBox inline onChange={handleChange} value={value}>
|
|
168
|
+
<ComboBox.Option value="Line Chart" description="Recommended" />
|
|
169
|
+
<ComboBox.Option value="Area Chart" />
|
|
170
|
+
<ComboBox.Option value="Column Chart" />
|
|
171
|
+
<ComboBox.Option value="Bar Chart" />
|
|
172
|
+
<ComboBox.Option value="Pie Chart" />
|
|
173
|
+
<ComboBox.Option value="Scatter Chart" />
|
|
174
|
+
<ComboBox.Option value="Bubble Chart" />
|
|
175
|
+
</ComboBox>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export default Controlled;
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
### Label
|
|
185
|
+
|
|
186
|
+
label can be used in addition to value when needed, similar to Select. label will be used for matching and must be a string.
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import React, { useState } from 'react';
|
|
190
|
+
|
|
191
|
+
import ComboBox, { ComboBoxChangeHandler } from '@splunk/react-ui/ComboBox';
|
|
192
|
+
import ControlGroup from '@splunk/react-ui/ControlGroup';
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
function Label() {
|
|
196
|
+
const [value, setValue] = useState('');
|
|
197
|
+
|
|
198
|
+
const handleChange: ComboBoxChangeHandler = (e, { value: comboBoxValue }) => {
|
|
199
|
+
setValue(comboBoxValue);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<>
|
|
204
|
+
<ControlGroup label="Controlled" labelPosition="top">
|
|
205
|
+
<ComboBox inline onChange={handleChange} value={value}>
|
|
206
|
+
<ComboBox.Option value="chart-1" label="Line Chart" description="Recommended" />
|
|
207
|
+
<ComboBox.Option value="chart-2" label="Area Chart" />
|
|
208
|
+
<ComboBox.Option value="chart-3" label="Column Chart" />
|
|
209
|
+
<ComboBox.Option value="chart-4" label="Bar Chart" />
|
|
210
|
+
<ComboBox.Option value="chart-5" label="Pie Chart" />
|
|
211
|
+
<ComboBox.Option value="chart-6" label="Scatter Chart" />
|
|
212
|
+
<ComboBox.Option value="chart-7" label="Bubble Chart" />
|
|
213
|
+
</ComboBox>
|
|
214
|
+
</ControlGroup>
|
|
215
|
+
|
|
216
|
+
<ControlGroup label="Uncontrolled" labelPosition="top">
|
|
217
|
+
<ComboBox inline>
|
|
218
|
+
<ComboBox.Option value="chart-1" label="Line Chart" description="Recommended" />
|
|
219
|
+
<ComboBox.Option value="chart-2" label="Area Chart" />
|
|
220
|
+
<ComboBox.Option value="chart-3" label="Column Chart" />
|
|
221
|
+
<ComboBox.Option value="chart-4" label="Bar Chart" />
|
|
222
|
+
<ComboBox.Option value="chart-5" label="Pie Chart" />
|
|
223
|
+
<ComboBox.Option value="chart-6" label="Scatter Chart" />
|
|
224
|
+
<ComboBox.Option value="chart-7" label="Bubble Chart" />
|
|
225
|
+
</ComboBox>
|
|
226
|
+
</ControlGroup>
|
|
227
|
+
</>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export default Label;
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
### Fetching
|
|
237
|
+
|
|
238
|
+
You can populate the Options from a server. Here, that behavior is simulated. This simplified example only matches the start of the label. The matchRange prop can be set manually to reflect the matching algorithm used to filter the results. Otherwise matching text is not shown.
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
242
|
+
|
|
243
|
+
import ComboBox, { ComboBoxChangeHandler } from '@splunk/react-ui/ComboBox';
|
|
244
|
+
import useFetchOptions, { MovieOption } from '@splunk/react-ui/fixtures/useFetchOptions';
|
|
245
|
+
import { _ } from '@splunk/ui-utils/i18n';
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
function Fetching() {
|
|
249
|
+
|
|
250
|
+
const { fetch, getFullCount, stop } = useFetchOptions();
|
|
251
|
+
|
|
252
|
+
const [fullCount, setFullCount] = useState(0);
|
|
253
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
254
|
+
const [options, setOptions] = useState<MovieOption[]>([]);
|
|
255
|
+
const [value, setValue] = useState('');
|
|
256
|
+
|
|
257
|
+
const handleFetch = useCallback(
|
|
258
|
+
(comboBoxValue = '') => {
|
|
259
|
+
setValue(comboBoxValue);
|
|
260
|
+
setIsLoading(true);
|
|
261
|
+
fetch(comboBoxValue)
|
|
262
|
+
.then((comboBoxOptions) => {
|
|
263
|
+
setFullCount(getFullCount());
|
|
264
|
+
setIsLoading(false);
|
|
265
|
+
setOptions(comboBoxOptions);
|
|
266
|
+
})
|
|
267
|
+
.catch((error) => {
|
|
268
|
+
if (!error.isCanceled) {
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
},
|
|
273
|
+
[fetch, getFullCount]
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
const generateOptions = () => {
|
|
277
|
+
if (isLoading) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/*
|
|
282
|
+
* Filtering is done server-side and the `matchRanges` prop would be either
|
|
283
|
+
* be provided by the server, deduced based on the match algorithm, or omitted.
|
|
284
|
+
*/
|
|
285
|
+
return options.map((movie) => (
|
|
286
|
+
<ComboBox.Option value={movie.title} key={movie.id} matchRanges={movie.matchRanges} />
|
|
287
|
+
));
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const footerMessage = () => {
|
|
291
|
+
if (fullCount > options.length && !isLoading) {
|
|
292
|
+
return _('%1 of %2 movies')
|
|
293
|
+
.replace('%1', options.length.toString())
|
|
294
|
+
.replace('%2', fullCount.toString());
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const handleChange: ComboBoxChangeHandler = (e, { value: comboBoxValue }) => {
|
|
300
|
+
handleFetch(comboBoxValue);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
useEffect(() => {
|
|
304
|
+
handleFetch();
|
|
305
|
+
|
|
306
|
+
return () => {
|
|
307
|
+
stop();
|
|
308
|
+
};
|
|
309
|
+
}, [handleFetch, stop]);
|
|
310
|
+
|
|
311
|
+
const comboBoxOptions = generateOptions();
|
|
312
|
+
const footerMessageValue = footerMessage();
|
|
313
|
+
|
|
314
|
+
return (
|
|
315
|
+
<ComboBox
|
|
316
|
+
value={value}
|
|
317
|
+
controlledFilter
|
|
318
|
+
inline
|
|
319
|
+
placeholder={_('Select a movie...')}
|
|
320
|
+
menuStyle={{ width: 300 }}
|
|
321
|
+
onChange={handleChange}
|
|
322
|
+
isLoadingOptions={isLoading}
|
|
323
|
+
footerMessage={footerMessageValue}
|
|
324
|
+
>
|
|
325
|
+
{comboBoxOptions}
|
|
326
|
+
</ComboBox>
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export default Fetching;
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
### Load more on scroll bottom
|
|
336
|
+
|
|
337
|
+
Similar example to Fetching, but you can also append more Options from a server when the list is scrolled to the bottom. Here, that behavior is simulated. The onScrollBottom prop is a function that fetches more results and appends them to the current Options. Once all items are loaded, the onScrollBottom prop should be set to null.
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
341
|
+
|
|
342
|
+
import ComboBox, { ComboBoxChangeHandler } from '@splunk/react-ui/ComboBox';
|
|
343
|
+
import useFetchOptions, { MovieOption } from '@splunk/react-ui/fixtures/useFetchOptions';
|
|
344
|
+
import { _ } from '@splunk/ui-utils/i18n';
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
const createFooterMessage = (loadingMore: string, count: string) => {
|
|
348
|
+
return _('%1 movies %2').replace('%1', loadingMore).replace('%2', count);
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
function LoadMoreOnScrollBottom() {
|
|
352
|
+
const [fullCount, setFullCount] = useState<number>(0);
|
|
353
|
+
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
354
|
+
const [isLoadingMore, setIsLoadingMore] = useState<boolean>(false);
|
|
355
|
+
const [options, setOptions] = useState<MovieOption[]>([]);
|
|
356
|
+
const [value, setValue] = useState<string>('');
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
const { fetch, fetchMore, getFullCount, stop } = useFetchOptions();
|
|
360
|
+
|
|
361
|
+
const handleFetch = useCallback(
|
|
362
|
+
(comboBoxValue = '') => {
|
|
363
|
+
setValue(comboBoxValue);
|
|
364
|
+
setIsLoading(true);
|
|
365
|
+
|
|
366
|
+
fetch(comboBoxValue)
|
|
367
|
+
.then((comboBoxOptions) => {
|
|
368
|
+
setOptions(comboBoxOptions);
|
|
369
|
+
setIsLoading(false);
|
|
370
|
+
setIsLoadingMore(false);
|
|
371
|
+
setFullCount(getFullCount());
|
|
372
|
+
})
|
|
373
|
+
.catch((error) => {
|
|
374
|
+
if (!error.isCanceled) {
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
},
|
|
379
|
+
[fetch, getFullCount, setOptions]
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
const handleFetchMore = (currentOptions?: MovieOption[]) => {
|
|
383
|
+
setIsLoadingMore(true);
|
|
384
|
+
|
|
385
|
+
fetchMore(currentOptions)
|
|
386
|
+
.then((comboBoxOptions) => {
|
|
387
|
+
setOptions(comboBoxOptions);
|
|
388
|
+
setIsLoadingMore(false);
|
|
389
|
+
setIsLoadingMore(false);
|
|
390
|
+
setFullCount(getFullCount());
|
|
391
|
+
})
|
|
392
|
+
.catch((error) => {
|
|
393
|
+
if (!error.isCanceled) {
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
const handleScrollBottom = () => {
|
|
400
|
+
if (!isLoadingMore) {
|
|
401
|
+
handleFetchMore(options);
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const generateOptions = () => {
|
|
406
|
+
if (isLoading) {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/*
|
|
411
|
+
* Filtering is done server-side and the `matchRanges` prop would be either
|
|
412
|
+
* be provided by the server, deduced based on the match algorithm, or omitted.
|
|
413
|
+
*/
|
|
414
|
+
return options.map((movie) => (
|
|
415
|
+
<ComboBox.Option value={movie.title} key={movie.id} matchRanges={movie.matchRanges} />
|
|
416
|
+
));
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
useEffect(() => {
|
|
420
|
+
handleFetch();
|
|
421
|
+
|
|
422
|
+
return () => {
|
|
423
|
+
stop();
|
|
424
|
+
};
|
|
425
|
+
}, [setOptions, handleFetch, stop]);
|
|
426
|
+
|
|
427
|
+
const handleChange: ComboBoxChangeHandler = (e, { value: comboBoxValue }) => {
|
|
428
|
+
handleFetch(comboBoxValue);
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
const footerMessage = createFooterMessage(
|
|
432
|
+
fullCount.toString(),
|
|
433
|
+
isLoadingMore ? _('(Loading more movies)') : ''
|
|
434
|
+
);
|
|
435
|
+
const comboBoxOptions = generateOptions();
|
|
436
|
+
|
|
437
|
+
return (
|
|
438
|
+
<ComboBox
|
|
439
|
+
value={value}
|
|
440
|
+
controlledFilter
|
|
441
|
+
inline
|
|
442
|
+
placeholder={_('Select a movie...')}
|
|
443
|
+
menuStyle={{ width: 300 }}
|
|
444
|
+
onChange={handleChange}
|
|
445
|
+
onScrollBottom={
|
|
446
|
+
// Disable when all items are loaded.
|
|
447
|
+
fullCount === options.length ? undefined : handleScrollBottom
|
|
448
|
+
}
|
|
449
|
+
isLoadingOptions={isLoading}
|
|
450
|
+
footerMessage={footerMessage}
|
|
451
|
+
>
|
|
452
|
+
{comboBoxOptions}
|
|
453
|
+
</ComboBox>
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export default LoadMoreOnScrollBottom;
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
## API
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
### ComboBox API
|
|
467
|
+
|
|
468
|
+
`ComboBox` allows the user to select a predefined string or enter a new value. Unlike `Select`
|
|
469
|
+
and `Multiselect`, `Option` value must always be a string.
|
|
470
|
+
|
|
471
|
+
#### Props
|
|
472
|
+
|
|
473
|
+
| Name | Type | Required | Default | Description |
|
|
474
|
+
|------|------|------|------|------|
|
|
475
|
+
| animateLoading | boolean | no | | |
|
|
476
|
+
| append | boolean | no | | Append removes rounded borders and border from the right side. |
|
|
477
|
+
| children | React.ReactNode | no | | All children must be instances of `ComboBox.Option`. |
|
|
478
|
+
| controlledFilter | boolean | no | | If true, this component will not handle filtering. The parent must update the Options based on the onChange value. |
|
|
479
|
+
| defaultPlacement | 'above' \| 'below' \| 'vertical' | no | 'vertical' | The default placement of the dropdown menu. It might be rendered in a different direction depending upon the space available. |
|
|
480
|
+
| defaultValue | string | no | | The initial value of the input. Only applicable in uncontrolled mode. |
|
|
481
|
+
| describedBy | string | no | | The id of the description. When placed in a ControlGroup, this is automatically set to the ControlGroup's help component. |
|
|
482
|
+
| disabled | boolean | no | | |
|
|
483
|
+
| elementRef | React.Ref<HTMLDivElement> | no | | A React ref which is set to the DOM element when the component mounts and null when it unmounts. |
|
|
484
|
+
| error | boolean | no | | Highlight the field as having an error. The border and text will turn red. |
|
|
485
|
+
| footerMessage | React.ReactNode | no | | The footer message can show additional information, such as a truncation message. |
|
|
486
|
+
| inline | boolean | no | | Make the control an inline block with variable width. |
|
|
487
|
+
| inputId | string | no | | An id for the input, which may be necessary for accessibility, such as for aria attributes. |
|
|
488
|
+
| inputRef | React.Ref<HTMLInputElement> | no | | A React ref which is set to the input element when the component mounts and null when it unmounts. |
|
|
489
|
+
| isLoadingOptions | boolean | no | | |
|
|
490
|
+
| labelledBy | string | no | | The id of the label. When placed in a ControlGroup, this is automatically set to the ControlGroup's label. |
|
|
491
|
+
| loadingMessage | React.ReactNode | no | | The loading message to show when isLoadingOptions. |
|
|
492
|
+
| menuStyle | React.CSSProperties | no | {} | |
|
|
493
|
+
| name | string | no | | The name is returned with onChange events, which can be used to identify the control when multiple controls share an onChange callback. |
|
|
494
|
+
| noOptionsMessage | React.ReactNode | no | | The noOptionsMessage is shown when there are no children and it's not loading, such as when there are no Options matching the filter. This can be customized to the type of content, for example: "No matching dashboards". You can insert content such as an error message or communicate a minimum number of characters to enter to see results. |
|
|
495
|
+
| onBlur | ComboBoxBlurHandler | no | | |
|
|
496
|
+
| onChange | ComboBoxChangeHandler | no | | |
|
|
497
|
+
| onClose | () => void | no | | A callback function invoked when the popover closes. |
|
|
498
|
+
| onFocus | ComboBoxFocusHandler | no | | |
|
|
499
|
+
| onKeyDown | React.KeyboardEventHandler<HTMLInputElement> | no | | |
|
|
500
|
+
| onOpen | () => void | no | | A callback function invoked when the popover opens. |
|
|
501
|
+
| onScroll | React.UIEventHandler<Element> | no | | A callback function invoked when the menu is scrolled. |
|
|
502
|
+
| onScrollBottom | ComboBoxScrollBottomHandler | no | | A callback function for loading additional list items. Called when the list is scrolled to the bottom or all items in the list are visible. This is called with an event argument if this is the result of a scroll. This should be set this to `null` when all items are loaded. |
|
|
503
|
+
| onSelect | React.ReactEventHandler<HTMLInputElement> | no | | |
|
|
504
|
+
| placeholder | string | no | _('Select...') | |
|
|
505
|
+
| prepend | boolean | no | | Prepend removes rounded borders from the left side. |
|
|
506
|
+
| value | string | no | | The value of the input. Only applicable in controlled mode. |
|
|
507
|
+
|
|
508
|
+
#### Types
|
|
509
|
+
|
|
510
|
+
| Name | Type | Description |
|
|
511
|
+
|------|------|------|
|
|
512
|
+
| ComboBoxBlurHandler | ( event: React.FocusEvent<HTMLInputElement>, data: { name?: string; value: string; } ) => void | |
|
|
513
|
+
| ComboBoxChangeHandler | ( event: \| React.ChangeEvent<HTMLInputElement> \| React.MouseEvent<HTMLButtonElement \| HTMLSpanElement> \| React.KeyboardEvent<HTMLInputElement>, data: { name?: string; value: string; } ) => void | |
|
|
514
|
+
| ComboBoxFocusHandler | ( event: React.FocusEvent<HTMLInputElement>, data: { name?: string; value: string; } ) => void | |
|
|
515
|
+
| ComboBoxScrollBottomHandler | ( event: React.UIEvent<HTMLDivElement> \| React.KeyboardEvent<HTMLInputElement> \| null ) => void | |
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
### ComboBox.Option API
|
|
520
|
+
|
|
521
|
+
An option within a `ComboBox`.
|
|
522
|
+
|
|
523
|
+
#### Props
|
|
524
|
+
|
|
525
|
+
| Name | Type | Required | Default | Description |
|
|
526
|
+
|------|------|------|------|------|
|
|
527
|
+
| description | string | no | | Additional information to explain the option, such as "Recommended". |
|
|
528
|
+
| descriptionPosition | 'right' \| 'bottom' | no | 'bottom' | The description text may appear to the right of the label or under the label. |
|
|
529
|
+
| disabled | boolean | no | | If disabled=true, the option is grayed out and cannot be clicked. |
|
|
530
|
+
| elementRef | React.Ref<HTMLButtonElement \| HTMLAnchorElement> | no | | A React ref which is set to the DOM element when the component mounts and null when it unmounts. |
|
|
531
|
+
| icon | React.ReactNode | no | | The icon to show before the label. See the @splunk/react-icons package for drop in icons. Caution: The element(s) passed here must be pure. All icons in the react-icons package are pure. |
|
|
532
|
+
| label | string | no | | When provided, `label` is rendered instead of the `value` and used for matching. |
|
|
533
|
+
| matchRanges | { start: number; end: number }[] | no | | Sections of the label string to highlight as a match. This is automatically set for uncontrolled filters, so it's not normally necessary to set this property when using filtering. |
|
|
534
|
+
| truncate | boolean | no | | When `true`, wrapping is disabled and any additional text is ellipsised. |
|
|
535
|
+
| value | string | yes | | The value of this option and the label shown for it. |
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
|