useMosaicLayout
Holds a MosaicGrid's layout, its undo history, and the add, remove, and reset operations.
Signature
MosaicGrid handles pointer edits itself. Use this hook for edits from outside the grid: an "add tile" menu, a remove button in a tile header, a "reset layout" item, or restoring a saved layout. It wraps the pure layout functions so these edits settle the board the same way a drag does.
function useMosaicLayout(options?: {
/** Normalised once on mount, so a hand-written array is safe. */
defaultLayout?: MosaicLayout;
columns?: number; // default 12
compact?: "vertical" | "none"; // default "vertical"
allowOverlap?: boolean;
historyLimit?: number; // default 50; 0 turns undo off
}): {
layout: MosaicLayout;
setLayout: (next: MosaicLayout) => void;
restore: (next: MosaicLayout) => void;
add: (item: MosaicPlacement) => void;
remove: (id: string) => void;
update: (id: string, patch: Partial<Omit<MosaicLayoutItem, "id">>) => void;
move: (id: string, x: number, y: number) => void;
resize: (id: string, w: number, h: number) => void;
has: (id: string) => boolean;
item: (id: string) => MosaicLayoutItem | undefined;
reset: () => void;
undo: () => void;
redo: () => void;
canUndo: boolean;
canRedo: boolean;
/** Spread onto MosaicGrid to wire it up in one prop. */
gridProps: { layout; onLayoutChange; columns; compact; allowOverlap };
};| Prop | Type | Default | Description |
|---|---|---|---|
layout | MosaicLayout | — | Current layout array. |
setLayout | (next: MosaicLayout) => void | — | Replaces the layout without normalization. Used by gridProps.onLayoutChange for grid-normalized values. |
restore | (next: MosaicLayout) => void | — | Adopts an arbitrary layout after clamping and settling it. Use this for anything from outside: storage, a preset, another user. |
add | (item: MosaicPlacement) => void | — | Adds a tile. Omit x and y and it lands in the first free slot, found in reading order. |
remove | (id: string) => void | — | Removes a tile and closes the gap it leaves. |
update | (id, patch) => void | — | Patches one item (a new minW, a static pin) and re-settles the board. |
move | (id, x, y) => void | — | Moves a tile's top-left corner, pushing whatever it lands on. |
resize | (id, w, h) => void | — | Resizes a tile within its minimum, maximum, and grid-column limits. |
has | (id: string) => boolean | — | Whether a tile is on the board. Use it to gate an “add tile” menu. |
item | (id) => MosaicLayoutItem | undefined | — | One entry, by id. |
reset | () => void | — | Restores defaultLayout, for a “reset layout” menu item. |
undo | () => void | — | Steps back one edit. Each change (a drop, a resize, an add, a remove, a reset) is one step. |
redo | () => void | — | Steps forward again. Cleared as soon as a new edit is made. |
canUndo | boolean | — | Whether an earlier layout exists in undo history. |
canRedo | boolean | — | Whether an earlier layout exists in undo history. |
gridProps | object | — | layout, onLayoutChange, columns, compact and allowOverlap in one spreadable object, so the hook's options and the grid's can never disagree. |
Usage
Layout editor
The hook holds the layout array, gridProps wires it to the grid, and the controls around it call add, remove, and reset. Tiles are rendered from layout, so removing an entry removes a tile.
const dash = useMosaicLayout({ defaultLayout: DEFAULT_LAYOUT, columns: 12 });
const available = Object.keys(WIDGETS).filter((key) => !dash.has(key));
return (
<>
<DropdownMenu trigger={<Button size="sm">Add tile</Button>}>
{available.map((key) => (
<MenuItem
key={key}
onClick={() => dash.add({ id: key, w: 4, h: 2, minH: 2 })}
>
{WIDGETS[key].title}
</MenuItem>
))}
<MenuDivider />
<MenuItem onClick={dash.reset}>Reset to default</MenuItem>
</DropdownMenu>
<MosaicGrid {...dash.gridProps} editable={editing}>
{dash.layout.map((item) => (
<MosaicTile
key={item.id}
id={item.id}
title={WIDGETS[item.id].title}
actions={
<IconButton
size="sm"
variant="ghost"
icon={<IconX size={12} />}
aria-label={`Remove ${WIDGETS[item.id].title}`}
onClick={() => dash.remove(item.id)}
/>
}
>
{WIDGETS[item.id].render()}
</MosaicTile>
))}
</MosaicGrid>
</>
);Undo
Each layout-changing edit creates one undo step. Edits that leave the layout unchanged are not recorded.
const dash = useMosaicLayout({ defaultLayout: DEFAULT_LAYOUT });
<IconButton
aria-label="Undo layout change"
disabled={!dash.canUndo}
icon={<IconArrowBackUp size={14} />}
onClick={dash.undo}
/>;Restoring a saved board
restore clamps items to the current grid and resolves overlaps. Use restore when loading saved or externally supplied layouts.
useEffect(() => {
const saved = localStorage.getItem("board:on-call");
if (saved) dash.restore(JSON.parse(saved));
}, []);