ContextMenu
ContextMenu manages right-click menu coordinates and renders menu content in a portal.
Props
| Export | Key props | Description |
|---|---|---|
ContextMenu | container | Provider that stores open state and optional boundary element. |
ContextMenuTrigger | disabled, onlyDirectTarget, children | Opens the surrounding menu from trigger content. Child contextmenu handlers can prevent opening. |
ContextMenuContent | closeOnItemClick, div props | Portal layer clamped to the viewport or container. |
useContextMenu | - | Programmatic access to openAt and close. |
Examples
Right-click menu
Right click this field
import { ContextMenu, ContextMenuContent, ContextMenuTrigger, Menu, MenuItem, MenuSeparator } from '@murasaki/react98'
export function ContextMenuBasicDemo(): React.ReactElement {
return (
<ContextMenu>
<ContextMenuTrigger>
<div className="flex h-32 w-72 items-center justify-center bg-(--window) text-(--window-text) shadow-(--shadow-border-field)">
Right click this field
</div>
</ContextMenuTrigger>
<ContextMenuContent>
<Menu className="w-44">
<MenuItem>Open</MenuItem>
<MenuItem selected>Rename</MenuItem>
<MenuSeparator />
<MenuItem disabled>Paste</MenuItem>
<MenuItem>Properties</MenuItem>
</Menu>
</ContextMenuContent>
</ContextMenu>
)
}Adaptive edge placement
Top-left corner
Top-right corner
Center area
Bottom-left corner
Bottom-right corner
Right-click any target to see adaptive placement.
import {
ContextMenu,
ContextMenuContent,
ContextMenuTrigger,
Menu,
MenuItem,
MenuSeparator,
useContextMenu,
} from '@murasaki/react98'
import { Fragment } from 'react'
const TARGETS = [
{ id: 'top-left', label: 'Top-left corner', style: { gridColumn: 1, gridRow: 1 } },
{ id: 'top-right', label: 'Top-right corner', style: { gridColumn: 3, gridRow: 1 } },
{ id: 'center', label: 'Center area', style: { gridColumn: 2, gridRow: 2 } },
{ id: 'bottom-left', label: 'Bottom-left corner', style: { gridColumn: 1, gridRow: 3 } },
{ id: 'bottom-right', label: 'Bottom-right corner', style: { gridColumn: 3, gridRow: 3 } },
] as const
const ITEMS = [
'Open',
'Explore',
'Search',
'Send To',
'Cut',
'Copy',
'Create Shortcut',
'Delete',
'Rename',
'Properties',
'View',
'Arrange Icons',
'Refresh',
'Paste',
'Paste Shortcut',
]
function RightClickTarget({
label,
style,
}: {
label: string
style: React.CSSProperties
}): React.ReactElement {
return (
<ContextMenuTrigger>
<div
style={{
...style,
alignItems: 'center',
background: 'var(--button-face)',
boxSizing: 'border-box',
boxShadow: 'var(--shadow-raised)',
color: 'var(--button-text)',
display: 'flex',
fontSize: 12,
height: '100%',
justifyContent: 'center',
minHeight: 72,
minWidth: 0,
padding: '0 12px',
textAlign: 'center',
width: '100%',
}}
>
{label}
</div>
</ContextMenuTrigger>
)
}
function ContextMenuMaxHeight({
children,
}: {
children: (maxHeight: number | undefined) => React.ReactNode
}): React.ReactElement {
const ctx = useContextMenu()
return <>{children(ctx.availableHeight ?? undefined)}</>
}
export function ContextMenuEdgeTestDemo(): React.ReactElement {
return (
<ContextMenu>
<div
style={{
background: 'var(--window)',
boxSizing: 'border-box',
boxShadow: 'var(--shadow-border-field)',
display: 'grid',
gap: 12,
gridTemplateColumns: 'repeat(3, minmax(0, 1fr))',
gridTemplateRows: 'repeat(3, minmax(0, 1fr))',
height: '100%',
minHeight: 280,
padding: 12,
width: '100%',
}}
>
{TARGETS.map(target => (
<RightClickTarget key={target.id} label={target.label} style={target.style} />
))}
<div
style={{
color: 'var(--gray-text)',
fontSize: 11,
gridColumn: 2,
gridRow: 3,
placeSelf: 'end center',
textAlign: 'center',
width: '100%',
}}
>
Right-click any target to see adaptive placement.
</div>
</div>
<ContextMenuContent>
<ContextMenuMaxHeight>
{maxHeight => (
<Menu maxHeight={maxHeight} style={{ width: 192 }}>
{ITEMS.map((item, index) => (
<Fragment key={item}>
<MenuItem reserveIconSpace>{item}</MenuItem>
{(index === 3 || index === 8) && <MenuSeparator />}
</Fragment>
))}
</Menu>
)}
</ContextMenuMaxHeight>
</ContextMenuContent>
</ContextMenu>
)
}ARIA
Pair ContextMenuContent with semantic Menu and MenuItem children. Opening the layer moves focus into the menu and closing restores focus to the previously focused element. Escape, outside pointer, scroll, and resize dismiss the layer.
Keyboard
When opened with Menu children, focus starts on the first enabled menu item. Arrow keys, Home, End, typeahead, Enter, and Space follow the Menu keyboard model. Escape closes the layer.
SSR
ContextMenuContent renders only after the menu opens on the client. The trigger child should be stable during hydration.
Last updated on