A declarative context menu for React 😲 !
- Demo
- Installation
- Usage
- Api
- To-Do
- Migration from v2 to v3
- Browser Support
- Release Notes
- Contribute
- License
⚠️ The v3 introduces a lot of breaking changes. Please consider reading the migration guide.
Live demo here
$ yarn add react-contexify
or
$ npm install --save react-contexify
import { ContextMenu, Item, Separator, Submenu, ContextMenuProvider } from 'react-contexify';
import 'react-contexify/dist/ReactContexify.min.css';
const onClick = ({ event, ref, data, dataFromProvider }) => console.log('Hello');
// create your menu first
const MyAwesomeMenu = () => (
<ContextMenu id='menu_id'>
<Item onClick={onClick}>Lorem</Item>
<Item onClick={onClick}>Ipsum</Item>
<Separator />
<Item disabled>Dolor</Item>
<Separator />
<Submenu label="Foobar">
<Item onClick={onClick}>Foo</Item>
<Item onClick={onClick}>Bar</Item>
</Submenu>
</ContextMenu>
);
const App = () => (
<div>
<h1>Welcome to My App</h1>
<ContextMenuProvider id="menu_id">
<div>Some Content ... </div>
</ContextMenuProvider>
<MyAwesomeMenu />
</div>
);
The ContextMenuProvider
expose a renderTag
prop to let you do that.
const Tr = (props) => (
<ContextMenuProvider id="menu_id" renderTag="tr">
<td>{props.cel1}</td>
<td>{props.cel2}</td>
</ContextMenuProvider>
);
You can disable an Item
with a boolean or a callback. When a callback is used, a boolean must be returned. The callback has access to the same parameter as the onClick
callback.
const isDisabled = ({ event, ref, data, dataFromProvider }) => {
return true;
}
<ContextMenu id='menu_id'>
<Item disabled>Foo</Item>
<Item disabled={isDisabled}>Bar</Item>
</ContextMenu>
Disable a Submenu
is simple as disabling an Item
. The disabled callback is slightly different, there is no data param.
<ContextMenu id='menu_id'>
<Item>Foo</Item>
<Submenu label="Submenu" disabled>
<Item>Bar</Item>
</Submenu>
</ContextMenu>
<ContextMenu id='menu_id'>
<Item>Foo</Item>
<Submenu label="Submenu" arrow="🦄">
<Item>Bar</Item>
</Submenu>
<Separator />
<Submenu label="Submenu" arrow={<i className="rocket">🚀</i>}>
<Item>Bar</Item>
</Submenu>
</ContextMenu>
The onClick
callback of the Item
component gives you access to an object with 4 properties:
The event property refers to the native event which triggered the menu. It can be used to access the mouse coordinate or any other event prop.
const onClick = ({ event, ref, data, dataFromProvider }) => {
// Accessing the mouse coordinate
console.log(event.clientX, event.clientY);
}
If you wrap a single react component ref will be the mounted instance of the wrapped component.
If you wrap more than one component ref will be an array containing a ref of every component.
ref will be an instance of the react component only if the component is declared as a class
For more details about ref please read this
- With a single component
const Wrapped = () => (
<ContextMenuProvider id="id">
<Avatar id="foobar" />
</ContextMenuProdider>
);
const onClick = ({ event, ref, data, dataFromProvider }) => {
// Retrieve the Avatar id
console.log(ref.props.id);
}
- With more than one component
const Wrapped = () => (
<ContextMenuProvider id="id">
<Avatar id="foobar" />
<Avatar id="plop" />
</ContextMenuProdider>
);
const onClick = ({ event, ref, data, dataFromProvider }) => {
// Print foobar
console.log(ref[0].props.id);
// Print plop
console.log(ref[1].props.id);
}
- With an html node, the ref contains the html node 🤣
const Wrapped = () => (
<ContextMenuProvider id="id">
<div id="foobar" data-xxx="plop">bar</div>
</ContextMenuProdider>
);
const onClick = ({ event, ref, data, dataFromProvider }) => {
// Retrieve the div id
console.log(ref.id);
// Access the data attribue
console.log(ref.dataset.xxx);
}
With more than one html node wrapped you get an array as well.
const onClick = ({ event, ref, data, dataFromProvider }) => {
// Print Ada
console.log(data.name);
}
const YourMenu = () => (
<ContextMenu>
<Item data={name: 'Ada'} onClick={onClick}>Hello</Item>
</ContextMenu>
);
The data prop passed to the ContextMenuProvider
is accessible for every Item
as dataFromProvider
.
const Wrapped = () => (
<ContextMenuProvider id="id" data={name: 'Ada'}>
<div id="foobar" data-xxx="plop">bar</div>
</ContextMenuProdider>
);
const onClick = ({ event, ref, data, dataFromProvider }) => {
// Print Ada Again
console.log(dataFromProvider.name);
}
- As a developer, pick only what you want:
({ ref }) => {}
- As a maintainer, easier to extend the library:
({ event, ref, data, dataFromProvider, theFithParameter }) => {}
'destructuring'.substring(-1, 8)
💥
Props | Default | Required | Description |
---|---|---|---|
id: string | number | - | ✓ | Id used to map your component to a context menu |
renderTag: string | 'div' | ✘ | The tag used to wrap the child component |
event: string | 'onContextMenu' | ✘ | Same as React Event (onClick, onContextMenu ...). Event used to trigger the context menu |
data: any | - | ✘ | Data are passed to the Item onClick callback. |
storeRef: boolean | true | ✘ | Store ref of the wrapped component. |
className: string | - | ✘ | Additional className |
style: object | - | ✘ | Additional style |
<ContextMenuProvider id="menu_id" data={foo: 'bar'}>
<MyComponent />
</ContextMenuProvider>
Props | Required | Possible Value | Description |
---|---|---|---|
id: string | number | ✓ | - | Used to identify the corresponding menu |
style: object | ✘ | - | An optional style to apply |
classname: string | ✘ | - | An optional css class to apply |
theme: string | ✘ | light | dark | Theme is appended to react-contexify__theme--${given theme} |
animation: string | ✘ | fade | flip | pop | zoom | Animation is appended to .react-contexify__will-enter--${given animation} |
// You can set built-in theme and animation using the provided helpers as follow
import { ContextMenu, Item, theme, animation } from 'react-contexify';
<ContextMenu id="foo" theme={theme.dark} animation={animation.pop}>
<Item>Foo</Item>
<Item disabled>Bar</Item>
{/* and so on */}
</ContextMenu>
Props | Default | Required | Description |
---|---|---|---|
label: node | - | ✓ | Submenu label. It can be a string or any node element like <div>hello</div> |
disabled: bool | ({ event, ref, dataFromProvider }) => bool | false | ✘ | Disable the item. If a function, it must return a boolean. |
arrow: node | - | ✘ | Define a custom arrow |
Props | Default | Required | Description |
---|---|---|---|
disabled: bool | ({ event, ref, data, dataFromProvider }) => bool | false | ✘ | Disable the item. If a function, it must return a boolean. |
onClick: ({ event, ref, data, dataFromProvider }) => void | - | ✘ | Callback when the item is clicked |
data: any | - | ✘ | Additional data that will be passed to the callback |
Don't expect any props. It's just a separator xD.
<Separator />
Props | Required | Description |
---|---|---|
className: string | ✘ | Additional className |
style: object | ✘ | Additional style |
The icon font renders a i tag. It's just a helper
//example with Font Awesome
<Item>
<IconFont className="fa fa-trash" />Delete
</Item>
//example with material icons
<Item>
<IconFont className="material-icons">remove_circle</IconFont>Delete
<Item>
- Allow keyboard navigation
- Switch or not to styled component?
- Accessibility
- RTL support
A huge part of the code has been reviewed. The api has been simplified.
- There is no more
leftIcon
andrightIcon
on theItem
component. Do<Item><IconFont className="fa fa-delete" /> delete</Item>
instead - The
menuProvider
HOC has been removed. You can create yours easely - The
onClick
callback use destructuring assignment over explicit parameter
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
---|---|---|---|---|---|
IE 11+ ✔ | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ |
- Support submenu
- Add typescript definition
- Reviewed the api
- Upgrade to react 16
- Fix child references. Thanks to @kinke
- Fixed issue 33
- Removed Proxy Container.
- Fixed for real ! issue 27
- Context menu is now rendered outside of the main react root component to avoid the fixed position being broken by the parent style. For more details please see issue 27
- Simplified implementation Pull Request #22
- Typo fix in documentation
- Pass props down to
menuProvider
function
- Will now hide menu on mousedown.Relate to issue 19
- Added flag noIdents to cssnano to avoid animation name collision with other libs
- Fixed zoom animation
- Minor code cleanup
- Firefox trigger a click event also on context menu which was making the menu disappear. Relate to issue 16
- Fix issue #14
- conditional rendering was badly tested, shame on me !
- This version introduce breaking changes for the item component
- Upgrade to
prop-types
- Tested with jest and enzyme
- Reviewed build system
- The
onClick
callback provide a ref to the wrapped component - Can now use condtionnal rendering for dynamic menu. Relate to issue 12
- Added
IconFont
component
- Fixed right click behavior. Relate to issue 11
- Added possibility to set the render tag for the wrapper
- Added a ContextMenuProvider component
- Added possibility to set className and style to ContextMenuProvider
- Removed ContextMenuProvider style.Was too unpredictable
- fixed incorrect PropTypes used
- dead code elimination
Any idea and suggestions are welcome.
React Contexify is licensed under MIT.