docs/documentation/components/select-menu.mdx
The SelectMenu component is an advanced interaction pattern which allows selection of multiple items from a dropdown list.
It can be used as a substitute for the native multiple select element.
The SelectMenu builds on top of the Popover component
and uses react-tiny-virtual-list for the rendering of the virtualized list of options.
This example shows basic usage with a single selected item.
function SingleSelectedItemExample() {
const [selected, setSelected] = React.useState(null)
return (
<SelectMenu
title="Select name"
options={['Apple', 'Apricot', 'Banana', 'Cherry', 'Cucumber'].map((label) => ({ label, value: label }))}
selected={selected}
onSelect={(item) => setSelected(item.value)}
>
<Button>{selected || 'Select name...'}</Button>
</SelectMenu>
)
}
The title and default filter can be removed via the hasFilter and hasTitle props, respectively.
function NoFilterAndTitleSelectedMenuExample() {
const [selected, setSelected] = React.useState(null)
return (
<SelectMenu
title="Select name"
options={['Apple', 'Apricot', 'Banana', 'Cherry', 'Cucumber'].map((label) => ({ label, value: label }))}
hasFilter={false}
hasTitle={false}
selected={selected}
onSelect={(item) => setSelected(item.value)}
>
<Button>{selected || 'Select name...'}</Button>
</SelectMenu>
)
}
You can make the size of a SelectMenu custom via the width and height properties.
function CustomSizeSelectedMenuExample() {
const [selected, setSelected] = React.useState(null)
return (
<SelectMenu
title="Select name"
options={['Apple', 'Apricot', 'Banana', 'Cherry', 'Cucumber'].map((label) => ({ label, value: label }))}
width={280}
height={200}
selected={selected}
onSelect={(item) => setSelected(item.value)}
>
<Button>{selected || 'Select name...'}</Button>
</SelectMenu>
)
}
You can change the position of SelectMenu by changing its position property to Position.TOP,
Position.TOP_LEFT,
Position.TOP_RIGHT,
Position.BOTTOM,
Position.BOTTOM_LEFT, or
Position.BOTTOM_RIGHT,
function CustomPositionSelectMenuExample() {
const [selected, setSelected] = React.useState(null)
return (
<SelectMenu
title="Select name"
options={['Apple', 'Apricot', 'Banana', 'Cherry', 'Cucumber'].map((label) => ({ label, value: label }))}
position={Position.TOP}
selected={selected}
onSelect={(item) => setSelected(item.value)}
>
<Button>{selected || 'Select name...'}</Button>
</SelectMenu>
)
}
It's possible to display a custom empty view instead of options list via emptyView, when there are no properties supplied.
Note that empty view won't be shown when options are being filtered and there are no search results.
<SelectMenu
title="Empty view"
options={[]}
emptyView={
<Pane height="100%" display="flex" alignItems="center" justifyContent="center">
<Text size={300}>No options found</Text>
</Pane>
}
>
<Button>Select option...</Button>
</SelectMenu>
It's possible to include icons in the menu list.
<SelectMenu
title="Options with icons"
options={[
{
label: 'Apple',
value: 'Apple',
icon: 'https://upload.wikimedia.org/wikipedia/commons/d/d2/Malus-Boskoop_organic.jpg',
},
{
label: 'Banana',
value: 'Banana',
icon:
'https://upload.wikimedia.org/wikipedia/commons/thumb/4/44/Bananas_white_background_DS.jpg/2560px-Bananas_white_background_DS.jpg',
},
]}
>
<Button>Select option...</Button>
</SelectMenu>
This example shows usage with multiple selected items. As users click on selected values to remove them, you can update state. Note that this is just an example.
function MultiSelectSelectMenuExample() {
const [options] = React.useState(
['Apple', 'Apricot', 'Banana', 'Cherry', 'Cucumber'].map((label) => ({
label,
value: label,
}))
)
const [selectedItemsState, setSelectedItems] = React.useState([])
const [selectedItemNamesState, setSelectedItemNames] = React.useState(null)
return (
<SelectMenu
isMultiSelect
title="Select multiple names"
options={options}
selected={selectedItemsState}
onSelect={(item) => {
const selected = [...selectedItemsState, item.value]
const selectedItems = selected
const selectedItemsLength = selectedItems.length
let selectedNames = ''
if (selectedItemsLength === 0) {
selectedNames = ''
} else if (selectedItemsLength === 1) {
selectedNames = selectedItems.toString()
} else if (selectedItemsLength > 1) {
selectedNames = selectedItemsLength.toString() + ' selected...'
}
setSelectedItems(selectedItems)
setSelectedItemNames(selectedNames)
}}
onDeselect={(item) => {
const deselectedItemIndex = selectedItemsState.indexOf(item.value)
const selectedItems = selectedItemsState.filter((_item, i) => i !== deselectedItemIndex)
const selectedItemsLength = selectedItems.length
let selectedNames = ''
if (selectedItemsLength === 0) {
selectedNames = ''
} else if (selectedItemsLength === 1) {
selectedNames = selectedItems.toString()
} else if (selectedItemsLength > 1) {
selectedNames = selectedItemsLength.toString() + ' selected...'
}
setSelectedItems(selectedItems)
setSelectedItemNames(selectedNames)
}}
>
<Button>{selectedItemNamesState || 'Select multiple...'}</Button>
</SelectMenu>
)
}
You can attach a callback handler for the search filter via the onFilterChange property.
function OnFilterChangeSelectMenuExample() {
const [selected, setSelected] = React.useState(null)
const [filter, setFilter] = React.useState('')
return (
<Pane>
<Pane marginBottom={8}>
<Text>Filter value: {filter}</Text>
</Pane>
<SelectMenu
title="Select name"
onFilterChange={(filter) => setFilter(filter)}
options={['Apple', 'Apricot', 'Banana', 'Cherry', 'Cucumber'].map((label) => ({ label, value: label }))}
selected={selected}
onSelect={(item) => setSelected(item.value)}
>
<Button>{selected || 'Select name...'}</Button>
</SelectMenu>
</Pane>
)
}
This example shows basic usage for disabling some options. Options that are disabled cannot be clicked and their labels are muted.
function DisabledItemSelectMenuExample() {
const [selected, setSelected] = React.useState(null)
return (
<Pane>
<SelectMenu
title="Select Option"
options={[
{ label: 'Disabled', value: 'disabled', disabled: true },
{ label: 'Not Disabled', value: 'not-disabled' },
]}
selected={selected}
onSelect={(item) => setSelected(item.value)}
>
<Button>{selected || 'Select name...'}</Button>
</SelectMenu>
</Pane>
)
}
This example shows how one should use the titleView property to pass in a custom title.
function CustomTitleSelectMenuExample() {
const [selected, setSelected] = React.useState(null)
return (
<Pane>
<SelectMenu
title="Select Option"
options={[
{ label: 'Option 1', value: 'option-1' },
{ label: 'Option 2', value: 'option-2' },
]}
tooltipContent="Choose one of the options below."
titleView={({ close, title, headerHeight }) => {
return (
<Pane
display="flex"
alignItems="center"
borderBottom="default"
padding={8}
height={headerHeight}
boxSizing="border-box"
>
<Pane flex="1" display="flex" alignItems="center">
<Heading size={400}>{title}</Heading>
<Tooltip content="Pick one of the options below">
<HelpIcon size={12} marginLeft={4} />
</Tooltip>
</Pane>
<IconButton icon={CrossIcon} appearance="minimal" height={24} onClick={close} />
</Pane>
)
}}
selected={selected}
onSelect={(item) => setSelected(item.value)}
>
<Button>{selected || 'Select name...'}</Button>
</SelectMenu>
</Pane>
)
}
It's possible to change the filter placeholder and filter icon through the filterPlaceholder and filterIcon props.
Note that the icon can be one found in Segment's various icons, or none.
function CustomPlaceholderAndIconSelectMenuExample() {
const [selected, setSelected] = React.useState(null)
return (
<Pane>
<SelectMenu
title="Select Option"
options={['Comedy', 'Drama', 'Fantasy', 'Family', 'Action'].map((label) => ({ label, value: label }))}
selected={selected}
filterPlaceholder="Choose a genre"
filterIcon={FilmIcon}
onSelect={(item) => setSelected(item.value)}
>
<Button>{selected || 'Select name...'}</Button>
</SelectMenu>
</Pane>
)
}