Autocomplete

Description

The Autocomplete component is a combination of an Input and a Dropdown, also called ComboBox. During typing, matching data items gets suggested in an option menu (listbox).

Type Ahead and ComboBox

The Autocomplete component may also be known as Type Ahead or ComboBox. But autocomplete describes the purpose more precisely and descriptive, therefore Eufemia is using this term.

When to use it:

Use it for both small autocomplete purposes and large (async) data set searches. The component supports two ways of showing ProgressIndicator.

You may check out the Dropdown component for more details on how to use it etc. They both share the same DrawerList.

Highlighting

Words found during typing are highlighted. The rules are:

  1. The two first words will match the beginning of a word
  2. The third word will match inside a word (can be changed with search_in_word_index)
  3. Case insensitive

Using Components inside content

It is not possible to wrap them inside React Components. The reason is that the Autocomplete component needs to know what data it wants to search for before your React Component has rendered. But also, the component can't update the HTML to make the bold highlighting – "after" your component has rendered.

That means you can't run a component that will render as soon as it is displayed.

If you need to format numbers, then do it before you send in the data content.

It is possible to wrap your content inside one HTML Element. Nested elements are not supported.

To wrap your content only visually, you can provide your wrappers inside an array:

{
content: [
<IconPrimary icon="bell" />,
<span className="custom-selector-a">The Shawshank Redemption</span>,
<span className="custom-selector-b">The Dark Knight</span>,
// etc.
<NumberFormat value={1234} />, // <-- Not searchable nor highlightable
]
}

or you can provide it inside a fragment:

{
content: <>
<IconPrimary icon="bell" />
<span className="custom-selector-a">The Shawshank Redemption</span>
<span className="custom-selector-b">The Dark Knight</span>
</>
}

and if you need to decouple the searchable content from what's displayed, then you can put your searchable content inside search_content:

{
content: ['your visual content'],
search_content: ['your search content'],
}

Numbers

Numbers are often different than a word filter. You can use search_numbers={true} to enable a number specialized filtering. See example in the demos.

Now the user could search for e.g. bank account numbers by just entering 201, even if you format it like 2000 12 34567 (e.g. use format(20001234567, { ban: true }) from /components/number-format/NumberUtils)

Screen reader support

To enhance screen-reader usage, this component uses aria-live to announce the number of options found (aria_live_options).

Custom size

.dnb-autocomplete {
--autocomplete-width: 20rem; /* custom width */
}

You can also set the width directly, but then it has to be defined like so (including min-width):

/** Because of the included label/status etc. we target the "__shell" */
.dnb-autocomplete__shell {
width: 10rem;
}
/** In order to change only the drawer-list width */
.dnb-autocomplete .dnb-drawer-list__root {
width: 10rem;
}

Dynamically change data

You can manipulate the used data dynamically, either by changing the data property or during user events like on_type or on_focus. The following properties and methods are there to use:

Methods

  • updateData replace all data entries.
  • emptyData remove all data entries.
  • setInputValue update the input value.
  • showIndicator shows a progress indicator instead of the icon (inside the input).
  • hideIndicator hides the progress indicator inside the input.
  • showIndicatorItem shows an item with a ProgressIndicator status as an data option item.
  • showNoOptionsItem shows the "no entries found" status as an data option item.
  • setVisible shows the DrawerList.
  • setHidden hides the DrawerList.
  • showAllItems shows all DrawerList items.
  • setMode switch the mode during runtime.
  • debounce a debounce method with a cancel invocation method on repeating calls

Properties

  • dataList contains all the data entries

Example

<Autocomplete
on_focus={({ updateData, showIndicator }) => {
showIndicator()
setTimeout(() => {
updateData(topMovies)
}, 1e3)
}}
on_type={({ value /* updateData, ... */ }) => {
console.log('on_type', value)
}}
/>

Demos

Default autocomplete

<Autocomplete
data={topMovies}
label="Label:"
/>

Autocomplete with numbers

const numbers = [
format(20001234567, { ban: true }),
format(22233344425, { ban: true }),
format(1234.5, { currency: true }),
format('+47116000', { phone: true }),
]
render(
<Autocomplete
input_value="201"
show_clear_button
label="Label:"
data={numbers}
search_numbers={true}
/>
)

