Honeycomb

legacy-autocomplete-options

Deprecated

This component is deprecated since v12.0.0. It is being kept to ease the transition to the new Autocomplete controller. If you are using this component, please consider migrating to the new one, as it is easier to use, has more features and has less bugs.

Props

Prop nameTypeDefaultDescription
appearance"boxed" | "flat" | undefinedboxed

Appearance of the dropdown

extraClassesstring | undefined

Injection in className

innerRefRef<HTMLDivElement> | undefined

React ref forwarded to the wrapping element.

labelstringRequired

Provides aria-label for the list of options, required by assistive technologies

optionHasSubtitleboolean | undefinedtrue

If the options should display the optional "subtitle" field

optionPropsobject | undefined{}

spread on every option button (use for special handling)

optionsToDisplaynumber | undefined3

amount of options to be displayed before overflowing

renderOption((item: LegacyAutocompleteOptionType) => ReactNode) | undefined

render prop for option in a dropdown, receives `option` as param

highlightQuerystring | 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 options
  • key: Used to identify the group within the list
  • options: 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 renderOption function the component is not able to highlight the query text passed as highlightQuery prop, 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>