@jobber/components 8.4.0 → 8.5.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.
Files changed (44) hide show
  1. package/dist/Autocomplete/Autocomplete.d.ts +6 -4
  2. package/dist/Autocomplete/Autocomplete.types.d.ts +3 -137
  3. package/dist/Autocomplete/components/FloatingMenu.d.ts +6 -6
  4. package/dist/Autocomplete/components/MenuList.d.ts +4 -4
  5. package/dist/Autocomplete/components/PersistentRegion.d.ts +3 -3
  6. package/dist/Autocomplete/index.cjs +25 -1658
  7. package/dist/Autocomplete/index.d.ts +3 -30
  8. package/dist/Autocomplete/index.mjs +25 -1647
  9. package/dist/Autocomplete/tests/Autocomplete.setup.d.ts +15 -15
  10. package/dist/Autocomplete/useAutocomplete.d.ts +2 -2
  11. package/dist/Autocomplete-cjs.js +1306 -0
  12. package/dist/Autocomplete-es.js +1304 -0
  13. package/dist/Menu-es.js +1 -1
  14. package/dist/MenuSubmenuTrigger-es.js +1 -1
  15. package/dist/docs/Autocomplete/{AutocompleteV2.md → Autocomplete.md} +13 -107
  16. package/dist/docs/Banner/Banner.md +3 -3
  17. package/dist/docs/Box/Box.md +2 -2
  18. package/dist/docs/Chips/Chips.md +1 -1
  19. package/dist/docs/DataList/DataList.md +1 -1
  20. package/dist/docs/Icon/Icon.md +1 -1
  21. package/dist/docs/InputGroup/InputGroup.md +1 -1
  22. package/dist/docs/Menu/Menu.md +1 -1
  23. package/dist/docs/Modal/Modal.md +3 -3
  24. package/dist/docs/MultiSelect/MultiSelect.md +1 -1
  25. package/dist/docs/index.md +1 -2
  26. package/dist/docs/usage-guidelines/usage-guidelines.md +8 -10
  27. package/dist/floating-ui.react-dom-es.js +1 -1
  28. package/dist/floating-ui.react-es.js +1 -1
  29. package/dist/index.cjs +2 -2
  30. package/dist/index.esm-es.js +1 -1
  31. package/dist/index.mjs +1 -1
  32. package/dist/styles.css +29 -153
  33. package/package.json +2 -2
  34. package/dist/Autocomplete/Autocomplete.rebuilt.d.ts +0 -8
  35. package/dist/Autocomplete/Autocomplete.utils.d.ts +0 -8
  36. package/dist/Autocomplete/Menu/DefaultMenu.d.ts +0 -16
  37. package/dist/Autocomplete/Menu/Menu.d.ts +0 -3
  38. package/dist/Autocomplete/Menu/MenuWrapper.d.ts +0 -23
  39. package/dist/Autocomplete/Option.d.ts +0 -78
  40. package/dist/Autocomplete/V1.docgen.d.ts +0 -4
  41. package/dist/Autocomplete/V2.docgen.d.ts +0 -6
  42. package/dist/Autocomplete/useKeyboardNavigation.d.ts +0 -35
  43. package/dist/Autocomplete/useRepositionMenu.d.ts +0 -10
  44. package/dist/docs/Autocomplete/AutocompleteV1.md +0 -1328
