Skip to main content

ActionMenu

The ActionMenu widget presents a handy solution for web applications by providing a menu of additional actions upon clicking an element on a page. Whether it's editing, deleting, sharing, or performing any other relevant action, this widget provides an efficient user experience. With this menu type you can choose whether to provide common or different context for several targets. Besides, Action Menu allows setting a handler for custom triggering from an HTML element.

Action Menu

  • Get the widget by installing the @svar-ui/react-menu package.
  • Check ActionMenu API Reference to see the list of configuration properties and events.
  • Explore the samples to visualize the available features.

Initialization

To create an ActionMenu instance on a page, you need to complete several steps:

  • Import the source file of the ActionMenu widget on a page:
App.jsx
import { ActionMenu } from "@svar-ui/react-menu";

import "@svar-ui/react-core/style.css";
import "@svar-ui/react-menu/style.css";
  • Apply the <ActionMenu> tag to initialize an action menu:
App.jsx
<ActionMenu />
  • Load a data set with options into the menu:
App.jsx
<ActionMenu options={options} />
  • Make sure you apply a theme if you use the ActionMenu component standalone. Please, refer to applying a theme for more information.
<Willow>
<ActionMenu options={options} />
</Willow>

Loading options

Options can be loaded into ActionMenu on its initialization or after it. To begin with, you need to define a data set in the JSON format.

Specifying a data set

A data set will contain options of the Action Menu. You need to specify them as objects with attributes presented as key:value pairs. A data set for ActionMenu can have both a plain structure and a more complex one - tree-like structure.

  • a plain data set structure looks like this:
var options = [
{ id: "add-task:child", text: "Child task" },
{ id: "add-task:above", text: "Task above" },
{ id: "add-task:below", text: "Task below" },
];
  • a tree-like data set structure is the following:
var options = [
{ id: "add-task", text: "Add", icon: "wxi wxi-plus", data:[
{ id: "child", text: "Child task" },
{ id: "above", text: "Task above" },
{ id: "below", text: "Task below" }
]}
];

A menu item object may contain a set of properties, they are listed below:

  • id - the id of a menu item
  • text - the text of a menu item
  • subtext - a dimmed text displayed to the right of the main item's text, such as a hot key or other additional info
  • icon - the name of an icon displayed before the text. It converts to adding <i className={item.icon}>, so any icons with the wxi- prefix can be used here, e.g ".wxi-plus" or any custom icons defined on the page. Read more about the usage of icons
  • css - the name of a CSS class that will be applied to an item to change its style
  • type - the type of an item. It can be a "separator" or a custom type registered as a menu item (e.g. type:"button")
  • data - an array of sub-items for a menu item. Used to create a tree-like menu structure

Loading options on initialization

To load a prepared data set on initialization of the Action Menu, you should use the options property:

App.jsx
import { useState } from "react";
import { ActionMenu } from "@svar-ui/react-menu";

export default function App(){
const [message, setMessage] = useState("");
const options = [
{
text: "Cut",
icon: "wxi wxi-content-cut",
handler: ({ item }) => setMessage(`cut ${item}`)
},
{
text: "Copy",
icon: "wxi wxi-content-copy",
handler: ({ item }) => setMessage(`copy ${item}`)
},
{
text: "Paste",
icon: "wxi wxi-content-paste",
handler: ({ item }) => setMessage(`paste ${item}`)
},
];

return <ActionMenu options={options} />;
}

Related sample: Custom Area

Loading options after initialization

You can load options into ActionMenu after its initialization from a separate JS file. These are the steps to follow:

  • prepare a file with data ("data.js") in the same directory where your application is placed
  • specify a function that will return an array of menu options in the data file and export this function to make it available from outside:
data.js
export function getProjects(){
return [
{ id:"a", text:"Project A" },
{ id:"b", text:"Project B" },
{ id:"c", text:"Project C" },
{ id:"d", text:"Project D" }
]
}
  • import the function into your application and assign it to the variable specified for options
  • use the options configuration property in the <ActionMenu> tag. Set the name of the variable as its value:
App.jsx
import { ActionMenu } from "@svar-ui/react-menu";
import { getProjects } from "./data";

const options = getProjects();

export default function App(){
return <ActionMenu options={options} />;
}

