Menu
A trigger and the MenuList of commands it opens. It isn't modal: no focus trap, no scroll lock, nothing laid over the page, so the pointer passes over it and the page stays usable around it.
A trigger and the MenuList of commands it opens. It isn't modal: no
focus trap, no scroll lock, nothing laid over the page, so the pointer passes over it and the
page stays usable around it. The list is placed position: fixed in the window, under the
trigger, so a panel that scrolls or clips its content can't cut it off - but it stays inside the
menu's own box in the DOM, so focus moving inside it is still focus inside the menu.
import { Menu, MenuDivider } from '@soroush.tech/design-system/Menu'
import { MenuItem } from '@soroush.tech/design-system/MenuItem'
import { Button } from '@soroush.tech/design-system/Button'
;<Menu trigger={<Button variant="text">main</Button>} onSelect={run}>
<MenuItem value="new">New branch...</MenuItem>
<MenuItem value="checkout" disabled note="Coming later">
Checkout tag or revision...
</MenuItem>
<MenuDivider />
<MenuItem value="rename">Rename...</MenuItem>
</Menu>
Examples
Default
const args: Partial<MenuProps> = { align: 'start', defaultOpen: false, marginThreshold: 2, elevation: 8, layer: 'modal', color: 'primary', size: 'md', } <Menu {...args} trigger={<Button variant="outlined">main</Button>} />
Current Choice
const args: Partial<MenuProps> = { align: 'start', defaultOpen: false, marginThreshold: 2, elevation: 8, layer: 'modal', color: 'primary', size: 'md', } <Branches {...args} />
Aligned
const args: Partial<MenuProps> = { align: 'start', defaultOpen: false, marginThreshold: 2, elevation: 8, layer: 'modal', color: 'primary', size: 'md', } <Flex justifyContent="space-between" width="100%"> <Menu {...args} align="start" trigger={<Button variant="outlined">Start</Button>}> {branchRows} </Menu> <Menu {...args} align="end" trigger={<Button variant="outlined">End</Button>}> {branchRows} </Menu> </Flex>
Icons And Shortcuts
const args: Partial<MenuProps> = { align: 'start', defaultOpen: false, marginThreshold: 2, elevation: 8, layer: 'modal', color: 'primary', size: 'md', } <Menu {...args} trigger={<Button variant="text">File</Button>}> <MenuItem startIcon={<Icon name="folder" size="sm" color="inherit" />} shortcut="Control+O"> Open... </MenuItem> <MenuItem startIcon={<Icon name="refresh" size="sm" color="inherit" />} shortcut="F5"> Reload </MenuItem> <MenuDivider /> <MenuItem shortcut="Control+W">Close</MenuItem> </Menu>
Empty
const args: Partial<MenuProps> = { align: 'start', defaultOpen: false, marginThreshold: 2, elevation: 8, layer: 'modal', emptyLabel: 'Coming soon', color: 'primary', size: 'md', } <Menu {...args} trigger={<Button variant="outlined">Remote branches</Button>} />
Controlled Menu Bar
Open a menu, then step to its neighbours with Left and Right.
const args: Partial<MenuProps> = { align: 'start', defaultOpen: false, marginThreshold: 2, elevation: 8, layer: 'modal', color: 'primary', size: 'md', } <> <Typography variant="caption" mb={1}> Open a menu, then step to its neighbours with Left and Right. </Typography> <MenuBar {...args} /> </>
Submenus
const args: Partial<MenuProps> = { align: 'start', defaultOpen: false, marginThreshold: 2, elevation: 8, layer: 'modal', color: 'primary', size: 'md', } <BranchMenu {...args} />
Nested Submenus
const args: Partial<MenuProps> = { align: 'start', defaultOpen: false, marginThreshold: 2, elevation: 8, layer: 'modal', color: 'primary', size: 'md', } <Menu {...args} trigger={<Button variant="text">Insert</Button>}> <MenuItem submenu={[ <MenuItem key="text" submenu={[ <MenuItem key="h1">Heading</MenuItem>, <MenuItem key="p">Paragraph</MenuItem>, ]} > Text </MenuItem>, <MenuItem key="media" submenu={[<MenuItem key="img">Image</MenuItem>, <MenuItem key="vid">Video</MenuItem>]} > Media </MenuItem>, ]} > Element </MenuItem> <MenuItem>Component...</MenuItem> </Menu>
MenuList
Default
- New branch...
- Checkout tag or revision...Coming later
- Rename...
- Delete
const args: Partial<MenuListProps> = { 'aria-label': 'Branch', shouldWrap: true, shouldAutoFocus: false, color: 'primary', size: 'md', } <MenuList {...args} />
Icons And Shortcuts
- Open...
- Reload
- Close
- Open terminal
const args: Partial<MenuListProps> = { 'aria-label': 'Branch', shouldWrap: true, shouldAutoFocus: false, color: 'primary', size: 'md', } <MenuList {...args} aria-label="File"> <MenuItem startIcon={<Icon name="folder" size="sm" color="inherit" />} shortcut="Control+O"> Open... </MenuItem> <MenuItem startIcon={<Icon name="refresh" size="sm" color="inherit" />} shortcut="F5"> Reload </MenuItem> <MenuItem shortcut="Control+W">Close</MenuItem> <MenuDivider /> <MenuItem startIcon={<Icon name="terminal" size="sm" color="inherit" />}> Open terminal </MenuItem> </MenuList>
Radio And Checkbox Rows
- main
- feat/menu
- fix/tabs
- Wrap lines
const args: Partial<MenuListProps> = { 'aria-label': 'Branch', shouldWrap: true, shouldAutoFocus: false, color: 'primary', size: 'md', } <CheckableRows {...args} />
Type Ahead
Focus a row and type a label's first letters.
- Undo
- Redo
- Cut
- Copy
- Paste
- Rename
- Run
const args: Partial<MenuListProps> = { 'aria-label': 'Branch', shouldWrap: true, shouldAutoFocus: false, color: 'primary', size: 'md', } <> <Typography variant="caption" px={2} pt={1}> Focus a row and type a label's first letters. </Typography> <MenuList {...args} aria-label="Edit" shouldAutoFocus> {['Undo', 'Redo', 'Cut', 'Copy', 'Paste', 'Rename', 'Run'].map((label) => ( <MenuItem key={label}>{label}</MenuItem> ))} </MenuList> </>
Long List
- origin/feature-1
- origin/feature-2
- origin/feature-3
- origin/feature-4
- origin/feature-5
- origin/feature-6
- origin/feature-7
- origin/feature-8
- origin/feature-9
- origin/feature-10
- origin/feature-11
- origin/feature-12
- origin/feature-13
- origin/feature-14
- origin/feature-15
- origin/feature-16
- origin/feature-17
- origin/feature-18
- origin/feature-19
- origin/feature-20
const args: Partial<MenuListProps> = { 'aria-label': 'Branch', shouldWrap: true, shouldAutoFocus: false, color: 'primary', size: 'md', maxHeight: '200px', } <MenuList {...args} aria-label="Remote branches" overflowY="auto"> {Array.from({ length: 20 }, (_, index) => ( <MenuItem key={index} value={index}> origin/feature-{index + 1} </MenuItem> ))} </MenuList>
Row Actions
- README.md
- Tabs.tsx
- Menu.tsx
- theme.ts
const args: Partial<MenuListProps> = { 'aria-label': 'Branch', shouldWrap: true, shouldAutoFocus: false, color: 'primary', size: 'md', } <OpenFiles {...args} />
Row Roles
- menuitem
- menuitemradio
- menuitemcheckbox
<MenuList aria-label="Roles"> {menuItemRoleTokens.map((role) => ( <MenuItem key={role} role={role} selected> {role} </MenuItem> ))} </MenuList>
ContextMenu
Default
Right-click here, or focus and press Shift+F10
const args: Partial<ContextMenuProps> = { marginThreshold: 2, elevation: 8, layer: 'modal', color: 'primary', size: 'md', 'aria-label': 'Canvas', } <ContextMenu {...args} target={canvas} />
Opened From Code
const args: Partial<ContextMenuProps> = { marginThreshold: 2, elevation: 8, layer: 'modal', color: 'primary', size: 'md', 'aria-label': 'Canvas', } <OpenedFromCodeDemo {...args} />
Rows From Data
Chosen: nothing yet
Right-click here, or focus and press Shift+F10
const args: Partial<ContextMenuProps> = { marginThreshold: 2, elevation: 8, layer: 'modal', color: 'primary', size: 'md', 'aria-label': 'Canvas', } <FromData {...args} />
MenuDivider
Default
- Cut
- Copy
- Select all
<MenuDivider />
MenuItems
Default
- New branch...
- Checkout...Coming later
- Local branches
- Remote branches
<MenuItems items={[{ value: 'new', label: 'New branch...', shortcut: 'Control+B' }, { value: 'checkout', label: 'Checkout...', disabled: true, note: 'Coming later' }, { type: 'divider' }, { label: 'Local branches', submenu: [{ value: 'main', label: 'main', role: 'menuitemradio', selected: true }, { value: 'feat', label: 'feat/menu', role: 'menuitemradio' }] }, { label: 'Remote branches', submenu: [], emptyLabel: 'Coming soon' }]} />