Skip to content
marek edited this page Oct 19, 2025 · 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 etc.

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
) => 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, app menu can be made complex and structured based on app's needs.

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.

Add MenuItem into Section:

 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",
            },
        });
    }, []);

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