legacy-autocomplete-options
Props
| Prop name | Type | Default | Description |
|---|---|---|---|
appearance | "boxed" | "flat" | undefined | boxed | Appearance of the dropdown |
extraClasses | string | undefined | Injection in className | |
innerRef | Ref<HTMLDivElement> | undefined | React ref forwarded to the wrapping element. | |
label | string | Required | Provides aria-label for the list of options, required by assistive technologies |
optionHasSubtitle | boolean | undefined | true | If the options should display the optional "subtitle" field |
optionProps | object | undefined | {} | spread on every option button (use for special handling) |
optionsToDisplay | number | undefined | 3 | amount of options to be displayed before overflowing |
renderOption | ((item: LegacyAutocompleteOptionType) => ReactNode) | undefined | render prop for option in a dropdown, receives `option` as param | |
highlightQuery | string | undefined | The input query to highlight in the options. since v. 9.8.0 |
LegacyAutocompleteOptions component is used to display the list of possible options for Autocomplete.
You can customize its appearance, number of options to display or pass custom render function to fully control how individual options are rendered.
import { LegacyAutocomplete, LegacyAutocompleteInput, LegacyAutocompleteOptions, Fieldset } from '@flixbus/honeycomb-react'; // -------------------------- // Mock data and the filter function const AutocompleteMockData = [ { title: 'Bologna', subtitle: 'Don\'t forget to eat tortellini', }, { title: 'Belo Horizonte', subtitle: 'Ready for a really long ride?', }, { title: 'Belgium', subtitle: '🍫 and 🍻', }, { title: 'Bermuda', subtitle: 'Bring your 🎣', }, { title: 'Berlin', subtitle: 'The place to be', }, ]; const [data, setData] = React.useState(AutocompleteMockData); const [value, setValue] = React.useState(''); const [highlight, setHighlight] = React.useState(); const [loading, setLoading] = React.useState(false); function filterAutocompleteMockData(searchQuery, data) { return new Promise((resolve) => { setTimeout(() => { const res = data.filter(item => ( item.title.toLowerCase().includes(searchQuery.toLowerCase()) )); resolve(res); }, 200); }); } // -------------------------- <Fieldset> <LegacyAutocomplete options={data} value={value} onChange={(e) => { setLoading(true) }} onDebounce={(e) => { setLoading(true) setHighlight(e.target.value) filterAutocompleteMockData(e.target.value, AutocompleteMockData).then( options => {setData(options);setLoading(false);} ) }} onSelect={(item) => { setHighlight() setValue(item.title); setData([]); }} > <LegacyAutocompleteInput id="autocomplete-1" placeholder="Try to type Berlin slowly" label="Place:" loading={loading} type="search" autoComplete="off" info={data.length === 0 ? '' : `${data.length} option${data.length > 1 ? 's' : ''}.`} /> <LegacyAutocompleteOptions appearance="flat" label="Places" optionsToDisplay={3} optionHasSubtitle={false} highlightQuery={highlight} /> </LegacyAutocomplete> </Fieldset>
Option grouping
To group up the options, to do so you need to modify the data structure.
The group keys are all required:
group: The group name, will be displayed above the optionskey: Used to identify the group within the listoptions: A list of the group options in the same format as you would provide for the options prop if they were not grouped
In the next example we show options grouped by continent, notice the dataSize prop to show the available options in the input info prop.
Please notice that you cannot mix up grouped and ungrouped options.
import { LegacyAutocomplete, LegacyAutocompleteInput, LegacyAutocompleteOptions, Fieldset, a11yHelpers } from '@flixbus/honeycomb-react'; import { Icon, IconTimeSolid } from '@flixbus/honeycomb-icons-react'; // -------------------------- // Mock data and the filter function const AutocompleteMockData = [{ group: <><Icon InlineIcon={IconTimeSolid} aria-hidden="true" /> Recent Search</>, key: 'recent', options: [{ title: 'Brisbane', subtitle: 'The River City', recent: true, }], }, { group: 'Americas', key: 'americas', options: [{ title: 'Boston', subtitle: 'The city of champions', }, { title: 'Belo Horizonte', subtitle: 'Ready for a really long ride?', }, { title: 'Bermuda', subtitle: 'Bring your 🎣', }, { title: 'Buenos Aires', subtitle: 'The Paris of South America', }, { title: 'Bogotá', subtitle: 'The melting pot of Colombia', }], }, { group: 'Europe', key: 'europe', options: [{ title: 'Bologna', subtitle: 'Don\'t forget to eat tortellini', }, { title: 'Belfast', subtitle: 'The city of Titanic', }, { title: 'Belgium', subtitle: '🍫 and 🍻', }, { title: 'Berlin', subtitle: 'The place to be', }, { title: 'Barcelona', subtitle: 'The city of Gaudi', }], }, { group: 'Asia', key: 'asia', options: [{ title: 'Beijing', subtitle: 'The capital of China', }] }, ]; const inputRef = React.useRef(null); const [data, setData] = React.useState(AutocompleteMockData); const [dataSize, setDataSize] = React.useState(0); const [value, setValue] = React.useState(''); const [highlight, setHighlight] = React.useState(); const [loading, setLoading] = React.useState(false); function filterAutocompleteMockData(searchQuery, data) { return new Promise((resolve) => { setTimeout(() => { const res = data.filter(item => { if (item.options && item.options.length > 0) { const filteredOptions = item.options.filter((item) => item.title.toLowerCase().includes(searchQuery.toLowerCase())) || item.subtitle.toLowerCase().includes(searchQuery.toLowerCase() ); item.options = filteredOptions; return item; } else if (item.title) { return item.title.toLowerCase().includes(searchQuery.toLowerCase()) } }); resolve(res); }, 200); }); } function updateData(dataSet) { setData(dataSet); setDataSize(dataSet.reduce((acc, curr) => acc + curr.options.length, 0)); } // -------------------------- <Fieldset> <LegacyAutocomplete options={data} value={value} onFocus={() => { updateData(AutocompleteMockData); }} onChange={(e) => { setLoading(true) }} onDebounce={(e) => { setLoading(true); setHighlight(e.target.value); filterAutocompleteMockData(e.target.value, AutocompleteMockData).then( (filteredOptions) => { updateData(filteredOptions); setLoading(false); } ) }} onSelect={(item) => { setValue(item.title); setHighlight(''); updateData([]); }} > <LegacyAutocompleteInput id="autocomplete-grouping" innerRef={inputRef} placeholder="Try to type Berlin slowly" label="Place:" loading={loading} type="search" autoComplete="off" info={( <span aria-live="polite" className={a11yHelpers().srOnly}> {dataSize === 0 ? '' : `${dataSize} option${dataSize > 1 ? 's' : ''}.`} </span> )} /> <LegacyAutocompleteOptions appearance="boxed" label="Places" optionsToDisplay={6} highlightQuery={highlight} /> </LegacyAutocomplete> </Fieldset>
Custom render with renderOption function
You can customize how do you want your options to look like by utilizing renderOption prop.
This render prop allows having custom content for every option in the dropdown and receives an option item as a param.
Please notice:
- When using the
renderOptionfunction the component is not able to highlight the query text passed ashighlightQueryprop, so make sure to handle it on your side.* - It is not ideal to render headings inside of the autocomplete options, so in the example bellow we use the heading with
Elem="span"to replace the HTML heading element with one that does not disrupt the markup.
import { LegacyAutocomplete, LegacyAutocompleteInput, LegacyAutocompleteOptions, Fieldset, Tag, Heading, a11yHelpers } from '@flixbus/honeycomb-react'; import { Icon, IconTimeSolid } from '@flixbus/honeycomb-icons-react'; const inputRef = React.useRef(null); const [data, setData] = React.useState([]); const [dataSize, setDataSize] = React.useState(0); const [value, setValue] = React.useState(''); const [loading, setLoading] = React.useState(false); // -------------------------- // Mock data and the filter function const AutocompleteMockData = [{ group: <><Icon InlineIcon={IconTimeSolid} aria-hidden="true" /> Recent Search</>, key: 'recent', options: [{ title: 'Brisbane', subtitle: 'The River City', recent: true, }], }, { group: 'Americas', key: 'americas', options: [{ title: 'Boston', subtitle: 'The city of champions', }, { title: 'Belo Horizonte', subtitle: 'Ready for a really long ride?', }, { title: 'Bermuda', subtitle: 'Bring your 🎣', }, { title: 'Buenos Aires', subtitle: 'The Paris of South America', }, { title: 'Bogotá', subtitle: 'The melting pot of Colombia', }], }, { group: 'Europe', key: 'europe', options: [{ title: 'Bologna', subtitle: 'Don\'t forget to eat tortellini', }, { title: 'Belfast', subtitle: 'The city of Titanic', }, { title: 'Belgium', subtitle: '🍫 and 🍻', }, { title: 'Berlin', subtitle: 'The place to be', }, { title: 'Barcelona', subtitle: 'The city of Gaudi', }], }, { group: 'Asia', key: 'asia', options: [{ title: 'Beijing', subtitle: 'The capital of China', }] }, ]; function filterAutocompleteMockData(searchQuery, data) { return new Promise((resolve) => { setTimeout(() => { const res = data.filter(item => { if (item.options && item.options.length > 0) { const filteredOptions = item.options.filter(item => item.title.toLowerCase().includes(searchQuery.toLowerCase())); item.options = filteredOptions; return item; } else if (item.title) { return item.title.toLowerCase().includes(searchQuery.toLowerCase()) } }); resolve(res); }, 200); }); } function updateData(dataSet) { setData(dataSet); setDataSize(dataSet.reduce((acc, curr) => acc + curr.options.length, 0)); } // -------------------------- <Fieldset> <LegacyAutocomplete options={data} value={value} onFocus={() => { updateData(AutocompleteMockData); }} onChange={(e) => { setLoading(true) }} onDebounce={(e) => { setLoading(true) filterAutocompleteMockData(e.target.value, AutocompleteMockData).then( options => { updateData(options); setLoading(false); } ) }} onSelect={(item) => { setValue(item.title); updateData([]); }} > <LegacyAutocompleteInput id="autocomplete-render" innerRef={inputRef} placeholder="Try to type Berlin slowly" label="Place:" loading={loading} type="search" autoComplete="off" info={( <span className={a11yHelpers().srOnly}> {dataSize === 0 ? 'No options found.' : `${dataSize} option${dataSize > 1 ? 's' : ''}.`} </span> )} /> <LegacyAutocompleteOptions appearance="boxed" label="Places" optionsToDisplay={6} optionHasSubtitle={false} renderOption={(item) => ( <Heading Elem="span" size={4} flushSpace sectionHeader> {item.title} <Tag small display="outlined">{item.subtitle}</Tag> </Heading> )} /> </LegacyAutocomplete> </Fieldset>
Option widget
The option object can have a widget which you cna use to attach a component that should be rendered alongside the option.
For example: a clear button for recent searches or an expand button for collapsible options.
import { LegacyAutocomplete, LegacyAutocompleteInput, LegacyAutocompleteOptions, Fieldset, Tag, Heading, CloseButton } from '@flixbus/honeycomb-react'; import { Icon, IconTimeSolid } from '@flixbus/honeycomb-icons-react'; const inputRef = React.useRef(null); const [data, setData] = React.useState([]); const [value, setValue] = React.useState(''); const [loading, setLoading] = React.useState(false); // -------------------------- // Mock data and the filter function const AutocompleteMockData = [{ group: <><Icon InlineIcon={IconTimeSolid} aria-hidden="true" /> Recent Search</>, key: 'recent', options: [{ title: 'Brisbane', subtitle: 'The River City', recent: true, widget: ( <CloseButton size="sm" aria-label={`Remove Brisbane from history`} onClick={() => console.log('Removing Brisbane from history...')} onKeyDown={(event) => { // stops bubbling of Enter key press from close button to not trigger option select event.stopPropagation(); }} /> ), }], }, { group: 'Americas', key: 'americas', options: [{ title: 'Boston', subtitle: 'The city of champions', }, { title: 'Belo Horizonte', subtitle: 'Ready for a really long ride?', }, { title: 'Bermuda', subtitle: 'Bring your 🎣', }, { title: 'Buenos Aires', subtitle: 'The Paris of South America', }, { title: 'Bogotá', subtitle: 'The melting pot of Colombia', }], }, { group: 'Europe', key: 'europe', options: [{ title: 'Bologna', subtitle: 'Don\'t forget to eat tortellini', }, { title: 'Belfast', subtitle: 'The city of Titanic', }, { title: 'Belgium', subtitle: '🍫 and 🍻', }, { title: 'Berlin', subtitle: 'The place to be', }, { title: 'Barcelona', subtitle: 'The city of Gaudi', }], }, { group: 'Asia', key: 'asia', options: [{ title: 'Beijing', subtitle: 'The capital of China', }] }, ]; function filterAutocompleteMockData(searchQuery, data) { return new Promise((resolve) => { setTimeout(() => { const res = data.filter(item => { if (item.options && item.options.length > 0) { const filteredOptions = item.options.filter(item => item.title.toLowerCase().includes(searchQuery.toLowerCase())); item.options = filteredOptions; return item; } else if (item.title) { return item.title.toLowerCase().includes(searchQuery.toLowerCase()) } }); resolve(res); }, 200); }); } // -------------------------- <Fieldset> <LegacyAutocomplete optionsListVisible options={data} value={value} onFocus={() => { setData(AutocompleteMockData); }} onChange={(e) => { setLoading(true) }} onDebounce={(e) => { setLoading(true) filterAutocompleteMockData(e.target.value, AutocompleteMockData).then( options => {setData(options); setLoading(false);} ) }} onSelect={(item) => { setValue(item.title); setData([]); }} > <LegacyAutocompleteInput id="autocomplete-render" innerRef={inputRef} placeholder="Try to type Berlin slowly" label="Place:" loading={loading} type="search" autoComplete="off" /> <LegacyAutocompleteOptions appearance="boxed" label="Places" optionsToDisplay={6} optionHasSubtitle={false} renderOption={(item) => ( <Heading Elem="span" size={4} flushSpace sectionHeader> {item.title} <Tag small display="outlined">{item.subtitle}</Tag> </Heading> )} /> </LegacyAutocomplete> </Fieldset>
Rest option and group props
The optionProps is spread and forwarded to all options evenly, and you can pass individual rest
props to each option by using the rest key on the option object.
Notice the ids passed down to each option on the example bellow:
import { LegacyAutocomplete, LegacyAutocompleteInput, LegacyAutocompleteOptions, Fieldset, Tag, Heading } from '@flixbus/honeycomb-react'; import { Icon, IconTimeSolid } from '@flixbus/honeycomb-icons-react'; const [data, setData] = React.useState([]); const [value, setValue] = React.useState(''); const [highlight, setHighlight] = React.useState(); const [loading, setLoading] = React.useState(false); // -------------------------- // Mock data and the filter function const AutocompleteMockData = [{ group: <><Icon InlineIcon={IconTimeSolid} aria-hidden="true" /> Recent Search</>, key: 'recent', options: [ { title: 'Brisbane', rest: { id: 'option-brisbane' } }, ], }, { group: 'Americas', key: 'americas', options: [ { title: 'Boston', rest: { id: 'option-boston' } }, { title: 'Belo Horizonte', rest: { id: 'option-belo-horizonte' } }, { title: 'Bermuda', rest: { id: 'option-bermuda' } }, { title: 'Buenos Aires', rest: { id: 'option-buenos-aires' } }, { title: 'Bogotá', rest: { id: 'option-bogota' } }, ], }, { group: 'Europe', key: 'europe', options: [ { title: 'Bologna', rest: { id: 'option-bologna' } }, { title: 'Belfast', rest: { id: 'option-belfast' } }, { title: 'Belgium', rest: { id: 'option-belgium' } }, { title: 'Berlin', rest: { id: 'option-berlin' } }, { title: 'Barcelona', rest: { id: 'option-barcelona' } }, ], }, { group: 'Asia', key: 'asia', options: [ { title: 'Beijing', rest: { id: 'option-beijing' } }, ], }]; function filterAutocompleteMockData(searchQuery, data) { return new Promise((resolve) => { setTimeout(() => { const res = data.filter(item => { if (item.options && item.options.length > 0) { const filteredOptions = item.options.filter(item => item.title.toLowerCase().includes(searchQuery.toLowerCase())); item.options = filteredOptions; return item; } else if (item.title) { return item.title.toLowerCase().includes(searchQuery.toLowerCase()) } }); resolve(res); }, 200); }); } // -------------------------- <Fieldset> <LegacyAutocomplete optionsListVisible options={data} value={value} onChange={(e) => { setLoading(true); }} onDebounce={(e) => { setLoading(true); setHighlight(e.target.value); filterAutocompleteMockData(e.target.value, AutocompleteMockData) .then((options) => { setData(options); setLoading(false); }); }} onSelect={(item) => { setValue(item.title); setHighlight(''); setData([]); }} > <LegacyAutocompleteInput id="autocomplete-rest" placeholder="Try to type Berlin slowly" label="Place:" loading={loading} type="search" autoComplete="off" /> <LegacyAutocompleteOptions label="Places" optionProps={{ 'data-foo': 'bar' }} optionsToDisplay={6} optionHasSubtitle={false} highlightQuery={highlight} /> </LegacyAutocomplete> </Fieldset>