Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

WorkbenchShell

WorkbenchShell(chrome) { Outlet(retain: 8); slot explorer { … } slot status { … } } — an activity bar, an explorer, panes and a status line

The arrangement of a developer tool or an operator's console: an activity bar with one mark per section, an explorer beside it listing the current section's pages, a top bar, full-height panes for the page (pair it with Dock for right and bottom drawers) and a status line along the foot. Same AppChrome and slots as every shell.

stable1 example compiled by CIuikitshellnavigation

Summary#

WorkbenchShell splits navigation in two. The activity bar carries one mark per top-level section and is always in view; the explorer beside it lists the pages of the section you are in, with whatever the app adds under them (saved views, a queue's filters). Pressing the current section's mark — or Mod+B — folds the explorer away, because once you know where you are the width is worth more than the map. The page fills full-height panes, and a status line runs along the foot.

[Layout]
[AllowAnonymous]
component Console() {
  action Search() { }
  render {
    WorkbenchShell(new AppChrome {
      Product = "Beacon",
      Tagline = "Operations",
      Home = "/",
      Nav = [
        new NavItem { Label = "Queues", Icon = Icons.Inbox, Children = [
          new NavItem { Label = "Open", To = "/", Icon = Icons.CircleDot, Exact = true },
          new NavItem { Label = "Waiting", To = "/waiting", Icon = Icons.Clock },
        ] },
        new NavItem { Label = "Deploys", To = "/deploys", Icon = Icons.Upload },
      ],
    }) {
      Outlet(retain: 8);
      slot search { ShellSearch("Search", onPress: Search); }
      slot explorer { SectionLabel("Saved views"); }
      slot status { Lamp(lit: true, live: true, tone: Tone.Success); Text("Live"); }
    }
  }
}

[Route("/")] [Layout(Console)] [Title("Open")] [Render(CSR)] [AllowAnonymous]
component OpenQueue() { render { Stack(p: 4) { Text("Nothing open."); } } }

[Route("/waiting")] [Layout(Console)] [Title("Waiting")] [Render(CSR)] [AllowAnonymous]
component WaitingQueue() { render { Stack(p: 4) { Text("Nothing waiting."); } } }

[Route("/deploys")] [Layout(Console)] [Title("Deploys")] [Render(CSR)] [AllowAnonymous]
component Deploys() { render { Stack(p: 4) { Text("No deploys today."); } } }

Signature#

WorkbenchShell(AppChrome chrome) {
  Outlet(retain: 8);
  slot explorer { … }   // under the section's pages
  slot search   { … }   // the top bar
  slot actions  { … }   // the top bar, right of search
  slot status   { … }   // the status line along the foot
}

Description#

Which items become marks, and which pages does the explorer list?#

Every top-level NavItem is a mark; a NavKind.Section's children are lifted to the top level (a section heading is a word, and an activity bar has no room for words). A mark with Children is a SECTION: its children are what the explorer lists while you are in it. A mark with no children is a page on its own, and the explorer lists just it. A mark's name shows beside it on hover and keyboard focus, with its Badge. A mark with a Badge carries it as a dot in the badge's tone, and the badge's words are in the mark's accessible name — "Inbox — 3" — so a screen reader and a test (Ui.Click("Inbox — 3", within: "Main navigation")) read the count without pointing at it.

What happens when a row of the explorer opens a page outside the nav?#

The explorer stays. A tree in the explorer slot — records, files, a backlog — opens pages that are in no section of the nav (/orders/42), and the explorer is not taken away the moment one of its rows is opened: it stays on the section of the latest open route that is in one, and that section's mark stays lit, as an editor's side bar stays on the view a file was opened from. This needs the outlet to keep routes open (Outlet(retain: 8)); with a single open route, a page outside every section shows no explorer.

Where do the drawers come from?#

From the page, through Dock — the inspector a row opens on the right, a log along the bottom. They belong to the page because they open over what the PAGE has selected; the shell gives them a frame that is full height with nothing scrolling but the panes.

Where does ShowVersion show the versions?#

At the far end of the status line. The workbench's brand is a bare mark with no tagline under it, so where every other shell puts the app's version under the product's name, this one puts the same two lines in its foot, after whatever the status slot holds: "Tagline · App.Version" (just the version when there is no tagline) and "platform " + App.PlatformVersion. A test reads them there: Assert.Visible("platform " + App.PlatformVersion, within: "Status"). See App.Version — which version of your app is running, on which platform release.

What happens on a phone?#

Below Tablet the activity bar and the explorer become one drawer behind the menu button — every section with its pages under it, then the explorer slot — and the status line stays at the foot.

How do I drive it in a test?#

The marks are links in the region "Main navigation"; the explorer is a region named by the section's label; the status line is "Status". Ui.Click("Hide the explorer") folds the explorer, and Assert.Visible("Live", within: "Status") reads the foot.

See also#

Related

Dock

The page's drawers: the page in the middle, a drawer docked on the right (the record a row opened, an inspector) and…

App shells

An app shell is the frame around every page: a brand, a navigation tree, the signed-in person and a menu behind them, a…

RailShell

An app shell built around a permanently narrow icon rail, in the shape Slack, Linear and Discord converge on. Every row…

TabbedShell

An app shell whose primary navigation is a horizontal strip of tabs under the brand row, and a bottom tab bar within…

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…

Navigation

The routes the user has open, which of them hold UNSAVED edits, and the verbs that move between them or close one…