Related sample: Action Menu

Adding custom options

Creating a menu item

You can use a custom component inside of an ActionMenu item. To add a custom menu item, create a file that will contain the code of your item, for example:

ButtonMenuItem.jsx
import { Button, Text } from "@svar-ui/react-core";

export default function ButtonMenuItem({ item }){
return (
<div>
<div style={{ width: 120 }} onClick={ev => ev.preventDefault()}>
<Text placeholder={item.name} />
</div>
<Button icon="wxi-plus" type="primary" />
</div>
);
}

By default, clicking anywhere inside of a custom item component will activate menu closing and the click handler triggering. You can prevent it by intercepting the onClick event and calling event.preventDefault() as in the above example.

Registering the item

When a component is ready, you should register it like this:

App.jsx
import { Button } from "@svar-ui/react-core";
import { ActionMenu } from "@svar-ui/react-menu";

import ButtonMenuItem from "./your_items/ButtonMenuItem.jsx";
import { registerMenuItem } from "../your_sources/helpers";

registerMenuItem("button", ButtonMenuItem);

The above code has added the type: "button" component.

Using the item as ActionMenu option

Now you can use the newly created item in the options configuration. A custom "Add New" button is added into the menu below:

App.jsx
import { useRef } from "react";
import { ActionMenu } from "@svar-ui/react-menu";
import { Button } from "@svar-ui/react-core";

export default function App(){
const options = [
{ id:1, text:"Add User", subtext:"Ctrl+A" },
{ id:2, text:"Refresh", subtext:"Ctrl+R" },
{ id:3, text:"Delete User", css:"danger" },
{ id:"btn", type:"button", name:"Add New" }
];

const menuRef = useRef();

return (
<>
<ActionMenu options={options} ref={menuRef} />
<Button onClick={() => menuRef.current?.show()}>Click me</Button>
</>
);
}

The result of adding a custom option into the menu is given below:

Custom option

Setting the position of Action Menu

You can control the position of Action Menu relative to the target via the at property. In the example below ActionMenu appears to the right of the target:

<ActionMenu options={options} onClick={clicked} at="right" />

Related sample: Custom area

The at property can have one of the values listed below:

  • "bottom" - (default) a menu appears below the target node, left borders aligned (the menu goes to the right of the action area)

Action Menu

  • "right" - a menu appears right to the target node

Action Menu right position

  • "left" - a menu appears left to the target node

Action Menu left position

  • "bottom-left" - a menu appears below the target node, right borders aligned (the menu goes to the left of the action area)

Action Menu bottom left position

  • "bottom-fit" - a menu appears below the target node, the width of the menu is equal to the width of the target node

Action Menu bottom fit position

  • "point" - a menu appears at the specified left/top position and ignores the provided node

Using Action Menu for multiple targets

It is possible to use Action Menu for multiple active areas. The key point of this feature is the resolver property of the widget. This prop enables the multi-area mode and the associated function will be called before opening the menu. The logic inside of this method is able to decide whether an action menu is necessary. You can specify whether the context for several areas will be the same or different.

Common context for different targets

In the example below Action Menu will appear for all items with the data-context-id attribute:

import { useRef } from "react";
import { ActionMenu } from "@svar-ui/react-menu";

const resolver = id => id;
const menuRef = useRef();

export default function App({ items, options }){
return (
<>
{/* a menu will appear for all items with the "data-context-id" attribute */}
<ActionMenu options={options} resolver={resolver} ref={menuRef} />

{items.map(item => (
<div
key={item.id}
className="item"
onClick={ev => menuRef.current?.show(ev)}
data-context-id={item.id}
/>
))}
</>
);
}

The result of the resolver call will be provided in the resulting click event as ev.detail.item (the custom event object dispatched by the ActionMenu).

Check the examples of valid resolvers below:

const allowAll = (id, ev) => true;
const allowElementsWithCorrectAttribute = (id, ev) => id;
const allowAndReturnTaskObject = (id, ev) => tasks[id];

The id inside the resolver is the value of the data-context-id attribute. It can be redefined with the help of the dataKey property like this:

<ActionMenu dataKey="dataId" />
<div data-id="1"></div>

Related sample: Custom area

