MenuTrigger
The MenuTrigger serves as a wrapper around a Menu and its associated trigger, linking the Menu's open state with the trigger's press state.
install | yarn add @adobe/react-spectrum |
---|---|
added | 3.0.0 |
usage | import {MenuTrigger, Menu} from '@adobe/react-spectrum' |
Example#
<MenuTrigger>
<ActionButton>
Edit
</ActionButton>
<Menu>
<Item>Cut</Item>
<Item>Copy</Item>
<Item>Paste</Item>
</Menu>
</MenuTrigger>
<MenuTrigger>
<ActionButton>
Edit
</ActionButton>
<Menu>
<Item>Cut</Item>
<Item>Copy</Item>
<Item>Paste</Item>
</Menu>
</MenuTrigger>
<MenuTrigger>
<ActionButton>
Edit
</ActionButton>
<Menu>
<Item>Cut</Item>
<Item>Copy</Item>
<Item>Paste</Item>
</Menu>
</MenuTrigger>
Content#
The MenuTrigger accepts exactly two children: the element which triggers the opening of the Menu and the Menu itself. The trigger element must be the first child passed into the MenuTrigger and should support press events.
Events#
MenuTrigger accepts an onOpenChange
handler which is triggered whenever the Menu is opened or closed.
function Example() {
let [isOpen, setIsOpen] = React.useState(false);
return (
<Flex gap="size-100" alignItems="center">
<MenuTrigger onOpenChange={setIsOpen}>
<ActionButton>
Edit
</ActionButton>
<Menu>
<Item key="cut">Cut</Item>
<Item key="copy">Copy</Item>
<Item key="paste">Paste</Item>
</Menu>
</MenuTrigger>
<div>Currently open: {isOpen.toString()}</div>
</Flex>
);
}
function Example() {
let [isOpen, setIsOpen] = React.useState(false);
return (
<Flex gap="size-100" alignItems="center">
<MenuTrigger onOpenChange={setIsOpen}>
<ActionButton>
Edit
</ActionButton>
<Menu>
<Item key="cut">Cut</Item>
<Item key="copy">Copy</Item>
<Item key="paste">Paste</Item>
</Menu>
</MenuTrigger>
<div>Currently open: {isOpen.toString()}</div>
</Flex>
);
}
function Example() {
let [
isOpen,
setIsOpen
] = React.useState(
false
);
return (
<Flex
gap="size-100"
alignItems="center"
>
<MenuTrigger
onOpenChange={setIsOpen}
>
<ActionButton>
Edit
</ActionButton>
<Menu>
<Item key="cut">
Cut
</Item>
<Item key="copy">
Copy
</Item>
<Item key="paste">
Paste
</Item>
</Menu>
</MenuTrigger>
<div>
Currently open:
{' '}
{isOpen
.toString()}
</div>
</Flex>
);
}
Long press#
By default, a MenuTrigger's Menu is opened by pressing the trigger element or activating it via the Space or Enter keys. However, there may be cases in which your trigger element
should perform a separate default action on press such as selection, and should only display the Menu when long pressed. This behavior can be changed by providing "longPress"
to the trigger
prop. With this prop, the Menu will only be opened upon
pressing and holding the trigger element or by using the Option (Alt on Windows) + Down Arrow/Up Arrow keys while focusing the trigger element.
The example below illustrates how one would setup a MenuTrigger to have long press behavior.
import {Text} from '@adobe/react-spectrum';
import CloneStamp from '@spectrum-icons/workflow/CloneStamp';
import Crop from '@spectrum-icons/workflow/Crop';
import CropRotate from '@spectrum-icons/workflow/CropRotate';
import Slice from '@spectrum-icons/workflow/Slice';
<MenuTrigger trigger="longPress">
<ActionButton
aria-label="Crop tool"
onPress={() => alert('Cropping!')}
>
<Crop />
</ActionButton>
<Menu>
<Item textValue="Crop Rotate">
<CropRotate />
<Text>Crop Rotate</Text>
</Item>
<Item textValue="Slice">
<Slice />
<Text>Slice</Text>
</Item>
<Item textValue="Clone stamp">
<CloneStamp />
<Text>Clone Stamp</Text>
</Item>
</Menu>
</MenuTrigger>
import {Text} from '@adobe/react-spectrum';
import CloneStamp from '@spectrum-icons/workflow/CloneStamp';
import Crop from '@spectrum-icons/workflow/Crop';
import CropRotate from '@spectrum-icons/workflow/CropRotate';
import Slice from '@spectrum-icons/workflow/Slice';
<MenuTrigger trigger="longPress">
<ActionButton
aria-label="Crop tool"
onPress={() => alert('Cropping!')}
>
<Crop />
</ActionButton>
<Menu>
<Item textValue="Crop Rotate">
<CropRotate />
<Text>Crop Rotate</Text>
</Item>
<Item textValue="Slice">
<Slice />
<Text>Slice</Text>
</Item>
<Item textValue="Clone stamp">
<CloneStamp />
<Text>Clone Stamp</Text>
</Item>
</Menu>
</MenuTrigger>
import {Text} from '@adobe/react-spectrum';
import CloneStamp from '@spectrum-icons/workflow/CloneStamp';
import Crop from '@spectrum-icons/workflow/Crop';
import CropRotate from '@spectrum-icons/workflow/CropRotate';
import Slice from '@spectrum-icons/workflow/Slice';
<MenuTrigger trigger="longPress">
<ActionButton
aria-label="Crop tool"
onPress={() =>
alert(
'Cropping!'
)}
>
<Crop />
</ActionButton>
<Menu>
<Item textValue="Crop Rotate">
<CropRotate />
<Text>
Crop Rotate
</Text>
</Item>
<Item textValue="Slice">
<Slice />
<Text>
Slice
</Text>
</Item>
<Item textValue="Clone stamp">
<CloneStamp />
<Text>
Clone Stamp
</Text>
</Item>
</Menu>
</MenuTrigger>
Unavailable Items alpha#
ContextualHelpTrigger allows making a Menu Item unavailable along with showing a user extra information about the state of that unavailable item. It provides context and may point users to more information elsewhere.
The ContextualHelpTrigger accepts exactly two children: the Item which triggers opening of the Dialog and the Dialog itself. The trigger must be the first child passed into the ContextualHelpTrigger and should be an Item. Similar to ContextualHelp, the layout of the Dialog is very deliberate. See ContextualHelp for further explanation.
By default, a ContextualHelpTrigger's Dialog is opened by hovering, pressing the trigger element or activating it via the Space, Enter, or Right Arrow keys. Hovering another item or pressing the Esc key will close the Dialog and leave the Menu open.
Setting the isUnavailable
prop on the ContextualHelpTrigger makes a Menu Item unavailable and enables the Dialog with contextual help, allowing for programmatic control.
Note that the Menu's onAction
and onSelectionChange
callbacks will not fire for items made unavailable by a ContextualHelpTrigger.
The example below illustrates how one would setup a MenuTrigger to use ContextualHelpTrigger.
import {Content, ContextualHelpTrigger, Dialog, Heading} from '@adobe/react-spectrum';
<MenuTrigger>
<ActionButton>Edit</ActionButton>
<Menu>
<Item key="undo">Undo</Item>
<Item key="redo">Redo</Item>
<ContextualHelpTrigger isUnavailable>
<Item key="cut">Cut</Item>
<Dialog>
<Heading>Cut</Heading>
<Content>Please select text for 'Cut' to be enabled.</Content>
</Dialog>
</ContextualHelpTrigger>
<ContextualHelpTrigger isUnavailable>
<Item key="copy">Copy</Item>
<Dialog>
<Heading>Copy</Heading>
<Content>Please select text for 'Copy' to be enabled.</Content>
</Dialog>
</ContextualHelpTrigger>
<ContextualHelpTrigger>
<Item key="paste">Paste</Item>
<Dialog>
<Heading>Paste</Heading>
<Content>You have nothing to 'Paste'.</Content>
</Dialog>
</ContextualHelpTrigger>
</Menu>
</MenuTrigger>
import {
Content,
ContextualHelpTrigger,
Dialog,
Heading
} from '@adobe/react-spectrum';
<MenuTrigger>
<ActionButton>Edit</ActionButton>
<Menu>
<Item key="undo">Undo</Item>
<Item key="redo">Redo</Item>
<ContextualHelpTrigger isUnavailable>
<Item key="cut">Cut</Item>
<Dialog>
<Heading>Cut</Heading>
<Content>
Please select text for 'Cut' to be enabled.
</Content>
</Dialog>
</ContextualHelpTrigger>
<ContextualHelpTrigger isUnavailable>
<Item key="copy">Copy</Item>
<Dialog>
<Heading>Copy</Heading>
<Content>
Please select text for 'Copy' to be enabled.
</Content>
</Dialog>
</ContextualHelpTrigger>
<ContextualHelpTrigger>
<Item key="paste">Paste</Item>
<Dialog>
<Heading>Paste</Heading>
<Content>You have nothing to 'Paste'.</Content>
</Dialog>
</ContextualHelpTrigger>
</Menu>
</MenuTrigger>
import {
Content,
ContextualHelpTrigger,
Dialog,
Heading
} from '@adobe/react-spectrum';
<MenuTrigger>
<ActionButton>
Edit
</ActionButton>
<Menu>
<Item key="undo">
Undo
</Item>
<Item key="redo">
Redo
</Item>
<ContextualHelpTrigger
isUnavailable
>
<Item key="cut">
Cut
</Item>
<Dialog>
<Heading>
Cut
</Heading>
<Content>
Please select
text for
'Cut' to be
enabled.
</Content>
</Dialog>
</ContextualHelpTrigger>
<ContextualHelpTrigger
isUnavailable
>
<Item key="copy">
Copy
</Item>
<Dialog>
<Heading>
Copy
</Heading>
<Content>
Please select
text for
'Copy' to be
enabled.
</Content>
</Dialog>
</ContextualHelpTrigger>
<ContextualHelpTrigger>
<Item key="paste">
Paste
</Item>
<Dialog>
<Heading>
Paste
</Heading>
<Content>
You have
nothing to
'Paste'.
</Content>
</Dialog>
</ContextualHelpTrigger>
</Menu>
</MenuTrigger>
Props#
Name | Type | Default | Description |
children | ReactElement[] | — | The contents of the MenuTrigger - a trigger and a Menu. |
align | Alignment | 'start' | Alignment of the menu relative to the trigger. |
direction | 'bottom'
| 'top'
| 'left'
| 'right'
| 'start'
| 'end' | 'bottom' | Where the Menu opens relative to its trigger. |
shouldFlip | boolean | true | Whether the menu should automatically flip direction when space is limited. |
closeOnSelect | boolean | true | Whether the Menu closes when a selection is made. |
trigger | MenuTriggerType | 'press' | How the menu is triggered. |
isOpen | boolean | — | Whether the overlay is open by default (controlled). |
defaultOpen | boolean | — | Whether the overlay is open by default (uncontrolled). |
Events
Name | Type | Description |
onOpenChange | (
(isOpen: boolean
)) => void | Handler that is called when the overlay's open state changes. |
Visual options#
Align and direction#
The align
prop aligns the Menu relative to the trigger and the direction
prop controls the direction the Menu will render.
<Flex gap="size-100">
<MenuTrigger align="start">
<ActionButton>Edit</ActionButton>
<Menu>
<Item key="cut">Cut</Item>
<Item key="copy">Copy</Item>
<Item key="paste">Paste</Item>
</Menu>
</MenuTrigger>
<MenuTrigger align="end" direction="top" shouldFlip={false}>
<ActionButton>View</ActionButton>
<Menu>
<Item key="side">Side bar</Item>
<Item key="options">Page options</Item>
<Item key="edit">Edit Panel</Item>
</Menu>
</MenuTrigger>
<MenuTrigger direction="start" align="start">
<ActionButton>Edit</ActionButton>
<Menu>
<Item key="cut">Cut</Item>
<Item key="copy">Copy</Item>
<Item key="paste">Paste</Item>
</Menu>
</MenuTrigger>
<MenuTrigger direction="end" align="end">
<ActionButton>View</ActionButton>
<Menu>
<Item key="side">Side bar</Item>
<Item key="options">Page options</Item>
<Item key="edit">Edit Panel</Item>
</Menu>
</MenuTrigger>
</Flex>
<Flex gap="size-100">
<MenuTrigger align="start">
<ActionButton>Edit</ActionButton>
<Menu>
<Item key="cut">Cut</Item>
<Item key="copy">Copy</Item>
<Item key="paste">Paste</Item>
</Menu>
</MenuTrigger>
<MenuTrigger
align="end"
direction="top"
shouldFlip={false}
>
<ActionButton>View</ActionButton>
<Menu>
<Item key="side">Side bar</Item>
<Item key="options">Page options</Item>
<Item key="edit">Edit Panel</Item>
</Menu>
</MenuTrigger>
<MenuTrigger direction="start" align="start">
<ActionButton>Edit</ActionButton>
<Menu>
<Item key="cut">Cut</Item>
<Item key="copy">Copy</Item>
<Item key="paste">Paste</Item>
</Menu>
</MenuTrigger>
<MenuTrigger direction="end" align="end">
<ActionButton>View</ActionButton>
<Menu>
<Item key="side">Side bar</Item>
<Item key="options">Page options</Item>
<Item key="edit">Edit Panel</Item>
</Menu>
</MenuTrigger>
</Flex>
<Flex gap="size-100">
<MenuTrigger align="start">
<ActionButton>
Edit
</ActionButton>
<Menu>
<Item key="cut">
Cut
</Item>
<Item key="copy">
Copy
</Item>
<Item key="paste">
Paste
</Item>
</Menu>
</MenuTrigger>
<MenuTrigger
align="end"
direction="top"
shouldFlip={false}
>
<ActionButton>
View
</ActionButton>
<Menu>
<Item key="side">
Side bar
</Item>
<Item key="options">
Page options
</Item>
<Item key="edit">
Edit Panel
</Item>
</Menu>
</MenuTrigger>
<MenuTrigger
direction="start"
align="start"
>
<ActionButton>
Edit
</ActionButton>
<Menu>
<Item key="cut">
Cut
</Item>
<Item key="copy">
Copy
</Item>
<Item key="paste">
Paste
</Item>
</Menu>
</MenuTrigger>
<MenuTrigger
direction="end"
align="end"
>
<ActionButton>
View
</ActionButton>
<Menu>
<Item key="side">
Side bar
</Item>
<Item key="options">
Page options
</Item>
<Item key="edit">
Edit Panel
</Item>
</Menu>
</MenuTrigger>
</Flex>
Close on selection#
By default, the Menu closes when an item is selected. To change this, set the closeOnSelect
prop to false
. This might be useful when multiple selection is used. See menu selection for more information.
<MenuTrigger closeOnSelect={false}>
<ActionButton>
View
</ActionButton>
<Menu selectionMode="multiple">
<Item key="side">Side bar</Item>
<Item key="options">Page options</Item>
<Item key="edit">Edit Panel</Item>
</Menu>
</MenuTrigger>
<MenuTrigger closeOnSelect={false}>
<ActionButton>
View
</ActionButton>
<Menu selectionMode="multiple">
<Item key="side">Side bar</Item>
<Item key="options">Page options</Item>
<Item key="edit">Edit Panel</Item>
</Menu>
</MenuTrigger>
<MenuTrigger
closeOnSelect={false}
>
<ActionButton>
View
</ActionButton>
<Menu selectionMode="multiple">
<Item key="side">
Side bar
</Item>
<Item key="options">
Page options
</Item>
<Item key="edit">
Edit Panel
</Item>
</Menu>
</MenuTrigger>
Flipping#
By default, the Menu flips direction automatically upon opening when space is limited. To change this, set the shouldFlip
prop to false
. Try scrolling the viewport close to the edge of the trigger in the example to see this in action.
<Flex gap="size-100">
<MenuTrigger shouldFlip>
<ActionButton>
View
</ActionButton>
<Menu>
<Item key="side">Side bar</Item>
<Item key="options">Page options</Item>
<Item key="edit">Edit Panel</Item>
</Menu>
</MenuTrigger>
<MenuTrigger shouldFlip={false}>
<ActionButton>
Edit
</ActionButton>
<Menu>
<Item key="cut">Cut</Item>
<Item key="copy">Copy</Item>
<Item key="paste">Paste</Item>
</Menu>
</MenuTrigger>
</Flex>
<Flex gap="size-100">
<MenuTrigger shouldFlip>
<ActionButton>
View
</ActionButton>
<Menu>
<Item key="side">Side bar</Item>
<Item key="options">Page options</Item>
<Item key="edit">Edit Panel</Item>
</Menu>
</MenuTrigger>
<MenuTrigger shouldFlip={false}>
<ActionButton>
Edit
</ActionButton>
<Menu>
<Item key="cut">Cut</Item>
<Item key="copy">Copy</Item>
<Item key="paste">Paste</Item>
</Menu>
</MenuTrigger>
</Flex>
<Flex gap="size-100">
<MenuTrigger
shouldFlip
>
<ActionButton>
View
</ActionButton>
<Menu>
<Item key="side">
Side bar
</Item>
<Item key="options">
Page options
</Item>
<Item key="edit">
Edit Panel
</Item>
</Menu>
</MenuTrigger>
<MenuTrigger
shouldFlip={false}
>
<ActionButton>
Edit
</ActionButton>
<Menu>
<Item key="cut">
Cut
</Item>
<Item key="copy">
Copy
</Item>
<Item key="paste">
Paste
</Item>
</Menu>
</MenuTrigger>
</Flex>
Open#
The isOpen
and defaultOpen
props on the MenuTrigger control whether the Menu is open by default.
They apply controlled and uncontrolled behavior on the Menu respectively.
function Example() {
let [open, setOpen] = React.useState(false);
return (
<MenuTrigger
isOpen={open}
onOpenChange={setOpen}>
<ActionButton>
View
</ActionButton>
<Menu selectionMode="multiple">
<Item key="side">Side bar</Item>
<Item key="options">Page options</Item>
<Item key="edit">Edit Panel</Item>
</Menu>
</MenuTrigger>
);
}
function Example() {
let [open, setOpen] = React.useState(false);
return (
<MenuTrigger
isOpen={open}
onOpenChange={setOpen}>
<ActionButton>
View
</ActionButton>
<Menu selectionMode="multiple">
<Item key="side">Side bar</Item>
<Item key="options">Page options</Item>
<Item key="edit">Edit Panel</Item>
</Menu>
</MenuTrigger>
);
}
function Example() {
let [open, setOpen] =
React.useState(
false
);
return (
<MenuTrigger
isOpen={open}
onOpenChange={setOpen}
>
<ActionButton>
View
</ActionButton>
<Menu selectionMode="multiple">
<Item key="side">
Side bar
</Item>
<Item key="options">
Page options
</Item>
<Item key="edit">
Edit Panel
</Item>
</Menu>
</MenuTrigger>
);
}