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.
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.
| item | command | enable when |
|---|---|---|
| Close | tab.close { tabId } | model.can("tab.close", …) |
| Close others | batch of tab.close, one per other closable tab | at least one |
| Close to the right | batch of tab.close for the closable tabs after it | at least one |
| Close all | tabset.close { tabsetId }, or a batch of tab.close | model.can("tabset.close", …) |
| Rename | start your rename UI, then tab.rename { tabId, label } | model.can("tab.rename", { tabId, label: tab.label }) (the tab's renamable) |
| Pin / Unpin | tab.pin { tabId, value } | model.can("tab.pin", …) (refused in a border) |
| Pop out | tab.popout { tabId } | engine.is("popout-supported") and model.can("tab.popout", …) |
| Pop out the tabset | tabset.popout { tabsetId } | as Pop out, with tabset.popout |
| Maximize / Restore | tabset.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).
Menus and drags
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.