Different context for different targets

In the multi-area mode Action Menu can be configured to show different items for different targets.

Action Menu

It can be done by defining the filter property which will be a function similar to filterMenu() in the following example:

const filterMenu = (menuItem, task) => {
// hide the "delete" item for projects
if (menuItem.id === "delete" && task.type === "project") return false;
return true;
}
<ActionMenu options={options} resolver={getTasks} filter={filterMenu} />

The filterMenu() function takes the following parameters:

  • menuItem - a single record from the options collection
  • task - the result of the resolver (getTasks()) function call

Related sample: Custom handler

Custom triggering of Action Menu

You can use Action Menu with a custom trigger that will allow you to show the menu anywhere and whenever you need.

You can perform custom triggering in two possible ways:

  • triggering via the data-context-id with the context as its value and the resolver function to match the context (please refer to Common context for different targets)
  • calling the show function and passing the context to it as the second attribute

The example below shows how to add the action menu for 4 buttons and how to manually control when to show the ActionMenu. First, you should get the menu object with available functions using a ref. Currently, the show function is available, which you can link to an HTML element. To determine when to show the menu, you should call menuRef.current.show(ev, i) inside the onClick event handler of the Button component. The index of each item (button) ("i") in active is passed as the context.

import { useRef, useState } from "react";
import { ActionMenu } from "@svar-ui/react-menu";
import { Button } from "@svar-ui/react-core";

export default function App(){
const [active, setActive] = useState(["a", "b", "c", "d"]);
const menuRef = useRef();

function clicked(ev){
const { context, action } = ev;
if (action) {
const next = [...active];
next[context] = action.id;
setActive(next);
}
}

return (
<>
<ActionMenu onClick={clicked} ref={menuRef} />
{active.map((item, i) => (
<Button
key={i}
onClick={ev => menuRef.current?.show(ev, i)}
value={active[i]}
/>
))}
</>
);
}

Related samples:

Besides the click event, you can use onContextMenu or any other DOM event as well. It is also possible to show ActionMenu from custom code. It is important that the first argument will be an object that contains a target or the clientX/clientY coordinates. For example:

// showing a menu manually with a DOM event
menuRef.current.show(ev);

// or with a "fake" event related to the node
menuRef.current.show({ target: DOMTarget });

// at a specific position
menuRef.current.show({ clientX: 100, clientY: 100 });

// if necessary the function can receive the related item as well
// this value will be passed to the resolver function, if it is defined, and to the click event handler
// the click event will get the item as context
menuRef.current.show(ev, item);

Catching the change of a clicked option

You can catch the change of a clicked option by handling the onClick event. Inside the event you can get an object of the clicked option for further usage:

import { useRef, useState } from "react";
import { ActionMenu } from "@svar-ui/react-menu";
import { Button } from "@svar-ui/react-core";

export default function App(){
const [message, setMessage] = useState("");
const menuRef = useRef();

function clicked(ev){
const action = ev.action;
setMessage(action ? `clicked on ${action.id}` : "closed");
}

return (
<>
<ActionMenu options={options} onClick={clicked} ref={menuRef} />
<Button type="primary" onClick={() => menuRef.current?.show()}>Click me</Button>
</>
);
}

The event handler will receive an object related to the clicked menu item.

Related sample: Action Menu

Styling an item of Action Menu

You can apply a particular style to a menu item via the css attribute of an item object. Use a global CSS rule while specifying a style to reach an isolated menu item. In the example below, the text of the third menu option turns red:

App.jsx
import { useRef } from "react";
import { ActionMenu } from "@svar-ui/react-menu";
import { Button } from "@svar-ui/react-core";

export default function App(){
const options = [
{ id:1, text:"Add User", subtext:"Ctrl+A" },
{ id:2, text:"Refresh", subtext:"Ctrl+R" },
{ id:3, text:"Delete User", css:"my-color" }
];

const menuRef = useRef();

return (
<>
<ActionMenu options={options} ref={menuRef} />
<Button onClick={() => menuRef.current?.show()}>Click me</Button>
</>
);
}

Add a global CSS rule (separate CSS file or global style) like:

/* global.css */
.item.my-color span {
color: red;
}

As a result, the menu will look like this:

Menu item styling