Dropdown Menu
A dropdown menu displays a list of options for the element that triggers it.
install | yarn add @clayui/drop-down |
---|---|
version | 3.123.0 |
Stable3.123.0View in LexiconCHANGELOG
Example
Introduction
The Dropdown Menu component is designed to display menus that when clicked triggers some action, there are different forms of Menu in Clay/Lexicon. This document describes the main ways to build a menu and recommendations.
Clay provides two possible possibilities to build a Menu, using composition that we call low-level that allows you to add new use cases and adapt them to your use case and components like high-level which are property-setting oriented components. We recommend that you always try to use compositing when you need flexibility, whether it's configuring some internal components that aren't possible in a high-level component or adding your own elements to an item, group, or other use cases that don't exist in Clay.
Content
Dropdown Menu provides two options for building the menu using static content when data does not change during an application's lifecycle and dynamic for content that changes during the application lifecycle or the data comes from the server.
Static
The simplest example of a static menu is rendering a simple list of options.
<DropDown trigger={<Button>Click Me</Button>}>
<DropDown.ItemList>
<DropDown.Item>one</DropDown.Item>
<DropDown.Item>two</DropDown.Item>
<DropDown.Item>three</DropDown.Item>
</DropDown.ItemList>
</DropDown>
Other cases are also possible, for example rendering a group of options with a search bar to filter the options.
<DropDown trigger={<Button>Click Me</Button>}>
<DropDown.Search placeholder="Type to filter" />
<DropDown.ItemList>
<DropDown.Group header="Numbers">
<DropDown.Item>one</DropDown.Item>
<DropDown.Item>two</DropDown.Item>
<DropDown.Item>three</DropDown.Item>
</DropDown.Group>
<DropDown.Group header="Letters">
<DropDown.Item>a</DropDown.Item>
<DropDown.Item>b</DropDown.Item>
<DropDown.Item>c</DropDown.Item>
</DropDown.Group>
</DropDown.ItemList>
</DropDown>
The example above shows how Clay even with the explicit composition of the menu structure manages to provide the filter to work OOTB with static content.
Dynamic
<DropDown items={['one', 'two', 'three']} trigger={<Button>Click Me</Button>}>
{(item) => <DropDown.Item>{item}</DropDown.Item>}
</DropDown>
Compositing for dynamic content becomes simpler and also provides the same features as static, such as OOTB filter and groups. The component is also data agnostic designed from the ground up for this purpose to avoid transformations having to be done at render time.
<DropDown filterKey="name" trigger={<Button>Click Me</Button>}>
<DropDown.Search placeholder="Type to filter" />
<DropDown.ItemList
items={[
{
children: [{name: 'one'}, {name: 'two'}, {name: 'three'}],
name: 'Numbers',
},
{
children: [{name: 'a'}, {name: 'b'}, {name: 'c'}],
name: 'Letters',
},
]}
>
{(item) => (
<DropDown.Group
header={item.name}
items={item.children}
key={item.name}
>
{(item) => (
<DropDown.Item key={item.name}>{item.name}</DropDown.Item>
)}
</DropDown.Group>
)}
</DropDown.ItemList>
</DropDown>
Filter
The filter in a Menu works OOTB, just declaring the <DropDown.Search />
component in the composition, as in the examples shown for static and dynamic content. The Filter can be done by DropDown itself as well as the developer can control the filter and make his own filter rule.
function Example() {
const [value, setValue] = useState('');
const options = [{name: 'one'}, {name: 'two'}, {name: 'three'}];
const filteredOptions = useMemo(() => {
if (!value) {
return options;
}
return options.filter(
(option) => option.match(new RegExp(value, 'i')) !== null
);
}, [options, value]);
return (
<DropDown trigger={<Button>Click Me</Button>}>
<DropDown.Search
value={value}
onChange={setValue}
placeholder="Type to filter"
/>
<DropDown.ItemList items={filteredOptions}>
{(item) => (
<DropDown.Item key={item.name}>{item.name}</DropDown.Item>
)}
</DropDown.ItemList>
</DropDown>
);
}
If you just need to set an initial value, just use the defaultValue
property.
<DropDown filterKey="name" trigger={<Button>Click Me</Button>}>
<DropDown.Search defaultValue="o" placeholder="Type to filter" />
<DropDown.ItemList items={[{name: 'one'}, {name: 'two'}, {name: 'three'}]}>
{(item) => <DropDown.Item key={item.name}>{item.name}</DropDown.Item>}
</DropDown.ItemList>
</DropDown>
Value
DropDown filters items according to the value rendered as children
of <DropDown.Item />
, when there are more detailed compositions in the item, the filter will not work because it cannot determine which value to use to filter, in this scenario configure the item's value using the textValue
property.
<DropDown filterKey="name" trigger={<Button>Click Me</Button>}>
<DropDown.Search placeholder="Type to filter" />
<DropDown.ItemList items={[{name: 'one'}, {name: 'two'}, {name: 'three'}]}>
{(item) => (
<DropDown.Item key={item.name} textValue={item.name}>
<Checkbox />
{item.name}
</DropDown.Item>
)}
</DropDown.ItemList>
</DropDown>
Without Trigger
You may want to create a trigger that is not necessarily in the same tree as DropDown, due to HTML markup issues, for these cases you can use <ClayDropDown.Menu />
.
Using <ClayDropDown.Menu />
allows you to better control the state of DropDown but you will have to deal with visibility, focus management, and other details.
import ClayDropDown from '@clayui/drop-down';
import React, {useState, useRef} from 'react';
const Menu = ({children, hasLeftSymbols, hasRightSymbols}) => {
const triggerElementRef = useRef(null);
const [expand, setExpand] = useState(false);
const menuElementRef = useRef(null);
const handleExpand = (event) => {
// This is not ideal for allowing you to have more than
// one trigger for the same content but it simulates the
// advantages of controlling `DropDown.Menu`.
triggerElementRef.current = event.target;
setExpand(!expand);
};
return (
<div>
<div>
<button type="button" onClick={handleExpand}>
Home
</button>
</div>
<div>
<button type="button" onClick={handleExpand}>
Product
</button>
</div>
<ClayDropDown.Menu
active={expand}
alignElementRef={triggerElementRef}
hasLeftSymbols={hasLeftSymbols}
hasRightSymbols={hasRightSymbols}
onActiveChange={() => setExpand(!expand)}
ref={menuElementRef}
>
{children}
</ClayDropDown.Menu>
</div>
);
};
Variants
Clay provides other ways to use the Clay component, which we call high-level components, which are designed to do a specific high-level behavior different from compositing that allow different possibilities, are less flexible.
Icon
By default, a caret icon (▼) is displayed alongside the button text to indicate the dropdown functionality. This visually indicates a dropdown menu and improve user understanding.
The caret icon can be removed by passing null
to the triggerIcon
prop.
With Items
Allows you to create a simple DropDown, through its API you are able to create a Menu with groups of checkboxes and radios, links, buttons, search, caption, etc.
Customizable Trigger Icon
When using DropDownWithItems
, you can customize the icon on the dropdown button via the triggerIcon
prop.
Cascading Menu
DropDownWithItems
allows the possibility to create a contextual menu, the nature of the API allows the creation of more cascade menus but the Lexicon specification recommends using only one level and using DropDownWithDrilldown
component.
To render a cascading menu it is necessary to set the type
of the item to contextual
and add to the items
object that follows the same API as the other items.
const items = [
{label: 'Folder'},
{type: 'divider'},
{
items: [
{label: 'Basic Document'},
{label: 'Contract'},
{label: 'Marketing Banner'},
{label: 'Spreadsheet'},
{label: 'Presentation'},
],
label: 'Document',
type: 'contextual',
},
{label: 'Shortcut'},
{label: 'Repository'},
];
Drilldown
A Drilldown menu allows the user to navigate to and/or select an element from a contextual list. It can be triggered by a dropdown button.
The way the Drilldown component links the menus is done via reference, the menu needs a unique id
.
const menus = {
of23: [{title: 'First'}],
};
From the id
you are able to link to another menu using the child
property.
const menus = {
of23: [{title: 'First', child: 'of09'}],
of09: [{title: 'Three'}],
};
If you want to add separators between your menu items, it's possible to do so, by
using an item that has this shape {type: 'divider'}
.
const menus = {
of23: [
{title: 'First', child: 'of09'},
{type: 'divider'},
{title: 'Second'},
],
of09: [{title: 'Three'}],
};
Caveats
One caveat with the drop down menu is that it is rendered inside of a React Portal and is rendered directly to the body
element. This means if you are using the menuWidth
prop set to auto
, it will not respect the size of the node parent of the drop down, since the menu is rendered directly to the body.