Autocomplete with a custom title

  • keep_value means the input value gets not removed after an input blur happens.
  • show_clear_button means a clear button will show up when the input field contains a value.
<Autocomplete
data={topMovies}
keep_value={true}
show_clear_button={true}
label="Label:"
placeholder="Custom placeholder ..."
on_change={({ data }) => {
console.log('on_change', data)
}}
/>

Async usage, dynamically update data during typing

This example simulates server delay with a timeout and - if it gets debounced, we cancel the timeout.

Also, you may consider using disable_filter if you have a backend doing the search operation.

const onTypeHandler = ({
value,
showIndicator,
hideIndicator,
updateData,
debounce
/* ... */
}) => {
console.log('typed value:', value)
showIndicator()
debounce(
({ value }) => {
console.log('debounced value:', value)
// simulate server delay
const timeout = setTimeout(() => {
// update the drawerList
updateData(topMovies)
hideIndicator()
}, 600)
// cancel invocation method
return () => clearTimeout(timeout)
},
{ value },
250
)
}
render(<Autocomplete
mode="async"
on_type={onTypeHandler}
no_scroll_animation="true"
placeholder="Search ..."
/>)

Update data dynamically on the first focus

const onFocusHandler = ({ updateData, dataList, showIndicatorItem }) => {
if(!dataList.length){
showIndicatorItem()
setTimeout(() => {
updateData(topMovies)
}, 1e3)
}
}
render(<Autocomplete
mode="async"
no_scroll_animation="true"
prevent_selection="true"
on_type={({ value /* updateData, ... */ }) => {
console.log('on_type', value,)
}}
on_focus={onFocusHandler}
/>)

With a Button to toggle the open / close state

NB: Just to show the possibility; the data is given as a function.

<Autocomplete
label="Label:"
show_submit_button="true"
on_change={({ data }) => {
console.log('on_change', data)
}}
>
{() => (topMovies)}
</Autocomplete>

With a predefined input/search value

<Autocomplete
label="Label:"
input_value="the pa ther"
no_animation
on_change={({ data }) => {
console.log('on_change', data)
}}
>
{() => (topMovies)}
</Autocomplete>

Different sizes

Four sizes are available: small, default, medium and large.

<FormRow direction="vertical">
<Autocomplete
label="Label:"
size="default"
bottom
data={() => (topMovies)}
/>
<Autocomplete
label="Label:"
size="medium"
bottom
data={() => (topMovies)}
/>
<Autocomplete
label="Label:"
size="large"
bottom
data={() => (topMovies)}
/>
</FormRow>

Data suffix value

Data is provided as such:

const { locale } = React.useContext(Context)
const data = [
{
suffix_value: (
<NumberFormat currency srLabel="Total:">
{12345678}
</NumberFormat>
),
selected_value: `Brukskonto (${ban})`,
content: ['Brukskonto', ban],
},
]
12 345 678,00 kr
const CustomWidth = styled(Autocomplete)`
.dnb-drawer-list__root,
.dnb-autocomplete__shell {
width: 50vw;
min-width: 15rem;
max-width: 30rem;
}
`
render(
<CustomWidth
value={1}
data={numbers}
size="medium"
input_icon={null}
show_submit_button
label="From account"
label_direction="vertical"
/>
)

Custom width

const CustomWidthOne = styled(Autocomplete)`
.dnb-autocomplete__shell {
width: 10rem;
}
`
const CustomWidthTwo = styled(Autocomplete)`
&.dnb-autocomplete--is-popup .dnb-drawer-list__root {
width: 12rem;
}
`
const CustomWidthThree = styled(Autocomplete)`
/** Change the "__shell" width */
.dnb-autocomplete__shell {
width: 12rem;
}
/** Change the "__list" width */
.dnb-drawer-list__root {
width: 20rem;
}
`
render(<FormRow direction="vertical">
<CustomWidthOne
label="Label:"
label_sr_only
size="default"
icon_position="left"
bottom
data={topMovies}
/>
<CustomWidthTwo
label="Label:"
label_sr_only
size="medium"
bottom
data={topMovies}
/>
<CustomWidthThree
label="Label:"
label_sr_only
size="large"
align_autocomplete="right"
icon_position="right"
input_icon="bell"
bottom
data={topMovies}
/>
</FormRow>)