Skip to content

Navigation Menu

Sign in
Sign up
marek edited this page Apr 5, 2026 · 4 revisions

Menu

Introduction

[画像:obrazek]

Menu is a component with the name of the app, isDev badge and buttons with different purposes. It is rendered in Header component like this:

export function Header() {
 return (
 <TopBar>
 <Menu></Menu>
 <div className="header__right-side">
 <NotificationBell></NotificationBell>
 <Avatar></Avatar>
 </div>
 </TopBar>
 );
}

Menu buttons are handled by a special context provider called MenuProvider. This way, the application can have some initial default buttons, or it can also add buttons based on the current page/component, replace them and restore them or delete them completely.

Provider

Use useMenu hook to get access to the functions which change the menu state. Currently, there are these functions that can be utilised:

menu: Menu;
setMenu: React.Dispatch<React.SetStateAction<Menu>>;
addRootMenuItem: (item: RootMenuItem) => void;
deleteRootMenuItem: (id: string) => RootMenuItem | undefined;
retrieveIdByTitle: (name: string) => string | undefined;
addMenuItemIntoSection: (
 rootMenuItemId: string,
 sectionId: string,
 newItem: MenuItem,
 index?: number,
) => void;
addSectionIntoRootItem: (
 rootMenuItemId: string,
 section: Section,
 index?: number,
) => void;
deleteMenuItem: (menuItemId: string) => void;
replaceMenuItem: (menuItemId: string, newItem: MenuItem) => void;
restoreMenuItem: (menuItemId: string) => void;
[画像:obrazek]

A menu is a tree-like structure in its nature. The roots are so-called RootMenuItem objects. They can be added/deleted using addRootMenuItem and deleteRootMenuItem functions. It is worth noting that RootMenuItem is just a ordinary MenuItem with a order priority:

export type Priority = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
export type RootMenuItem = MenuItem & { priority: Priority };

The highest priority is 1. Root menu items are ordered based on priority. As for MenuItem, it is a general object which has id, title and task, and may have an icon. The most important is task - this tells what the item is: if it is an action, such as a button with a click handler, or a _dropdown _containing other menu items.

export type MenuItem = {
 id: string;
 title: string;
 icon?: MenuIcon;
 task: Dropdown | Action;
};

The Action is quite straigh-forward:

/**
 * The action can be either:
 * - `direct`: the action is executed immediately.
 * - `secondary`: the action triggers an intermediate step,
 * such as opening a file explorer or another dialog, before
 * the actual action runs.
 */
export type ActionType = "direct" | "secondary";
export type Action = {
 action: () => void;
 type: ActionType;
};

As for Dropdown, it is actually an array of Section. The Section object is a tool which can contain, recursively, other MenuItem. It can also have a label:

export type Section = {
 id: string;
 title?: string;
 items: MenuItem[];
 visible?: () => boolean;
};

This way, the app menu can be made complex and structured based on the app's needs.

Initial default menu

Application currently has an initial value containing three root menu items: File, Settings and Help - Settings is just a navigation action, Help will probably contain one section with a few actions, like to show the About dialogue. File item has three sections:

  • general containing two (three) actions: view file, process file and (open recent file)
  • dev-only section with an action to open dev tools
  • exit section with exit action item.

These are considered to be system menu tools, more or less general for all pages, therefore they are defined in src/ui/features/menu/systemMenuItems.ts. However, it is possibly to (delete them, of course) replace them using replaceMenuItem function and then easily restore the original one using restoreMenuItem:

useEffect(() => {
 const customExitAction = createCustomExitAction();
 replaceMenuItem("exit", createExitMenuItem(customExitAction));
 return () => {
 restoreMenuItem("exit");
 };
}, [...]);

Usage in page

Add page/component-specific root menu item:

const clearViewerItem: MenuItem = {
 id: "clear-viewer",
 title: t("menu.pageSpecific.viewer.Clear viewer"),
 icon: { icon: BroomIcon, position: "left" },
 task: {
 action: () => {
 clearViewer();
 },
 type: "direct",
 },
 };
 const section: Section = {
 id: "general-edit",
 items: [clearViewerItem],
 };
 const edit: RootMenuItem = {
 id: "edit",
 title: "Edit",
 task: [section],
 priority: 3,
 };
 useEffect(() => {
 addRootMenuItem(edit);
 return () => {
 deleteRootMenuItem(edit.id);
 };
 }, []);

Notice that the useEffect clean-up function deletes the root menu item after the page is unmounted.

You can also add MenuItem into Section using MenuProvider API:

 useEffect(() => {
 addMenuItemIntoSection("file", "general-file", {
 id: "open-file-in-viewer",
 title: "Open file in viewer",
 icon: { icon: IconFolderOpen, position: "left" },
 task: {
 action: () => {
 loadAndHandleFile({ regime: "toView" }, actions);
 },
 type: "secondary",
 },
 }, 2); // Index is 2 here, which makes it possible to change order of menu items in the section.
 }, []);

In this example, the button will remain in the menu for the lifetime of MenuProvider - which means probably for the app lifetime.

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /