Dropdown
An accessible, custom alternative to the native Select component.
React
The Dropdown component provides a more flexible alternative to the native Select component to implement things like populating options from a search API request.
Multiple Selection
We currently only support selecting a single item, if you need multi-select support, please request it here.
Installation
The Dropdown component is available as a separate package, @hoverinc/dropdown that depends on @hoverinc/design-system-react-web.
yarn add @hoverinc/dropdownTheme
The theme for the Dropdown component must also be added to your DesignSystemProvider
import { DesignSystemProvider } from '@hoverinc/design-system-react-web';
import { Dropdown } from '@hoverinc/design-system-react-web/extra/theme';
interface AppProps {
children: React.ReactNode;
}
const App = ({ children }: AppProps) => (
<DesignSystemProvider extraComponentThemes={{ Dropdown }}>
{children}
</DesignSystemProvider>
);
export { App };Simple Select
Dropdown can behave just like a simple native Select component, albeit a
bit prettier. That said, a native Select might be
the right choice here. See below for use cases where Dropdown shines.
The inPortal prop tells Dropdown to open its menu in a
Portal in cases where overflow: hidden or
adjacent components may interfere with it.
<Dropdown
inPortal
itemToString={item => (item ? item.title : '')}
itemToValue={({ id }) => id}
items={[
{ title: 'One', id: 1 },
{ title: 'Two', id: 2 },
{ title: 'Three', id: 3 },
{ title: 'Four', id: 4 },
]}
placeholder="Select..."
/>Within a Field
Wrap Dropdown in a Field to get the same
floating label, error and helper text treatment as a Field around a native
Select. Pass the label to Field and it renders
inside the control, floating up out of the way once a value is selected.
No extra wiring is needed: do not add your own FieldLabel, and do not override
.hover-dropdown styles to make room for the label.
<Field label="Bird" name="bird">
<Dropdown
inPortal
itemToString={item => (item ? item.title : '')}
itemToValue={({ id }) => id}
items={mocks.dropdown}
/>
</Field>An error on the Field styles the control as invalid and is announced to
assistive technology.
<Field error="Please select a bird" label="Bird" name="bird">
<Dropdown
inPortal
itemToString={item => (item ? item.title : '')}
itemToValue={({ id }) => id}
items={mocks.dropdown}
/>
</Field>Filtering
Use the onInputValue change prop to implement filtering on items.
() => {
const mockItems = mocks.dropdown;
const [items, setItems] = useState(mockItems);
return (
<Dropdown
inPortal
itemToString={item => (item ? item.title : '')}
itemToValue={({ id }) => id}
items={items}
onInputValueChange={({ inputValue }) => {
setItems(
inputValue
? mockItems.filter(({ title }) =>
title.toLowerCase().includes(inputValue.toLowerCase()),
)
: mockItems,
);
}}
placeholder="Filtering..."
/>
);
};Asynchronous
Set the isLoading prop to indicate the state of an asynchronous request when
populating items from an API.
() => {
const mockItems = mocks.dropdown;
const [isLoading, setIsLoading] = useState(false);
const [items, setItems] = useState(mockItems);
return (
<Dropdown
isLoading={isLoading}
inPortal
items={items}
itemToString={item => (item ? item.title : '')}
itemToValue={({ id }) => id}
onInputValueChange={({ inputValue }) => {
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
setItems(
inputValue
? mockItems.filter(({ title }) =>
title.toLowerCase().includes(inputValue.toLowerCase()),
)
: mockItems,
);
}, 500);
}}
placeholder="Asynchronous"
/>
);
};Icon
Like other form inputs, Dropdown supports displaying an icon in the input.
<Dropdown
inPortal
iconBefore={iconFilter}
itemToString={item => (item ? item.title : '')}
itemToValue={({ id }) => id}
items={[
{ title: 'One', id: 1 },
{ title: 'Two', id: 2 },
{ title: 'Three', id: 3 },
{ title: 'Four', id: 4 },
]}
placeholder="Select..."
/>Item Icons
Include a system icon as the icon property in items to render an icons in the
list.
<Dropdown
inPortal
iconBefore={iconFilter}
itemToString={item => (item ? item.title : '')}
itemToValue={({ id }) => id}
items={[
{ title: 'One', id: 1, icon: iconHeartFilled },
{ title: 'Two', id: 2, icon: iconUser },
{ title: 'Three', id: 3, icon: iconFile },
{ title: 'Four', id: 4, icon: iconExternalLink },
]}
placeholder="Select..."
/>Combobox
actAsComboboxkeeps the text input visible after selection and preserves the typed value on blur orEscape.
Autocomplete Mode (actAsCombobox)
Use actAsCombobox when the dropdown should behave like an autocomplete input.
() => {
const mockItems = mocks.dropdown;
const [items, setItems] = useState(mockItems);
return (
<Dropdown
actAsCombobox
inPortal
itemToString={item => (item ? item.title : '')}
itemToValue={({ id }) => id}
items={items}
onInputValueChange={({ inputValue }) => {
setItems(
inputValue
? mockItems.filter(({ title }) =>
title.toLowerCase().includes(inputValue.toLowerCase()),
)
: mockItems,
);
}}
placeholder="Search..."
/>
);
};