Tile manager

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.

Keyboard shortcuts
  • Tab — focus the board. Exactly one tile is in the tab order, so Tab again leaves it.
  • ←→↑↓ — focus the previous / next tile in reading order · Home / End — first / last
  • M — move mode: arrows move the tile one cell · R — resize mode: arrows grow or shrink it
  • Enter — commit · Esc — restore the layout from before the mode, or cancel a pointer gesture
Panels:
Lock:

Board width: —px Columns: —

Weather
Sunny · 22 °C · 5 km/h NW
Inbox
3 unread · 2 starred
Stats
1.2k visits · ↑12% week-over-week
Calendar
Next: Standup at 10:00
Notes
“Refactor packer · push v1 by Friday”
Activity
7 commits · 2 PRs reviewed today
Read-only.
Stored layouts
  
    
      
        {}
        
    
  
    
      
        <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())));
        
    
      
          }
        
    
      
        }