Documentation
Guides

Menus

The package provides the commands and their model.can answers; you render every menu with your own components and your own text.

Dockable renders no menus. A tab context menu, a tabset "more" menu or an overflow menu is a component you write, whose items run commands. model.can tells each item whether its command would apply (the layout's rules and your middleware included), so an item is disabled exactly when running it would be refused. This page lists the items a layout usually offers and the command behind each.

Tab context menu
Gallery

A tab context menu

Attach the menu to the Tab with onContextMenu (props are forwarded), or make the tab your menu library's trigger with render. With Fragiola UI's ContextMenu (Base UI):

import { Dockable, type TabOf, useDockable } from "@fragiola/dockable-react";
import { ContextMenu } from "#/components/ui/context-menu";

type Types = { tabs: { card: undefined } };

function TabWithMenu({ tab }: { tab: TabOf<Types> }) {
    const { model } = useDockable<Types>();
    const pinned = tab.pinned === true;
    return (
        <ContextMenu.Root>
            <Dockable.Tab node={tab} render={<ContextMenu.Trigger />}>
                {tab.label}
            </Dockable.Tab>
            <ContextMenu.Content>
                <ContextMenu.Item
                    disabled={!model.can("tab.close", { tabId: tab.id })}
                    onClick={() => model.run("tab.close", { tabId: tab.id })}
                >
                    Close
                </ContextMenu.Item>
                <ContextMenu.Item
                    disabled={!model.can("tab.pin", { tabId: tab.id, value: !pinned })}
                    onClick={() => model.run("tab.pin", { tabId: tab.id, value: !pinned })}
                >
                    {pinned ? "Unpin" : "Pin"}
                </ContextMenu.Item>
            </ContextMenu.Content>
        </ContextMenu.Root>
    );
}

Read the component's source for its exact parts; the pattern (a trigger rendering the Dockable.Tab, items running commands, model.can deciding what is enabled) is the same with any menu library. Make sure the trigger forwards the ref and props to the tab element, or wrap the tab in the trigger instead.

The usual items

parent is model.get("node-parent-by", { nodeId: tab.id }): the tab's tabset, or its border.

itemcommandenable when
Closetab.close { tabId }model.can("tab.close", …)
Close othersbatch of tab.close, one per other closable tabat least one
Close to the rightbatch of tab.close for the closable tabs after itat least one
Close alltabset.close { tabsetId }, or a batch of tab.closemodel.can("tabset.close", …)
Renamestart your rename UI, then tab.rename { tabId, label }model.can("tab.rename", { tabId, label: tab.label }) (the tab's renamable)
Pin / Unpintab.pin { tabId, value }model.can("tab.pin", …) (refused in a border)
Pop outtab.popout { tabId }engine.is("popout-supported") and model.can("tab.popout", …)
Pop out the tabsettabset.popout { tabsetId }as Pop out, with tabset.popout
Maximize / Restoretabset.maximize { tabsetId, value }model.can("tabset.maximize", …)
Overlay / Docked (a border)border.configure { borderId, mode }always

"Several closes" are one batch: one change event, one undo step, and atomic (if one command fails, none applies). Filter the tabs with model.can first, so a pinned or unclosable tab does not make the whole batch fail:

import type { BatchEntry, TabOf } from "@fragiola/dockable-react";

const parent = model.get("node-parent-by", { nodeId: tab.id });
const siblings = parent && parent.type !== "row" ? parent.children : [];
const others = siblings.filter((t) => t.id !== tab.id && model.can("tab.close", { tabId: t.id }));

model.run("batch", {
    commands: others.map((t): BatchEntry<Types> => ({ command: "tab.close", payload: { tabId: t.id } })),
});

tab.popout from a menu opens the window at a default place; engine.run("popout", { nodeId: tab.id }) (the engine from useDockable) runs the same command with the tab's current screen rect, so the window opens where the tab was. See Popouts.

The menu's text is yours: the package has none, and no label keys. Keep your strings wherever your app keeps them, translated or not (see Accessible names).

A context menu opened on a tab does not interfere with dragging (a drag starts from a primary button press and a move). A menu button inside a tab is part of the tab: stop its pointerdown from reaching the tabset, which would become active, and its click from reaching the tab, which would select it, as the close button does in Tabs.

Overflow menus

A menu listing a tabset's hidden tabs is covered in Tab overflow.