@@ -1,1328 +0,0 @@
1
- # Autocomplete
2
-
3
- An Autocomplete component allows a user to quickly pick a preset value from a
4
- larger list of possible options.
5
-
6
- ## Design & usage guidelines
7
-
8
- The options in an Autocomplete component should be a list of possible values
9
- that the user can select. These options should be presented in a way that makes
10
- it easy for the user to find the value they are looking for. There are a few
11
- ways to achieve this.
12
-
13
- [Details](/storybook/web/?path=/story/components-forms-and-inputs-autocomplete--with-details)
14
- can be added to an option, which will either appear below the option's label or
15
- on the bottom right of the option. This can be used to provide more information
16
- about the option, such as a description or a count.
17
-
18
- [Section headings](/storybook/web/?path=/story/components-forms-and-inputs-autocomplete--section-heading)
19
- can be added to an Autocomplete to break up the options into groups. This can be
20
- useful if there are a lot of options or if the options are related to different
21
- things.
22
-
23
- You can
24
- [set a value](/storybook/web/?path=/story/components-forms-and-inputs-autocomplete--set-a-value)
25
- to the Autocomplete component by passing an active option. This will set the
26
- `value` of the input.
27
-
28
- Using the `getOptions` prop, you can pass a function that will be called when
29
- the user types in the input. This async request should return a list of possible
30
- options based on the user's input. This can be achieved by using either promises
31
- or `await`.
32
-
33
- ## Related components
34
-
35
- * If you want to present a list of predefined options without text input, or the
36
- number of options is smaller, use a [Select](../Select/Select.md)
37
- * If autocompleted results are not required for the text input, use
38
- [InputText](../InputText/InputText.md)
39
-
40
-
41
- ## Component customization
42
-
43
- ### Custom rendering
44
-
45
- If you need more control over the rendering of content in the menu, you can
46
- provide a **custom render function** using `customRenderMenu`
47
-
48
- This is useful if the default rendering of the Autocomplete Menu doesn’t meet
49
- your requirements. If a custom render function is provided you will be able to
50
- completely control how the menu is rendered.
51
-
52
- This is achieved by providing a function to the `customRenderMenu` prop. This
53
- function will receive the following props:
54
-
55
- * `MenuWrapper` is the wrapper of the Autocomplete Menu, this is the element
56
- that will be visible when the menu is open. It handles the positioning of the
57
- Autocomplete Menu
58
- * `inputFocused` is a boolean that indicates if the input is focused.
59
- * `onOptionSelect` is a function that is called when an option is selected for
60
- updating the value of the Autocomplete.
61
- * `options` is the list of options to render in the Autocomplete Menu. This is
62
- the options that are returned from the `getOptions` function.
63
- * `selectedOption` is the option that is currently selected.
64
- * `menuRef` is a ref that is passed to the Autocomplete Menu this is used by the
65
- `useKeyboardNavigation` hook to handle the keyboard navigation of the options.
66
- * `inputRef` is a ref that is passed to the Autocomplete Menu to provide a ref
67
- to the `InputText` component.
68
-
69
- There are also some helper functions that are used to handle the keyboard
70
- navigation of the options and the base styling of the options.
71
-
72
- * `useKeyboardNavigation` is used to handle the default keyboard navigation of
73
- the options. Use this hook if you are using only using MenuOption or
74
- BaseMenuOption components in the menu.
75
- * `useCustomKeyboardNavigation` is used to handle the keyboard navigation of the
76
- options. Use this hook if you are using components in the menu that aren't
77
- MenuOption or BaseMenuOption such as custom elements. See the Advanced
78
- examples below for more information.
79
- * `MenuOption` is the default rendering of the options in the Autocomplete Menu.
80
- The `UNSAFE_style` prop is used to add a custom styling to the options.
81
- * `isOptionGroup` is a helper function that is used to determine if the option
82
- is a group.
83
- * `isOptionSelected` is a helper function that is used to determine if the
84
- option is selected.
85
- * `MenuOptionContent` is the default rendering of the content of the options in
86
- the Autocomplete Menu.
87
- * `BaseMenuOption` is the base component for the options in the Autocomplete
88
- Menu. This component provides the base styling and functionality for the
89
- options such as the separators and the highlighting. This is used in the
90
- advanced example below.
91
-
92
- ### Basic Override
93
-
94
- In this example we will override the default rendering of the Autocomplete Menu to add a custom styling to the options while keeping the default keyboard navigation.
95
-
96
- ```tsx
97
- <AutocompleteBasicCustomRenderExample />
98
- ```
99
-
100
- **Full Example (Click to Reveal Code)**
101
-
102
- ```tsx
103
- export const BasicCustomTemplate = () => {
104
- const [detailsValue, setDetailsValue] = useState<Option | undefined>();
105
- const planetSection = SectionHeadingOptions.find(
106
- ({ label }) => label === "Planets",
107
- )?.options;
108
-
109
- return (
110
- <Content>
111
- <Autocomplete
112
- placeholder="Search for an option with section headings"
113
- initialOptions={SectionHeadingOptions}
114
- value={detailsValue}
115
- onChange={newValue => setDetailsValue(newValue)}
116
- customRenderMenu={({
117
- MenuWrapper,
118
- inputFocused,
119
- onOptionSelect,
120
- options,
121
- selectedOption,
122
- menuRef,
123
- }) => {
124
- const { highlightedIndex } = useKeyboardNavigation({
125
- visible: inputFocused,
126
- options,
127
- onOptionSelect,
128
- menuRef,
129
- });
130
-
131
- return (
132
- <MenuWrapper visible={inputFocused}>
133
- {options.map((option, index) => {
134
- const optionStyle = getOptionStyling({
135
- option,
136
- highlightedIndex,
137
- options,
138
- });
139
-
140
- return (
141
- <MenuOption
142
- key={option.label}
143
- option={option}
144
- addSeparators={isOptionGroup(option)}
145
- isHighlighted={index === highlightedIndex}
146
- onOptionSelect={onOptionSelect}
147
- isSelected={isOptionSelected(selectedOption, option)}
148
- UNSAFE_style={optionStyle}
149
- />
150
- );
151
- })}
152
- </MenuWrapper>
153
- );
154
- }}
155
- getOptions={getHeadingsOption}
156
- />
157
- </Content>
158
- );
159
-
160
- // eslint-disable-next-line max-statements
161
- function getOptionStyling({
162
- option,
163
- highlightedIndex,
164
- options,
165
- }: {
166
- option: AnyOption;
167
- highlightedIndex: number;
168
- options: AnyOption[];
169
- }) {
170
- const isSectionLabel = "options" in option;
171
- const isPlanetsLabel = option.label === "Planets";
172
-
173
- const inPlanetSection = planetSection?.find?.(
174
- ({ label }) => label === option.label,
175
- );
176
- const activeOption = options[highlightedIndex];
177
-
178
- const activeOptionInSection =
179
- isSectionLabel &&
180
- option?.options?.find?.(({ label }) => label === activeOption?.label);
181
- let optionColor = "var(--color-success--surface)";
182
- let groupColor = "var(--color-success)";
183
-
184
- if (isSectionLabel && activeOptionInSection) {
185
- groupColor = "var(--color-informative)";
186
- optionColor = "var(--color-informative--surface)";
187
- } else if (!isSectionLabel && inPlanetSection) {
188
- groupColor = "var(--color-warning)";
189
- optionColor = "var(--color-warning--surface)";
190
- } else if (isSectionLabel && isPlanetsLabel) {
191
- groupColor = "var(--color-critical)";
192
- optionColor = "var(--color-critical--surface)";
193
- }
194
- const isActive = activeOption === option ? 0.5 : 1;
195
-
196
- return {
197
- groupOption: {
198
- heading: {
199
- backgroundColor: groupColor,
200
- },
201
- },
202
- option: {
203
- backgroundColor: optionColor,
204
- opacity: isActive,
205
- },
206
- };
207
- }
208
-
209
- function getHeadingsOption(text: string) {
210
- if (text === "") {
211
- return SectionHeadingOptions;
212
- }
213
- const filterRegex = new RegExp(text, "i");
214
-
215
- return SectionHeadingOptions.map(section => ({
216
- ...section,
217
- options: section.options.filter(option =>
218
- option.label.match(filterRegex),
219
- ),
220
- }));
221
- }
222
- };
223
- ```
224
-
225
- ### Advanced Menu Override
226
-
227
- In this example we will override the rendering of the menu to render the Options
228
- in a completely custom way. This example will also include a footer that is
229
- rendered at the bottom of the menu with sticky positioning.
230
-
231
- **Note:** Due to the complexity of the example, we will not be using the
232
- `useKeyboardNavigation` hook to handle the keyboard navigation of the options.
233
-
234
- Instead we will be handling the keyboard navigation manually with the
235
- `useCustomKeyboardNavigation` hook. This requires us to manually keep track of
236
- the highlighted option index and update it when the user navigates the menu.
237
-
238
- ```tsx
239
- <AutocompleteAdvancedCustomRenderExample />
240
- ```
241
-
242
- **Full Example (Click to Reveal Code)**
243
-
244
- ```tsx
245
- interface CustomOption {
246
- value: number;
247
- label: string;
248
- address: string;
249
- contact: string;
250
- status: string;
251
- }
252
- const AdvancedOptions: CustomOption[] = [
253
- {
254
- value: 1,
255
- label: "Nostromo",
256
- address: "4517 Washington Ave. Manchester, Kentucky 39495",
257
- contact: "(406) 555-4145 · darlene.r@gmail.com",
258
- status: "Lead",
259
- },
260
- {
261
- value: 2,
262
- label: "Rodger Young",
263
- address: "8502 Preston Rd. Inglewood, Maine 98380",
264
- contact: "(406) 555-0120 · marvin.m@gmail.com",
265
- status: "Active",
266
- },
267
- {
268
- value: 3,
269
- label: "Serenity",
270
- address: "3 Properties",
271
- contact: "(406) 555-0120 · seemore.b@gmail.com",
272
- status: "Active",
273
- },
274
- {
275
- value: 4,
276
- label: "Sleeper Service",
277
- address: "3891 Ranchview Dr. Richardson, California 62639",
278
- contact: "(406) 555-0120",
279
- status: "Active",
280
- },
281
- ];
282
-
283
- export const AdvancedCustomTemplate = () => {
284
- const [value, setValue] = useState<CustomOption | undefined>();
285
-
286
- function getOptions(text: string) {
287
- if (text === "") {
288
- return AdvancedOptions;
289
- }
290
- const filterRegex = new RegExp(text, "i");
291
-
292
- return AdvancedOptions.filter(
293
- option =>
294
- option.label.match(filterRegex) ||
295
- option.address.match(filterRegex) ||
296
- option.contact.match(filterRegex),
297
- );
298
- }
299
-
300
- return (
301
- <Autocomplete
302
- placeholder="Search for something"
303
- initialOptions={AdvancedOptions}
304
- value={value}
305
- onChange={newValue => setValue(newValue)}
306
- customRenderMenu={props => <CustomMenuContent {...props} />}
307
- getOptions={getOptions}
308
- />
309
- );
310
- };
311
-
312
- function CustomMenuContent({
313
- options,
314
- selectedOption,
315
- onOptionSelect,
316
- inputFocused,
317
- inputRef,
318
- MenuWrapper,
319
- menuRef,
320
- }: CustomOptionsMenuProp<CustomOption, CustomOption>) {
321
- // Set to -1 to account for the footer being the first option when options are being initialized
322
- const INITIAL_HIGHLIGHTED_OPTION_INDEX = -1;
323
-
324
- const [highlightedOptionIndex, setHighlightedOptionIndex] = useState(
325
- INITIAL_HIGHLIGHTED_OPTION_INDEX,
326
- );
327
- // Length of options + 1 to account for the footer
328
- const maxIndex = options.length - 1 + 1;
329
-
330
- // We need to track the footer focus state because it can be focused instead of just the Input
331
- const [footerFocused, setFooterFocused] = useState(false);
332
- const footerElement = document.querySelector(
333
- "#footerElement",
334
- ) as HTMLElement;
335
-
336
- const footerFocusedCallback = useCallbackRef(() => {
337
- setFooterFocused(true);
338
- });
339
- const footerBlurCallback = useCallbackRef(() => {
340
- setFooterFocused(false);
341
- });
342
-
343
- useEffect(() => {
344
- footerElement?.addEventListener("focus", footerFocusedCallback);
345
- footerElement?.addEventListener("blur", footerBlurCallback);
346
-
347
- return () => {
348
- footerElement?.removeEventListener("focus", footerFocusedCallback);
349
- footerElement?.removeEventListener("blur", footerBlurCallback);
350
- };
351
- }, [footerElement, footerFocusedCallback, footerBlurCallback]);
352
-
353
- const menuVisible = useMemo(
354
- () => inputFocused || footerFocused,
355
- [inputFocused, footerFocused],
356
- );
357
-
358
- const onRequestHighlightChange = useCallback(
359
- (event: KeyboardEvent, direction: KeyboardAction) => {
360
- const indexChange = getRequestedIndexChange({
361
- event,
362
- options,
363
- direction,
364
- highlightedIndex: highlightedOptionIndex,
365
- });
366
- const newPreviousIndex = Math.max(
367
- 0,
368
- highlightedOptionIndex + indexChange,
369
- );
370
- const newNextIndex = Math.min(
371
- maxIndex,
372
- highlightedOptionIndex + indexChange,
373
- );
374
- if (!menuVisible) return;
375
-
376
- switch (direction) {
377
- case KeyboardAction.Previous:
378
- // If the footer is focused, focus the input
379
- if (highlightedOptionIndex === maxIndex) {
380
- inputRef?.current?.focus();
381
- }
382
- setHighlightedOptionIndex(newPreviousIndex);
383
- menuRef?.children[newPreviousIndex]?.scrollIntoView?.({
384
- behavior: "smooth",
385
- block: "nearest",
386
- inline: "start",
387
- });
388
-
389
- break;
390
- case KeyboardAction.Next: {
391
- setHighlightedOptionIndex(newNextIndex);
392
- const nextElement = menuRef?.children[newNextIndex];
393
- const footerHeight = footerElement?.offsetHeight || 0;
394
-
395
- if (nextElement) {
396
- const rect = nextElement.getBoundingClientRect();
397
- const menuRect = menuRef.getBoundingClientRect();
398
-
399
- // If element is hidden behind footer
400
- if (rect.bottom > menuRect.bottom - footerHeight) {
401
- // Calculate exact scroll position needed
402
- const scrollOffset =
403
- rect.bottom - (menuRect.bottom - footerHeight);
404
- menuRef.scrollTop += scrollOffset;
405
- } else {
406
- nextElement.scrollIntoView({
407
- behavior: "smooth",
408
- block: "nearest",
409
- inline: "start",
410
- });
411
- }
412
- }
413
-
414
- if (newNextIndex === maxIndex) {
415
- footerElement?.focus();
416
- }
417
- break;
418
- }
419
-
420
- case KeyboardAction.Select:
421
- // Don't select the footer
422
- highlightedOptionIndex < maxIndex &&
423
- onOptionSelect(options[highlightedOptionIndex]);
424
-
425
- menuRef?.children[highlightedOptionIndex]?.scrollIntoView?.({
426
- behavior: "smooth",
427
- block: "nearest",
428
- inline: "start",
429
- });
430
- if (highlightedOptionIndex === maxIndex) {
431
- footerElement?.click();
432
- }
433
- break;
434
- }
435
- },
436
- [highlightedOptionIndex, options, onOptionSelect],
437
- );
438
- useCustomKeyboardNavigation({ onRequestHighlightChange });
439
-
440
- return (
441
- <MenuWrapper visible={menuVisible}>
442
- {options.map((option, index) => {
443
- const label = option.status;
444
- const status = option.status === "Active" ? "success" : "informative";
445
- return (
446
- <BaseMenuOption
447
- addSeparators={true}
448
- isHighlighted={index === highlightedOptionIndex}
449
- onOptionSelect={onOptionSelect}
450
- option={option}
451
- key={option.label}
452
- >
453
- <Flex template={["grow", "shrink"]}>
454
- <Flex align="start" template={["shrink", "grow"]}>
455
- <Content spacing="minuscule">
456
- {selectedOption === option && (
457
- <Icon name="checkmark" size="small" />
458
- )}
459
- <Text>{option.label}</Text>
460
- <Text variation="subdued">{option.address}</Text>
461
- <Text variation="subdued">{option.contact}</Text>
462
- </Content>
463
- </Flex>
464
- <StatusLabel status={status} label={label} />
465
- </Flex>
466
- </BaseMenuOption>
467
- );
468
- })}
469
- {footer}
470
- </MenuWrapper>
471
- );
472
- }
473
- ```
474
-
475
- #### Rendering the Menu Content
476
-
477
- The content in the Menu is wrapped by the `<MenuWrapper>` component which
478
- handles attaching the menu to the input. Our custom options are wrapped by the
479
- `BaseMenuOption` component which handles the base styling and functionality for
480
- the options. Inside the `BaseMenuOption` component the content that we want to
481
- render is provided as children.
482
-
483
- **Note:** For the footer to be sticky at the bottom of the menu without being
484
- scrollable, the footer needs to be wrapped in a `div` with `position: "sticky"`
485
- and `bottom: 0`.
486
-
487
- **Menu Option Content (Click to Reveal Code)**
488
-
489
- ```tsx
490
- function addNewClient() {
491
- window.alert("Add new client");
492
- }
493
- const footer = (
494
- <div style={{ position: "sticky", bottom: 0 }}>
495
- <Button
496
- label="+ Add new client"
497
- onClick={addNewClient}
498
- id="footerElement"
499
- size="small"
500
- fullWidth
501
- type="tertiary"
502
- />
503
- </div>
504
- );
505
- <MenuWrapper visible={menuVisible}>
506
- {options.map((option, index) => {
507
- const label = option.status;
508
- const status = option.status === "Active" ? "success" : "informative";
509
-
510
- return (
511
- <BaseMenuOption
512
- addSeparators={true}
513
- isHighlighted={index === highlightedOptionIndex}
514
- onOptionSelect={onOptionSelect}
515
- option={option}
516
- key={option.label}
517
- >
518
- <Flex template={["grow", "shrink"]}>
519
- <Flex align="start" template={["shrink", "grow"]}>
520
- <Content spacing="minuscule">
521
- {selectedOption === option && (
522
- <Icon name="checkmark" size="small" />
523
- )}
524
- <Text>{option.label}</Text>
525
- <Text variation="subdued">{option.address}</Text>
526
- <Text variation="subdued">{option.contact}</Text>
527
- </Content>
528
- </Flex>
529
- <StatusLabel status={status} label={label} />
530
- </Flex>
531
- </BaseMenuOption>
532
- );
533
- })}
534
- {footer}
535
- </MenuWrapper>
536
- ```
537
-
538
- #### Keyboard Navigation
539
-
540
- The following code is the keyboard navigation logic for the menu. It is used to
541
- handle the keyboard navigation of the options and the footer.
542
-
543
- * `highlightedOptionIndex` is the index of the option that is currently
544
- highlighted. It is initially set to -1 to account for the footer being the
545
- first option when options are being initialized.
546
- * `maxIndex` is the index of the footer.
547
- * `footerFocused` is a boolean that indicates if the footer is focused. We need
548
- to track this because it can be focused instead of just the Input and if we
549
- don't then it is possible for the Menu to remain opened
550
- * `menuVisible` is a boolean that indicates if the menu is visible.
551
- * `onRequestHighlightChange` is a function that is called when the user
552
- navigates the menu.
553
- * `getRequestedIndexChange` is a helper function that is used to get the index
554
- change based on the keyboard event, direction and if the option is a group.
555
- * `KeyboardAction.Next` case should calculate the exact scroll position needed
556
- with the footer height offset in mind, when the next element is hidden behind
557
- the footer.
558
-
559
- **Keyboard Navigation (Click to Reveal Code)**
560
-
561
- ```tsx
562
- // Set to -1 to account for the footer being the first option when options are being initialized
563
- const INITIAL_HIGHLIGHTED_OPTION_INDEX = -1;
564
-
565
- const [highlightedOptionIndex, setHighlightedOptionIndex] = useState(
566
- INITIAL_HIGHLIGHTED_OPTION_INDEX,
567
- );
568
- // Length of options + 1 to account for the footer
569
- const maxIndex = options.length - 1 + 1;
570
-
571
- // We need to track the footer focus state because it can be focused instead of just the Input
572
- const [footerFocused, setFooterFocused] = useState(false);
573
- const footerElement = document.querySelector(
574
- "#footerElement",
575
- ) as HTMLElement;
576
-
577
- const footerFocusedCallback = useCallbackRef(() => {
578
- setFooterFocused(true);
579
- });
580
- const footerBlurCallback = useCallbackRef(() => {
581
- setFooterFocused(false);
582
- });
583
-
584
- useEffect(() => {
585
- footerElement?.addEventListener("focus", footerFocusedCallback);
586
- footerElement?.addEventListener("blur", footerBlurCallback);
587
-
588
- return () => {
589
- footerElement?.removeEventListener("focus", footerFocusedCallback);
590
- footerElement?.removeEventListener("blur", footerBlurCallback);
591
- };
592
- }, [footerElement, footerFocusedCallback, footerBlurCallback]);
593
-
594
- const menuVisible = useMemo(
595
- () => inputFocused || footerFocused,
596
- [inputFocused, footerFocused],
597
- );
598
-
599
- const onRequestHighlightChange = useCallback(
600
- (event: KeyboardEvent, direction: KeyboardAction) => {
601
- const indexChange = getRequestedIndexChange({
602
- event,
603
- options,
604
- direction,
605
- highlightedIndex: highlightedOptionIndex,
606
- });
607
- const newPreviousIndex = Math.max(
608
- 0,
609
- highlightedOptionIndex + indexChange,
610
- );
611
- const newNextIndex = Math.min(
612
- maxIndex,
613
- highlightedOptionIndex + indexChange,
614
- );
615
- if (!menuVisible) return;
616
-
617
- switch (direction) {
618
- case KeyboardAction.Previous:
619
- // If the footer is focused, focus the input
620
- if (highlightedOptionIndex === maxIndex) {
621
- inputRef?.current?.focus();
622
- }
623
- setHighlightedOptionIndex(newPreviousIndex);
624
- menuRef?.children[newPreviousIndex]?.scrollIntoView?.({
625
- behavior: "smooth",
626
- block: "nearest",
627
- inline: "start",
628
- });
629
-
630
- break;
631
- case KeyboardAction.Next: {
632
- setHighlightedOptionIndex(newNextIndex);
633
- const nextElement = menuRef?.children[newNextIndex];
634
- const footerHeight = footerElement?.offsetHeight || 0;
635
-
636
- if (nextElement) {
637
- const rect = nextElement.getBoundingClientRect();
638
- const menuRect = menuRef.getBoundingClientRect();
639
-
640
- // If element is hidden behind footer
641
- if (rect.bottom > menuRect.bottom - footerHeight) {
642
- // Calculate exact scroll position needed
643
- const scrollOffset =
644
- rect.bottom - (menuRect.bottom - footerHeight);
645
- menuRef.scrollTop += scrollOffset;
646
- } else {
647
- nextElement.scrollIntoView({
648
- behavior: "smooth",
649
- block: "nearest",
650
- inline: "start",
651
- });
652
- }
653
- }
654
-
655
- if (newNextIndex === maxIndex) {
656
- footerElement?.focus();
657
- }
658
- break;
659
- }
660
-
661
- case KeyboardAction.Select:
662
- // Don't select the footer
663
- highlightedOptionIndex < maxIndex &&
664
- onOptionSelect(options[highlightedOptionIndex]);
665
-
666
- menuRef?.children[highlightedOptionIndex]?.scrollIntoView?.({
667
- behavior: "smooth",
668
- block: "nearest",
669
- inline: "start",
670
- });
671
- if (highlightedOptionIndex === maxIndex) {
672
- footerElement?.click();
673
- }
674
- break;
675
- }
676
- },
677
- [highlightedOptionIndex, options, onOptionSelect],
678
- );
679
- useCustomKeyboardNavigation({ onRequestHighlightChange });
680
- ```
681
-
682
- ### Advanced Custom Section Headings
683
-
684
- In this example we will override the rendering of the menu to render the an
685
- Autocomplete with Section Headings and Custom Elements in the Section Headings.
686
-
687
- **Note:** Similar to the Advanced Custom Menu Override, due to the complexity of
688
- the example, we will not be using the `useKeyboardNavigation` hook to handle the
689
- keyboard navigation of the options.
690
-
691
- ```tsx
692
- <AutocompleteAdvancedSectionHeadingExample />
693
- ```
694
-
695
- **Full Example (Click to Reveal Code)**
696
-
697
- ```tsx
698
- interface CustomOptionForGroup {
699
- value: number;
700
- label: string;
701
- status: string;
702
- }
703
- interface CustomGroupOption {
704
- label: string;
705
- icon: IconNames;
706
- options: CustomOptionForGroup[];
707
- }
708
-
709
- const SectionHeadingOptionsCustom: CustomGroupOption[] = [
710
- {
711
- label: "Quotes",
712
- icon: "quote",
713
- options: [
714
- { value: 1, label: "Quote for Acme Corp", status: "Draft" },
715
- { value: 2, label: "Quote for Stark Industries", status: "Approved" },
716
- {
717
- value: 3,
718
- label: "Quote for Wayne Enterprises",
719
- status: "Awaiting approval",
720
- },
721
- { value: 4, label: "Quote for Umbrella Corp", status: "Paid" },
722
- ],
723
- },
724
- {
725
- icon: "invoice",
726
- label: "Invoices",
727
- options: [
728
- { value: 5, label: "Invoice for Acme Corp", status: "Draft" },
729
- {
730
- value: 6,
731
- label: "Invoice for Stark Industries",
732
- status: "Awaiting payment",
733
- },
734
- {
735
- value: 7,
736
- label: "Invoice for Wayne Enterprises",
737
- status: "Awaiting payment",
738
- },
739
- { value: 8, label: "Invoice for Umbrella Corp", status: "Paid" },
740
- ],
741
- },
742
- ];
743
-
744
- interface CustomElementOption {
745
- sectionLabel: string;
746
- indexToInsertAfter: number;
747
- }
748
- type OptionsWithExtraElements =
749
- | CustomGroupOption
750
- | CustomOptionForGroup
751
- | CustomElementOption;
752
-
753
- export function AdvancedSectionHeadingTemplate() {
754
- const [value, setValue] = useState<CustomOptionForGroup | undefined>();
755
-
756
- return (
757
- <Autocomplete
758
- placeholder="Search for something under a section heading"
759
- initialOptions={SectionHeadingOptionsCustom}
760
- value={value}
761
- onChange={newValue => setValue(newValue)}
762
- getOptions={() => SectionHeadingOptionsCustom}
763
- customRenderMenu={props => (
764
- <AdvancedSectionHeadingCustomMenuContent {...props} />
765
- )}
766
- />
767
- );
768
- }
769
-
770
- function AdvancedSectionHeadingCustomMenuContent({
771
- options,
772
- selectedOption,
773
- onOptionSelect,
774
- inputFocused,
775
- inputRef,
776
- MenuWrapper,
777
- menuRef,
778
- }: CustomOptionsMenuProp<CustomGroupOption, CustomOptionForGroup>) {
779
- const { optionsWithExtraElements, extraElementFocused } =
780
- useCustomSectionOptions(options);
781
- // Set to 1 to account for the first option being the section heading
782
- const initialHighlight = 1
783
-
784
- const [highlightedOptionIndex, setHighlightedOptionIndex] =
785
- useState(initialHighlight);
786
- // Length of options -1 and the number of extra elements
787
-
788
- const maxIndex = optionsWithExtraElements.length - 1;
789
-
790
- const menuVisible = useMemo(
791
- () => extraElementFocused || inputFocused,
792
- [extraElementFocused, inputFocused],
793
- );
794
- const onRequestHighlightChange = useCallback(
795
- // eslint-disable-next-line max-statements
796
- (_event: KeyboardEvent, direction: KeyboardAction) => {
797
- const requestedIndex =
798
- optionsWithExtraElements[highlightedOptionIndex + direction];
799
- // Default index change to the direction. If the requested index is not an extra element,
800
- // then we need to adjust the index change to account for the section heading
801
- let indexChange = direction;
802
-
803
- if (requestedIndex && !isExtraElement(requestedIndex)) {
804
- indexChange =
805
- requestedIndex && isOptionGroup(requestedIndex)
806
- ? direction + direction
807
- : direction;
808
- }
809
-
810
- const newPreviousIndex = Math.max(
811
- 0,
812
- highlightedOptionIndex + indexChange,
813
- );
814
- const newNextIndex = Math.min(
815
- maxIndex,
816
- highlightedOptionIndex + indexChange,
817
- );
818
-
819
- if (!menuVisible) return;
820
-
821
- switch (direction) {
822
- case KeyboardAction.Previous:
823
- // If the option is an extra element, focus the extra element. Else focus the input
824
- if (isExtraElement(optionsWithExtraElements[newPreviousIndex])) {
825
- const element = document.getElementById(
826
- optionsWithExtraElements[newPreviousIndex].sectionLabel,
827
- ) as HTMLElement;
828
- element?.focus();
829
- } else {
830
- inputRef?.current?.focus();
831
- }
832
-
833
- setHighlightedOptionIndex(newPreviousIndex);
834
- menuRef?.children[newPreviousIndex]?.scrollIntoView?.({
835
- behavior: "smooth",
836
- block: "nearest",
837
- inline: "start",
838
- });
839
-
840
- break;
841
- case KeyboardAction.Next:
842
- setHighlightedOptionIndex(newNextIndex);
843
- menuRef?.children[newNextIndex]?.scrollIntoView?.({
844
- behavior: "smooth",
845
- block: "nearest",
846
- inline: "start",
847
- });
848
-
849
- // If the option is an extra element, focus the extra element. Else focus the input
850
- if (isExtraElement(optionsWithExtraElements[newNextIndex])) {
851
- const element = document.getElementById(
852
- optionsWithExtraElements[newNextIndex].sectionLabel,
853
- ) as HTMLElement;
854
- element?.focus();
855
- } else {
856
- inputRef?.current?.focus();
857
- }
858
- break;
859
-
860
- case KeyboardAction.Select:
861
- menuRef?.children[highlightedOptionIndex]?.scrollIntoView?.({
862
- behavior: "smooth",
863
- block: "nearest",
864
- inline: "start",
865
- });
866
-
867
- // If the option is an extra element, click the extra element. Else check it is not an option group and select the option
868
- if (
869
- isExtraElement(optionsWithExtraElements[highlightedOptionIndex])
870
- ) {
871
- const element = document.getElementById(
872
- optionsWithExtraElements[highlightedOptionIndex].sectionLabel,
873
- ) as HTMLElement;
874
- element?.click();
875
- } else if (
876
- !isOptionGroup(optionsWithExtraElements[highlightedOptionIndex])
877
- ) {
878
- onOptionSelect(optionsWithExtraElements[highlightedOptionIndex]);
879
- }
880
-
881
- break;
882
- }
883
- },
884
- [
885
- highlightedOptionIndex,
886
- options,
887
- onOptionSelect,
888
- extraElementFocused,
889
- maxIndex,
890
- optionsWithExtraElements,
891
- ],
892
- );
893
- useCustomKeyboardNavigation({ onRequestHighlightChange });
894
-
895
- ${AdvancedSectionMenuRenderingCode}
896
- }
897
-
898
- function AdvancedSectionFooterButton({
899
- label,
900
- id,
901
- }: {
902
- readonly label: string;
903
- readonly id: string;
904
- }) {
905
- return (
906
- <Button
907
- label={\`+ Add new \${label}\`}
908
- onClick={() => alert(\`Add new \${label}\`)}
909
- size="small"
910
- id={id}
911
- fullWidth
912
- type="tertiary"
913
- />
914
- );
915
- }
916
-
917
- ${AdvancedSectionSelectableOptionCode}
918
- ${useCustomSectionOptionsCode}
919
- ${isExtraElementCode}
920
- ```
921
-
922
- #### Rendering the Menu Content
923
-
924
- The content in the Menu is wrapped by the `<MenuWrapper>` component which
925
- handles attaching the menu to the input. We use the `optionsWithExtraElements`
926
- to render the options. The `optionsWithExtraElements` is the options with the
927
- extra elements inserted in the correct positions. This array is generated using
928
- a custom hook called `useCustomSectionOptions`. (explanation of the hook below).
929
-
930
- **Menu Content Rendering (Click to Reveal Code)**
931
-
932
- ```tsx
933
- <MenuWrapper visible={menuVisible}>
934
- {optionsWithExtraElements.map((option, index) => {
935
- if (isExtraElement(option)) {
936
- return (
937
- <AdvancedSectionFooterButton
938
- label={option.sectionLabel}
939
- id={option.sectionLabel}
940
- key={\`\${option.sectionLabel}-create-new\`}
941
- />
942
- );
943
- }
944
-
945
- if (isOptionGroup(option)) {
946
- return (
947
- <BaseMenuGroupOption key={option.label}>
948
- <Flex template={["grow", "shrink"]}>
949
- <Heading level={5}>{option.label}</Heading>
950
- <Icon name={option.icon} />
951
- </Flex>
952
- </BaseMenuGroupOption>
953
- );
954
- }
955
-
956
- return (
957
- <SelectableOption
958
- isSelected={isOptionSelected(selectedOption, option)}
959
- key={option.value}
960
- option={option}
961
- index={index}
962
- highlightedOptionIndex={highlightedOptionIndex}
963
- onOptionSelect={onOptionSelect}
964
- />
965
- );
966
- })}
967
- </MenuWrapper>
968
- ```
969
-
970
- #### Utilities used for Custom Rendering
971
-
972
- The following utilities were created in this example to help with custom
973
- rendering:
974
-
975
- `useCustomSectionOptions` determines the indices of the options that should have
976
- extra elements inserted after them. It then inserts the extra elements into an
977
- array and returns it with the extra elements inserted. This is used when the
978
- options are being rendered.
979
-
980
- **useCustomSectionOptions (Click to Reveal Code)**
981
-
982
- ```tsx
983
- /**
984
- * Get the indices of the options that should have extra elements inserted after them
985
- * @param options - The options to get the extra element indices for
986
- * @returns The indices of the options that should have extra elements inserted after them
987
- */
988
- function getExtraElementIndices(
989
- options: (CustomOptionForGroup | CustomGroupOption)[],
990
- ) {
991
- const sectionsOptions = options.filter(isOptionGroup);
992
-
993
- return sectionsOptions.map(({ label, options: optionsInSection }) => {
994
- const indexToInsertAfter = options.findIndex(
995
- opt => opt === optionsInSection[optionsInSection.length - 1],
996
- );
997
-
998
- return {
999
- sectionLabel: label,
1000
- indexToInsertAfter,
1001
- };
1002
- });
1003
- }
1004
-
1005
- /**
1006
- * Creates a new array with the extra elements inserted into the options array
1007
- * @param options - The options to insert the extra elements into
1008
- * @param extraElements - The extra elements to insert into the options
1009
- * @returns The options with the extra elements inserted
1010
- */
1011
- function insertExtraElements(
1012
- options: (CustomOptionForGroup | CustomGroupOption)[],
1013
- extraElements: CustomElementOption[],
1014
- ) {
1015
- return options.reduce<Array<OptionsWithExtraElements>>(
1016
- (acc, option, index) => {
1017
- const shouldInsertExtraElement = extraElements.find(
1018
- ({ indexToInsertAfter }) => indexToInsertAfter === index,
1019
- );
1020
-
1021
- if (shouldInsertExtraElement) {
1022
- return [
1023
- ...acc,
1024
- option,
1025
- {
1026
- sectionLabel: shouldInsertExtraElement.sectionLabel,
1027
- indexToInsertAfter: shouldInsertExtraElement.indexToInsertAfter,
1028
- },
1029
- ];
1030
- }
1031
-
1032
- return [...acc, option];
1033
- },
1034
- [],
1035
- );
1036
- }
1037
-
1038
- /**
1039
- * Custom hook to handle the section options with extra elements
1040
- * @param options - The options to handle
1041
- * @returns The options with the extra elements inserted and the focus state for the extra elements
1042
- */
1043
- function useCustomSectionOptions(
1044
- options: (CustomGroupOption | CustomOptionForGroup)[],
1045
- ) {
1046
- const extraElements = useMemo(
1047
- () => getExtraElementIndices(options),
1048
- [options],
1049
- );
1050
- const optionsWithExtraElements = useMemo(
1051
- () => insertExtraElements(options, extraElements),
1052
- [options, extraElements],
1053
- );
1054
- // We need to track the focus state for the extra elements because it can be focused instead of just the Input
1055
- const [extraElementFocused, setExtraElementFocused] = useState(false);
1056
-
1057
- const extraElementFocusCallback = useCallbackRef(() => {
1058
- setExtraElementFocused(true);
1059
- });
1060
- const extraElementBlurCallback = useCallbackRef(() => {
1061
- setExtraElementFocused(false);
1062
- });
1063
- // Add event listeners to the extra elements for focus and blur
1064
- useEffect(() => {
1065
- extraElements.forEach(({ sectionLabel }) => {
1066
- const element = document.getElementById(sectionLabel) as HTMLElement;
1067
- element?.addEventListener("focus", extraElementFocusCallback);
1068
- element?.addEventListener("blur", extraElementBlurCallback);
1069
- });
1070
-
1071
- return () => {
1072
- extraElements.forEach(({ sectionLabel }) => {
1073
- const element = document.getElementById(sectionLabel) as HTMLElement;
1074
- element?.removeEventListener("focus", extraElementFocusCallback);
1075
- element?.removeEventListener("blur", extraElementBlurCallback);
1076
- });
1077
- };
1078
- }, [extraElements]);
1079
-
1080
- return {
1081
- optionsWithExtraElements,
1082
- extraElementFocused,
1083
- };
1084
- }
1085
- ```
1086
-
1087
- `isExtraElement` is a helper function that is used to determine if the option is
1088
- an extra element.
1089
-
1090
- **isExtraElement (Click to Reveal Code)**
1091
-
1092
- ```tsx
1093
- /**
1094
- * Checks if the option is an extra element
1095
- * @param option - The option to check
1096
- * @returns Whether the option is an extra element
1097
- */
1098
- function isExtraElement(option: object): option is CustomElementOption {
1099
- return option && "sectionLabel" in option && "indexToInsertAfter" in option;
1100
- }
1101
- ```
1102
-
1103
- `SelectableOption` is a component that is used to render the selectable options
1104
- in the menu.
1105
-
1106
- **SelectableOption (Click to Reveal Code)**
1107
-
1108
- ```tsx
1109
- /**
1110
- * Renders the selectable option
1111
- * @param option - The option to render
1112
- * @param index - The index of the option
1113
- * @param highlightedOptionIndex - The index of the highlighted option
1114
- * @param onOptionSelect - The function to call when the option is selected
1115
- * @param isSelected - Whether the option is selected
1116
- */
1117
- function SelectableOption({
1118
- option,
1119
- index,
1120
- highlightedOptionIndex,
1121
- onOptionSelect,
1122
- isSelected,
1123
- }: {
1124
- readonly option: CustomOptionForGroup;
1125
- readonly index: number;
1126
- readonly highlightedOptionIndex: number;
1127
- readonly onOptionSelect: (option?: CustomOptionForGroup) => void;
1128
- readonly isSelected: boolean;
1129
- }) {
1130
- let status: StatusIndicatorType = "informative";
1131
-
1132
- switch (option.status) {
1133
- case "Paid":
1134
- case "Approved":
1135
- status = "success";
1136
- break;
1137
- case "Awaiting payment":
1138
- case "Awaiting approval":
1139
- status = "warning";
1140
- break;
1141
- case "Draft":
1142
- status = "inactive";
1143
- break;
1144
- }
1145
-
1146
- return (
1147
- <BaseMenuOption
1148
- addSeparators={true}
1149
- isHighlighted={highlightedOptionIndex === index}
1150
- onOptionSelect={onOptionSelect}
1151
- option={option}
1152
- >
1153
- <Flex template={["grow", "shrink"]}>
1154
- <Text>
1155
- {isSelected && <Icon name="checkmark" size="small" />} {option.label}
1156
- </Text>
1157
- <StatusLabel label={option.status} status={status} />
1158
- </Flex>
1159
- </BaseMenuOption>
1160
- );
1161
- }
1162
- ```
1163
-
1164
- #### Keyboard Navigation
1165
-
1166
- The following code is the keyboard navigation logic for the menu. It is used to
1167
- handle the keyboard navigation of the options and the footers in each of the
1168
- sections.
1169
-
1170
- The keyboard navigation logic is similar to the Advanced Custom Menu Override.
1171
- However, we need to account for the extra elements in each of the sections
1172
- instead of just a single footer.
1173
-
1174
- `useCustomSectionOptions` returns the options with the extra elements inserted
1175
- and the `extraElementFocused` state. It provides event listeners for keeping
1176
- track of the focused state for if an extra element is focused. There are
1177
- additional checks to see if the requested index is an extra element and if it
1178
- is, focus the extra element and if not it will focus the input.
1179
-
1180
- **Keyboard Navigation (Click to Reveal Code)**
1181
-
1182
- ```tsx
1183
- ...
1184
- // Other code
1185
- const { optionsWithExtraElements, extraElementFocused } =
1186
- useCustomSectionOptions(options);
1187
- // Set to 1 to account for the first option being the section heading
1188
- const initialHighlight = 1
1189
-
1190
- const [highlightedOptionIndex, setHighlightedOptionIndex] =
1191
- useState(initialHighlight);
1192
- // Length of options -1 and the number of extra elements
1193
-
1194
- const maxIndex = optionsWithExtraElements.length - 1;
1195
-
1196
- const menuVisible = useMemo(
1197
- () => extraElementFocused || inputFocused,
1198
- [extraElementFocused, inputFocused],
1199
- );
1200
- const onRequestHighlightChange = useCallback(
1201
- // eslint-disable-next-line max-statements
1202
- (_event: KeyboardEvent, direction: KeyboardAction) => {
1203
- const requestedIndex =
1204
- optionsWithExtraElements[highlightedOptionIndex + direction];
1205
- // Default index change to the direction. If the requested index is not an extra element,
1206
- // then we need to adjust the index change to account for the section heading
1207
- let indexChange = direction;
1208
-
1209
- if (requestedIndex && !isExtraElement(requestedIndex)) {
1210
- indexChange =
1211
- requestedIndex && isOptionGroup(requestedIndex)
1212
- ? direction + direction
1213
- : direction;
1214
- }
1215
-
1216
- const newPreviousIndex = Math.max(
1217
- 0,
1218
- highlightedOptionIndex + indexChange,
1219
- );
1220
- const newNextIndex = Math.min(
1221
- maxIndex,
1222
- highlightedOptionIndex + indexChange,
1223
- );
1224
-
1225
- if (!menuVisible) return;
1226
-
1227
- switch (direction) {
1228
- case KeyboardAction.Previous:
1229
- // If the option is an extra element, focus the extra element. Else focus the input
1230
- if (isExtraElement(optionsWithExtraElements[newPreviousIndex])) {
1231
- const element = document.getElementById(
1232
- optionsWithExtraElements[newPreviousIndex].sectionLabel,
1233
- ) as HTMLElement;
1234
- element?.focus();
1235
- } else {
1236
- inputRef?.current?.focus();
1237
- }
1238
-
1239
- setHighlightedOptionIndex(newPreviousIndex);
1240
- menuRef?.children[newPreviousIndex]?.scrollIntoView?.({
1241
- behavior: "smooth",
1242
- block: "nearest",
1243
- inline: "start",
1244
- });
1245
-
1246
- break;
1247
- case KeyboardAction.Next:
1248
- setHighlightedOptionIndex(newNextIndex);
1249
- menuRef?.children[newNextIndex]?.scrollIntoView?.({
1250
- behavior: "smooth",
1251
- block: "nearest",
1252
- inline: "start",
1253
- });
1254
-
1255
- // If the option is an extra element, focus the extra element. Else focus the input
1256
- if (isExtraElement(optionsWithExtraElements[newNextIndex])) {
1257
- const element = document.getElementById(
1258
- optionsWithExtraElements[newNextIndex].sectionLabel,
1259
- ) as HTMLElement;
1260
- element?.focus();
1261
- } else {
1262
- inputRef?.current?.focus();
1263
- }
1264
- break;
1265
-
1266
- case KeyboardAction.Select:
1267
- menuRef?.children[highlightedOptionIndex]?.scrollIntoView?.({
1268
- behavior: "smooth",
1269
- block: "nearest",
1270
- inline: "start",
1271
- });
1272
-
1273
- // If the option is an extra element, click the extra element. Else check it is not an option group and select the option
1274
- if (
1275
- isExtraElement(optionsWithExtraElements[highlightedOptionIndex])
1276
- ) {
1277
- const element = document.getElementById(
1278
- optionsWithExtraElements[highlightedOptionIndex].sectionLabel,
1279
- ) as HTMLElement;
1280
- element?.click();
1281
- } else if (
1282
- !isOptionGroup(optionsWithExtraElements[highlightedOptionIndex])
1283
- ) {
1284
- onOptionSelect(optionsWithExtraElements[highlightedOptionIndex]);
1285
- }
1286
-
1287
- break;
1288
- }
1289
- },
1290
- [
1291
- highlightedOptionIndex,
1292
- options,
1293
- onOptionSelect,
1294
- extraElementFocused,
1295
- maxIndex,
1296
- optionsWithExtraElements,
1297
- ],
1298
- );
1299
- useCustomKeyboardNavigation({ onRequestHighlightChange });
1300
- ```
1301
-
1302
-
1303
- ## Props
1304
-
1305
- ### Web
1306
-
1307
- | Prop | Type | Required | Default | Description |
1308
- |------|------|----------|---------|-------------|
1309
- | `getOptions` | `(newInputText: string) => (GenericOption | GenericGetOptionsValue)[] | Promise<(GenericOption | GenericGetOptionsValue)[]>` | Yes | — | Called as the user types in the input. The autocomplete will display what is returned from this method to the user as... |
1310
- | `onChange` | `(newValue?: GenericOptionValue) => void` | Yes | — | Simplified onChange handler that only provides the new value. @param newValue |
1311
- | `placeholder` | `string` | Yes | — | Hint text that goes above the value once the form is filled out. |
1312
- | `value` | `Option` | Yes | — | Set Autocomplete value. |
1313
- | `allowFreeForm` | `boolean` | No | `true` | Allow the autocomplete to use values not from the drop down menu. |
1314
- | `clearable` | `Clearable` | No | — | Add a clear action on the input that clears the value. You should always use `while-editing` if you want the input t... |
1315
- | `customRenderMenu` | `(props: CustomOptionsMenuProp<GenericOption | GenericGetOptionsValue, GenericOptionValue>) => ReactElement<...>` | No | — | Override the content rendered in the menu. |
1316
- | `debounce` | `number` | No | `300` | Debounce in milliseconds for getOptions |
1317
- | `description` | `ReactNode` | No | — | Further description of the input, can be used for a hint. |
1318
- | `initialOptions` | `GenericOption[]` | No | — | Initial options to display in the autocomplete. |
1319
- | `inputRef` | `RefObject<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>` | No | — | @deprecated Use `ref` instead. |
1320
- | `invalid` | `boolean` | No | — | Highlights the field red to indicate an error. |
1321
- | `name` | `string` | No | — | Name of the input. |
1322
- | `onBlur` | `(event?: FocusEvent<Element, Element>) => void` | No | — | Blur callback. |
1323
- | `onFocus` | `(event?: FocusEvent<Element, Element>) => void` | No | — | Focus callback. |
1324
- | `prefix` | `Affix` | No | — | Adds a prefix label and icon to the field |
1325
- | `ref` | `Ref<HTMLInputElement>` | No | — | Allows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (... |
1326
- | `size` | `"large" | "small"` | No | — | Adjusts the interface to either have small or large spacing. |
1327
- | `suffix` | `{ onClick: () => void; readonly ariaLabel: string; readonly icon: IconNames; readonly label?: string; } | { onClick?: never; ariaLabel?: never; readonly label?: string; readonly icon?: IconNames; }` | No | — | Adds a suffix label and icon with an optional action to the field |
1328
- | `version` | `1` | No | `1` | Version of the component to use. |