Drag a tile by its header to move it; drag a side / bottom / corner grip to resize. The board keeps one layout per column count: rearrange at one width and the other widths keep theirs. Below the stack width the tiles stack in one column. Layouts persist to localStorage.
Board width: —px Columns: —
{}
<bs-tile-manager
[tiles]="tiles()"
[(layouts)]="layouts"
[stackBelow]="576"
[gap]="12"
label="Dashboard">
<span *bsTileManagerHeader="let tile = tile">{{ panel(tile).title }}</span>
<div *bsTileManagerContent="let tile = tile" class="tile-body">{{ panel(tile).body }}</div>
</bs-tile-manager>
import { Component, effect, signal } from '@angular/core';
import {
BsTileManagerComponent,
BsTileManagerContentDirective,
BsTileManagerHeaderDirective,
MintTile,
TileLayouts,
} from '@mintplayer/ng-bootstrap/tile-manager';
interface DashboardTile extends MintTile {
title: string;
body: string;
}
@Component({
selector: 'my-dashboard',
templateUrl: './my-dashboard.component.html',
imports: [BsTileManagerComponent, BsTileManagerHeaderDirective, BsTileManagerContentDirective],
})
export class MyDashboardComponent {
readonly tiles = signal<DashboardTile[]>([
{ id: 'weather', title: 'Weather', body: 'Sunny · 22 °C', defaultSize: { colSpan: 2, rowSpan: 1 } },
{ id: 'inbox', title: 'Inbox', body: '3 unread' },
]);
// One layout per column count; a commit writes the full next record.
readonly layouts = signal<TileLayouts>(JSON.parse(localStorage.getItem('dashboard') ?? '{}'));
// The slots receive the tile object passed in tiles.
panel(tile: MintTile): DashboardTile {
return tile as DashboardTile;
}
constructor() {
effect(() => localStorage.setItem('dashboard', JSON.stringify(this.layouts())));
}
}