# WidgetGrid

> A workspace of cards — figures, charts and lists side by side on a four-column grid (two on a tablet, one on a phone), each a Widget with a title and a span. With editing on, each widget shows its tools (move it earlier or later, remove it), so a person arranges their own workspace; the order and the set are the page's to keep.

<!-- id: ui-widget-grid · area: ui · stability: stable · html: https://osysharp.com/reference/ui/widget-grid/ -->

## Summary        {#summary}

A dashboard is a set of answers a person checks at a glance, and the person checking them is the best judge of which
they need. `WidgetGrid` lays `Widget`s on a four-column grid (two columns on a tablet, one on a phone); a widget takes
one column, or `WidgetSpan.Two`, `Three` or `Full`. Turn `editing` on — `Switch("Customise", value: editing)` is the
control for it — and every widget shows its tools (earlier, later, remove); the page keeps what the person chose.

```osy title="a workspace a person can trim" test app=ui-widget-grid
[Route("/")] [Render(CSR)] [AllowAnonymous]
component Overview() {
  bool editing = false;
  List<string> hidden = [];
  action Hide(string key) {
    var next = new List<string>();
    foreach (var h in hidden) { next.Add(h); }
    next.Add(key);
    hidden = next;
  }

  render {
    Stack(gap: 4, p: 6) {
      PageHead("Overview") { Switch("Customise", value: editing); }
      WidgetGrid {
        if (!hidden.Contains("visits")) {
          Widget("Visits", subtitle: "today", editing: editing, onRemove: () => Hide("visits")) { Metric("visits", "1,208"); }
        }
        if (!hidden.Contains("orders")) {
          Widget("Orders", subtitle: "today", span: WidgetSpan.Two, editing: editing, onRemove: () => Hide("orders")) { Metric("orders", "64"); }
        }
      }
    }
  }
}
```

## Signature      {#signature}

```osy syntax
WidgetGrid { Widget(…); … }
Widget(string title, string subtitle = "", WidgetSpan span = WidgetSpan.One, bool editing = false,
       Action onRemove = null, Action onEarlier = null, Action onLater = null) { <content> }
enum WidgetSpan { One, Two, Three, Full }
```

## Description    {#description}

### Where is the arrangement kept?   {#keep}

Wherever the page keeps it — a field for a session, a stored row for good. Store it on a row and the customising is a
real edit of the page's unit of work, so `SaveActions` beside the Customise button keeps or drops it like any other
change (the command centre archetype does exactly that).

### How wide is a span on a phone?   {#span}

Every span is one column below `Tablet`, two at most on a tablet, and as asked from `Laptop` up — a span never asks
for more columns than there are.

### How do I drive it in a test?   {#test}

Each widget is a region named by its title: `Assert.Visible("1,208", within: "Visits")`. While editing, its tools are
`Ui.Click("Remove Visits")`, `Ui.Click("Move Visits earlier")` and `Ui.Click("Move Visits later")`.

## See also       {#see-also}

- [Arrangements — five ways to build a whole app, and which look wears each](https://osysharp.com/reference/ui/arrangements/) — the command centre built on it
- [The chart kit — line, column, bar, scatter and candle, with no JavaScript](https://osysharp.com/reference/ui/chart-kit/) — charts that sit in a widget without a frame of their own (`chrome: ChartChrome.Bare`)
- [Save, discard, undo — three ways to show a page's unit of work](https://osysharp.com/reference/ui/saving/) — keeping a customised arrangement
