useMosaicLayout
Hooks

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.

SignatureTypeScript
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 };
};
MosaicLayoutController props
PropTypeDefaultDescription
layoutMosaicLayout—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.
canUndoboolean—Whether an earlier layout exists in undo history.
canRedoboolean—Whether an earlier layout exists in undo history.
gridPropsobject—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.

ExampleTSX
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.

UndoTSX
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.

RestoreTSX
useEffect(() => {
  const saved = localStorage.getItem("board:on-call");
  if (saved) dash.restore(JSON.parse(saved));
}, []);