import { DropdownMenu } from "@vitality-ds/components";
A dropdown menu reveals its options once it has been clicked on. This enables maximum space efficiency while still offering many options. The component takes two props: trigger
and items
. Items represents the menu items within the dropdown, and trigger represents the element you click on to open the menu. Regardless of type, all dropdown menus list one or more menu items, each of which represents a command, option, or state that affects the current selection or context. When users choose a menu item, the action occurs and the menu typically closes.
() => { const trigger = { type: "button", props: { children: "Add...", appearance: "primary", }, }; const items = [ { label: "Attachment", }, { label: "Consult Note", }, ]; return <DropdownMenu trigger={trigger} items={items} />; };
Each menu item that can be interacted with should have a descriptive label that is easy to understand.
Use "..." after a label when an action requires a follow-up step.
() => { const trigger = { type: "button", props: { children: "Add", appearance: "primary", }, }; const items = [ { label: "Attachment", onClick: () => console.log("clicked"), }, { label: "Consult Note", onClick: () => console.log("clicked"), }, { label: "Custom Letter...", onClick: () => console.log("clicked"), }, ]; return <DropdownMenu trigger={trigger} items={items} />; };
Icons can be added to both the trigger component and/or dropdown menu items. Use icons to add extra visual support in understanding the result of a chosen action. For instance, in an "Add..." dropdown, where multiple types of entities can be added, you can use icons to provide extra visual support – each icon indicating what type of entity will be added.
As a rule of thumb, if you add one icon to a dropdown menu, then all items should have an icon. If you don't adhere to this, there will be misalignment of the labels.
() => { const trigger = { type: "button", props: { children: "Add", iconRight: <Add />, appearance: "primary", }, }; const items = [ { label: "Attachment", icon: <Attachment />, onClick: () => console.log("clicked"), }, { label: "Consult Note", icon: <ConsultNote />, onClick: () => console.log("clicked"), }, ]; return <DropdownMenu trigger={trigger} items={items} />; };
The DropdowMenu component does not provide the actual keyboard shortcut functionality, however it does allow you to indicate what shortcut an item has. Do this by passing an array of keys to the shortcut
property. For more information, see the <Shortcuts />
component.
() => { const trigger = { type: "button", props: { children: "Add", iconRight: <Add />, appearance: "primary", }, }; const items = [ { label: "Attachment", icon: <Attachment />, shortcut: ["⌘", "A"], onClick: () => console.log("clicked"), }, { label: "Consult Note", icon: <ConsultNote />, shortcut: ["⌘", "N"], onClick: () => console.log("clicked"), }, ]; return <DropdownMenu trigger={trigger} items={items} />; };
If an action cannot be performed, pass the disabled
prop to it.
() => { const trigger = { type: "button", props: { children: "Add", iconRight: <Add />, appearance: "primary", }, }; const items = [ { label: "Attachment", icon: <Attachment />, disabled: true, onClick: () => console.log("clicked"), }, { label: "Consult Note", icon: <ConsultNote />, onClick: () => console.log("clicked"), }, ]; return <DropdownMenu trigger={trigger} items={items} />; };
Custom menu items can be passed to items
. This should be used with caution and is the developers responsibility to ensure best practices around UX and development are followed.
() => { const CustomItem = () => ( <DocsBox css={{ padding: "$md" }}> <Typography variant="sectionSubtitle">Dr Andrew Demo</Typography> <Typography>Brisbane Medical Centre · GTU99999</Typography> </DocsBox> ); const trigger = { type: "button", props: { children: "Add" }, }; const items = [ { type: "custom", component: <CustomItem />, }, { type: "divider" }, { label: "Menu item", onClick: () => alert("clicked"), }, { label: "Menu item", onClick: () => alert("clicked"), }, ]; return <DropdownMenu items={items} trigger={trigger} />; };
Each dropdown menu item is essentially an action button. Clicking a menu item should perform an action and you should avoid navigating the user away from the page.
() => { const [chosenAction, setChosenAction] = React.useState(); const trigger = { type: "button", props: { children: "Add", iconRight: <Add />, appearance: "primary", }, }; const items = [ { label: "Attachment", icon: <Attachment />, onClick: () => setChosenAction("Attachment"), }, { label: "Consult Note", icon: <ConsultNote />, onClick: () => setChosenAction("Consult Note"), }, ]; return ( <Stack> <DropdownMenu trigger={trigger} items={items} /> {chosenAction && <>User clicked: {chosenAction}</>} </Stack> ); };
Some actions performed by a dropdown menu item may require the user to navigate to a different page. In this case, set type="link"
to an item, and you can use the href
prop to provide a link to the page or the onClick
prop is necessary for router libraries. The dropdown menu item can receie all valid <a>
props, such as target
, rel
, etc.
() => { const trigger = { type: "button", props: { children: "Links...", }, }; const items = [ { type: "link", label: "Patients", href: "https://wikipedia.com/wiki/Patient", target: "_blank", }, { type: "link", label: "Medications", href: "https://wikipedia.com/wiki/Medication", target: "_blank", }, ]; return <DropdownMenu trigger={trigger} items={items} />; };
Use Submenus when you have to "drill down" into more specific actions. For example, an "Open Recent..." action could show a submenu that displays a list of recently opened patient files. Whilst submenus can offer more options, they do add a layer of complexity to the menu and should be used sparingly.
Vitality has restricted the number of submenus to one – as nested submenus can create a complicated user experience.
() => { const trigger = { type: "button", props: { children: "File", appearance: "primary", }, }; const Container = (props) => ( <DocsBox css={{ width: "100%", backgroundColor: "$neutral1", padding: "$lg" }} {...props} /> ); const items = [ { label: "New", shortcut: ["⌘", "N"], }, { type: "divider", }, { label: "Open...", shortcut: ["⌘", "O"], }, { label: "Open Recent", type: "subMenu", subMenu: [ { label: "Aubrey McClusky" }, { label: "Darlene Johnson" }, { label: "Daphne Nonsemple" }, ], }, { label: "Re-open Closed Patient", shortcut: ["⌘", "⇧", "O"], }, ]; return ( <Container> <DropdownMenu trigger={trigger} items={items} /> </Container> ); };
The trigger prop takes an object which determines the type of trigger. It can be either a <Button>
, <IconButton />
or an <Avatar>
, and the props that component type requires. For a complete list of available props, see their respective documentation.
() => { const items = [ { label: "Account Settings", icon: <Settings /> }, { label: "Log Out", icon: <Logout /> }, ]; return ( <Stack direction="horizontal" align="center"> <DropdownMenu trigger={{ type: "button", props: { appearance: "primary", children: "Add..." }, }} items={items} /> <DropdownMenu trigger={{ type: "iconButton", props: { icon: <MenuMoreVertical />, tooltipContent: "Additional Options", }, }} items={items} /> <DropdownMenu trigger={{ type: "avatar", props: { size: "lg", color: "primary", initials: "XA", tooltipContent: "Account Settings & Log Out", }, }} items={items} /> </Stack> ); };
When a dropdown's trigger will appear among other button elements, consider including <Dropdown />
icon in the button to indicate that the button will reveal a dropdown.
() => { const trigger = { type: "button", props: { children: "Add", iconRight: <Dropdown />, appearance: "primary", }, }; const items = [ { label: "Attachment", onClick: () => console.log("clicked"), }, { label: "Consult Note", onClick: () => console.log("clicked"), }, ]; return <DropdownMenu trigger={trigger} items={items} />; };
If you pass the disabled
prop to the dropdown menu component, it will disable both the dropdown popover and the trigger element's clickability, while also reflecting the disabled styling of the trigger element.
() => { const trigger = { type: "button", props: { children: "Add...", appearance: "primary", }, }; const items = [ { label: "Attachment", }, { label: "Consult Note", }, ]; return <DropdownMenu disabled trigger={trigger} items={items} />; };
To organise the items in a dropdown menu, arrange them in a logical order with the most commonly used option at the top, if it is known. For lengthy lists, group similar menu items together (for example, 'cut', 'copy' and 'paste' are often grouped together in a dropdown menu). If incorporating checkable items, attempt to group associated actions.
Use a divider to create a logical separation between dropdown menu items. This can help the user to easily scan the items and identify groups of common actions.
() => { const items = [ { label: "New Patient...", shortcut: ["⌘", "N"] }, { type: "divider" }, { label: "Open...", shortcut: ["⌘", "O"] }, { label: "Open Recent..." }, { type: "divider" }, { label: "Save", shortcut: ["⌘", "S"] }, { label: "Save As...", shortcut: ["⌘", "⇧", "O"] }, ]; return ( <DropdownMenu trigger={{ type: "button", props: { children: "View as..." } }} items={items} /> ); };
As an extension of dividing menu items, you can also add more context to a grouping by adding a groupLabel
. Group the items by using a brief label that summarises the choices in that particular subcategory.
() => { const items = [ { type: "groupLabel", label: "Priority Patients" }, { label: "Aubrey McClusky" }, { label: "Max Payne" }, { type: "groupLabel", label: "Recently Viewed Patients" }, { label: "Darlene Johnson" }, { label: "Daphne Nonsemple" }, { label: "Sarah Smith" }, { type: "divider" }, { label: "Close Patient File", shortcut: ["⌘", "Q"] }, { label: "New Patient...", shortcut: ["⌘", "N"] }, ]; return ( <DropdownMenu trigger={{ type: "button", props: { children: "View as..." } }} items={items} /> ); };
You do not need to group all items in a menu. Adding a group label helps provide context to a specific group when useful. When other items appear below a group, use a divider to show a clear separation
If the item can be checked or unchecked (such as toggling a user preference), you can use type="checkableItem"
, this will display a check mark next to the menu item when selected.
The state of checkable items is a controlled component. This means that your application should manage their state, triggered by the onClick
prop and providing the boolean state into the checked
prop.
() => { const [showRecentPrescriptions, setShowRecentPrescriptions] = React.useState(true); const [showDeletedAppointments, setShowDeletedAppointments] = React.useState(false); const items = [ { label: "New Patient" }, { label: "Edit Patient" }, { type: "divider" }, { label: "Include Pregnancy Record", type: "checkableItem", checked: showDeletedAppointments, onClick: () => setShowDeletedAppointments(!showDeletedAppointments), shortcut: ["⌘", "P"], }, { label: "Show Recent Prescriptions", type: "checkableItem", checked: showRecentPrescriptions, onClick: () => setShowRecentPrescriptions(!showRecentPrescriptions), shortcut: ["⌘", "R"], }, ]; return ( <DropdownMenu trigger={{ type: "button", props: { children: "View as..." } }} items={items} /> ); };
() => { const trigger = { type: "button", props: { children: "Add", iconRight: <Add />, appearance: "primary", }, }; const items = [ { label: "Attachment", icon: <Attachment />, shortcut: ["⌘", "A"], }, { type: "divider", }, { label: "Consult Note", icon: <ConsultNote />, disabled: true, shortcut: ["⌘", "C"], }, { type: "subMenu", icon: <Medications />, label: "Medications", subMenu: [ { label: "Prescription", onClick: (event) => console.log(event.target), }, { label: "ePrescription" }, { label: "View List" }, ], }, { label: "Letter", icon: <Letter />, shortcut: ["⌘", "L"], }, { label: "Procedure", icon: <Procedure />, shortcut: ["⌘", "P"], }, { label: "Pregnancy Record", icon: <Obstetrics />, shortcut: ["⌘", "P"], }, { type: "divider", }, { label: "Quote", icon: <Billed />, shortcut: ["⌘", "Q"], }, { label: "Invoice", icon: <Invoice />, shortcut: ["⌘", "I"], }, { type: "divider", }, { label: "Recall", icon: <Recall />, shortcut: ["⌘", "R"], }, { label: "Request", icon: <Request />, shortcut: ["⌘", "⇧", "R"], }, { type: "divider", }, { label: "To Do", icon: <ToDo />, shortcut: ["⌘", "T"], }, ]; return <DropdownMenu trigger={trigger} items={items} />; };
Apple's Human Interface Guidelines
Description
Prevent the Dropdown's trigger from being pressed, therefore preventing the dropdown from showing
Type
boolean
Type
DropdownMenuItems<K>[]
Description
The preferred alignment against the trigger. May change when collisions occur.
Type
"start" | "center" | "end"
Default Value
start
Type
Trigger<T>
Description
Determines if the menu item can be clicked or not
Type
boolean
Type
"a"
Description
The optional icon that renders next to the menu item.
Type
ItemIconType
Type
boolean
Description
The text that renders in the dropdown menu for the user to interact with.
Type
string
Description
The function performed when a user clicks on a menu item.
Type
MouseEventHandler<HTMLDivElement>
Description
Array of strings that represent keyboard shortcuts.
Type
string[]
Description
Array of submenu items, in the same structure as root level menu items.
Type
DropdownMenuItem[]
Description
Determines whether the item is checked or not
Type
boolean
Description
Determines if the menu item can be clicked or not
Type
boolean
Type
("a" | ({ "@bp1"?: "a"; "@bp2"?: "a"; "@bp3"?: "a"; "@initial"?: "a"; } & { [x: string]: "a"; })) & ("a"
Description
The optional icon that renders next to the menu item.
Type
ItemIconType
Type
(boolean | "true" | ({ "@bp1"?: boolean | "true"; "@bp2"?: boolean | "true"; "@bp3"?: boolean | "true"; "@initial"?: boolean | "true"; } & { [x: string]: boolean | "true"; })) & (boolean | "true"
Description
The text that renders in the dropdown menu for the user to interact with.
Type
string
Description
The function performed when a user clicks on a menu item. The function performed when a user clicks on a checkable menu item.
Type
MouseEventHandler<HTMLDivElement> & ((checked: boolean) => void)
Description
Array of strings that represent keyboard shortcuts.
Type
string[]
Description
Array of submenu items, in the same structure as root level menu items.
Type
DropdownMenuItem[]