Documentation
Guides

Maximize

Let a tabset fill its layout with the tabset.maximize command, and style the maximized state from data attributes.

Maximizing a tabset makes it fill its layout: every other tabset, every row that does not contain it and every row splitter is hidden (structurally, display: none), so the maximized tabset takes the whole root row, however deeply it is nested.

This differs from FlexLayout, which moves (portals) the maximized tabset over the layout. Dockable hides what is off the maximized tabset's path instead: nothing remounts, and the content keeps its moveable element. The rule is the core's model.is("node-hidden-by-maximize", { nodeId }), which TabSet and Row read (TabSetState.hidden, RowState.hidden).

Maximize
Gallery

The command

tabset.maximize with value: true maximizes a tabset (and makes it the active one); value: false restores it. It is refused when the tabset's maximizable resolves to false, and for the only tabset of its layout (there is nothing to hide). model.can answers the same question without running anything, so a button can hide itself:

import { type TabsetNode, useDockable } from "@fragiola/dockable-react";

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

function MaximizeButton({ tabset }: { tabset: TabsetNode<Types> }) {
    const { model } = useDockable<Types>();
    const maximized = model.is("tabset-maximized", { tabsetId: tabset.id });
    if (!model.can("tabset.maximize", { tabsetId: tabset.id, value: true })) return null;
    return (
        <button
            type="button"
            aria-pressed={maximized}
            aria-label={maximized ? "Restore" : "Maximize"}
            onClick={() => model.run("tabset.maximize", { tabsetId: tabset.id, value: !maximized })}
        >
            {maximized ? <MinimizeIcon aria-hidden="true" /> : <MaximizeIcon aria-hidden="true" />}
        </button>
    );
}

The command needs only the tabset: each layout has its own maximized tabset, so in a popout window maximize applies to that window's layout, and model.is("tabset-maximized", { tabsetId }) answers for the tabset's own layout. layoutId from useDockable is the layout the component renders in (MAIN_LAYOUT or a window's id), which model.get("maximized-tabset", { layoutId }) takes.

A double click on the tab strip is a common shortcut; attach it to your header element, and skip presses on the tabs and buttons inside it:

<div
    className="header"
    onDoubleClick={(event) => {
        if ((event.target as Element).closest('[role="tab"], button')) return;
        model.run("tabset.maximize", {
            tabsetId: node.id,
            value: !model.is("tabset-maximized", { tabsetId: node.id }),
        });
    }}
>

Maximizing is allowed unless maximizable: false, per tabset or for every tabset through defaults.tabset.maximizable. Moving the maximized tabset (or a row holding it) elsewhere restores the layout.

Styling the state

attributeonmeaning
data-maximizedDockable.Roota tabset of the main layout is maximized
data-maximizedDockable.TabSetthis tabset is maximized
<Dockable.TabSet node={node} className="border border-palette-line data-maximized:border-palette-ring" />

The hidden tabsets and splitters need no styling: the primitives hide them. Their tabs' panels are hidden too (Panel's data-visible is absent), and their content stays mounted.

Reading it

model.get("maximized-tabset", { layoutId }) returns the maximized tabset of a layout (the main one without layout), or undefined. To re-render when it changes, select it with useModelState:

const maximized = useModelState((_state, model) => model.get("maximized-tabset")?.id);

The maximized state is part of the layout: model.get("layout-json") saves it as the layout's maximized (the tabset's id; a window layout has its own), and createModel or layout.load restores it.

Keyboard

There is no default key binding for maximize. Add one in your own key handler (the maximize example restores on Escape) and run the same command (see Keyboard and accessibility).