Workspace switcher menu
Popover anchored to the rail's WorkspaceBadge. Pure composition of
UI-03 menu primitives + UI-01 surface tokens — no bespoke CSS.
States covered
| State | Story |
|---|---|
| Default — one selected workspace | workspace-menu-default |
| Many workspaces | workspace-menu-many |
| Long names truncate | workspace-menu-long-names |
| No selection | workspace-menu-no-selection |
selected_id is a prop. The menu doesn't know which workspace is
"current" — app-ui passes the active workspace id down and the
menu compares against each row's id to render the ✓.
Composition
flowchart TD
Menu[WorkspaceSwitcherMenu] --> Popover[PopoverSurface]
Popover --> ListBody[MenuList]
ListBody --> Group[MenuSection — Your workspaces]
Group --> Row[MenuRow × N]
Row --> Tile[span.icon-tile-workspace]
Row --> Title[name]
Row --> Sub[member count]
Row --> Plan[Badge — Plan]
ListBody --> Actions[MenuSection — Actions]
Actions --> New[MenuRow — New workspace]
Actions --> Settings[MenuRow — Workspace settings]
Popover --> Footer[MenuFooter]
API
#![allow(unused)] fn main() { use ui_storybook::components::{WorkspaceSwitcherMenu, WorkspaceView}; view! { <WorkspaceSwitcherMenu workspaces=fixtures::workspaces::sample_workspace_views() selected_id="ws-northwind" /> } }
Member count formatting
format_member_count(u32) -> String renders 1 member (singular)
vs N members (plural). Unit-tested for both branches + zero.