Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

Dock

Dock(right: open != null, rightTitle: "Details", onCloseRight: Close, bottom: logOpen, bottomTitle: "Log") { page; slot right { … } slot bottom { … } }

The page's drawers: the page in the middle, a drawer docked on the right (the record a row opened, an inspector) and one along the bottom (a log, a console, a timeline), each with a title and a close, each resizable by its edge and remembered. The page decides when each is open; on a phone the right drawer becomes a sheet and the bottom one follows the page.

stable1 example compiled by CIuikitlayoutpanes

Summary#

Dock arranges a page around what it has SELECTED: the list or table stays in the middle, the thing a person opened docks on the right without covering it, and an ambient stream — a log, a console, a timeline — runs along the bottom. Both drawers are opened by the page (it is what knows what is selected), closed by their own ×, and resized by dragging their edge or with the arrow keys; the size is remembered.

class Job { public string Ref; public string Title; public string State; }

List<Job> Jobs() {
  return [
    new Job { Ref = "J-104", Title = "Rebuild the search index", State = "Running" },
    new Job { Ref = "J-103", Title = "Rotate the signing keys", State = "Queued" },
  ];
}

[Route("/jobs")] [Render(CSR)] [AllowAnonymous]
component JobsPage() {
  string open = "";
  bool log = true;
  action Open(Job j) { open = j.Ref; }
  action Close() { open = ""; }
  action CloseLog() { log = false; }

  render {
    Box(h: Length.Screen) {
      Dock(right: open != "", rightTitle: "Job " + open, onCloseRight: Close,
           bottom: log, bottomTitle: "Log", onCloseBottom: CloseLog) {
        DataGrid(label: "Jobs", rows: Jobs(), rowSelected: Open, columns: [
          new GridColumn<Job> { Name = "Ref", Label = "Job", Value = j => j.Ref, Width = 90 },
          new GridColumn<Job> { Name = "Title", Label = "Title", Value = j => j.Title, Fill = true },
          new GridColumn<Job> { Name = "State", Label = "State", Value = j => j.State, Width = 110 },
        ]);
        slot right { Stack(gap: 2, p: 4) { Text(Jobs().First(j => j.Ref == open).Title, fontWeight: FontWeight.Semibold); } }
        slot bottom { Text("10:24:07  index shard 3 of 8 rebuilt", fontFamily: Font.Mono, fontSize: FontSize.Code); }
      }
    }
  }
}

Signature#

Dock(bool right = false, bool bottom = false, string rightTitle = "Details", string bottomTitle = "Panel",
     Action onCloseRight = null, Action onCloseBottom = null) { <the page>; slot right { … } slot bottom { … } }

Description#

Who opens a drawer?#

The page. right: and bottom: are plain booleans the page computes — open != null, a toggle in its toolbar — and onCloseRight / onCloseBottom are what the drawers' × buttons run. That keeps the decision where the knowledge is: the page knows which row is selected; the dock only knows how to show it.

How big is it, and does it stay that size?#

A right drawer starts 380px wide and a bottom one 240px tall. Each edge is a handle: drag it, or focus it and use the arrow keys (Escape puts it back where the drag started). The size is [Remembered], so it survives a reload and the next visit, per browser.

What happens on a phone?#

Below Laptop the two drawers part ways. The right one is what the person OPENED, so it takes the screen as a sheet (its own close, and Escape) and closes back to the list. The bottom one is ambient, so it follows the page in the flow under its title instead of burying the page the moment it opens.

Where does the page scroll?#

Give Dock the page's height — WorkbenchShell does, as does any shell with Panes = true, or a Box(h: Length.Screen) — and the middle scrolls on its own while the drawers stay put. Each drawer scrolls inside itself.

How do I drive it in a test?#

The drawers are regions named by their titles: Assert.Visible("Rebuild the search index", within: "Job J-104"), and their close is Ui.Click("Close Job J-104"). Give each drawer a title that differs from anything in the table — "Job J-104" rather than "J-104" — or a scope naming it matches the row too.

See also#

Related

WorkbenchShell

The arrangement of a developer tool or an operator's console: an activity bar with one mark per section, an explorer…

Arrangements — five ways to build a whole app, and which look wears each

A look decides how an app's pixels read; an ARRANGEMENT decides how the app is built — where navigation lives, how a…

Save, discard, undo — three ways to show a page's unit of work

Every edit on a page joins the route's unit of work; how the page OFFERS to keep it is a choice of presentation…

drag

`drag:` binds a number to a drag gesture: grabbing the element moves the value, arrow keys move the same value, and…

SubNav

Navigation inside a tab: a list down the side of a tab's body that moves between that tab's own places — a record's…