Dashboard

Navigation

The shared sidebar/nav components from @repo/ui shown on a synthetic NavNode[] fixture. All four variants consume the same global NavNode interface from @repo/config — pick one for the whole app via the selector in the header.

Current app layout: tree
Tree
Default. Hierarchical, sections always expanded, ~240px wide.
Compact
Icon rail, ~68px wide. Tooltips on hover for full labels.
Stacked
Collapsible section blocks. Toggle headers to fold/unfold.
Overview
Building Blocks
Market Workspaces
Integrations
Admin
Top bar
Horizontal app bar. Sections as scrollable tabs with underline accent.
SubNav (in-page sub-navigation)

A composable secondary nav. Drop it inside any Next.js layout.tsx (or a page) to give a sub-tree its own tabs — without the outer shell needing to know. Supports both route-based tabs (`/places/ksfo/albums`) and search-param tabs (`?filter=picks`). Active state resolves from the URL.

Route-based tabs
Each tab links to a different page; active matches longest pathname prefix.
Search-param tabs
Each tab toggles a `?filter=` query, with counts and a disabled state.
How nav trees get populated

Every app in next-web follows the same pipeline: PageConfig map + section subset → buildNavTree(pages, sections)NavNode[]. Dynamic rows (Payload collections, CSV ingest, etc.) are merged into the same pages map server-side in each app's lib/nav-builder.ts. Below: the source shape, the result rendered, and the same pipeline with one dynamic row class appended.

Authoring shape
The universal PageConfig / SECTIONS schema apps author against.
// Author pages as PageConfig (@repo/config)
const pages: Record<string, PageConfig> = {
  "/": { title: "Explore", section: "Library", order: 0 },
  "/albums": { title: "All Albums", section: "Albums", order: 0 },
  ...
}

// Pick the sections this app cares about
const sections = { Library: SECTIONS.Library, Albums: SECTIONS.Albums }

// Build the NavNode[] every variant consumes
const navTree = buildNavTree(pages, sections)
//   → [{ id: "library", label: "Library",
//        children: [{ id: "explore", path: "/", ... }] }, ...]

// Merge dynamic rows the same way (server-side, see nav-builder.ts)
for (const album of await fetchAlbums()) {
  pages[`/albums/${album.slug}`] = {
    title: album.title, section: "Albums", order: i++
  }
}
Static pages only
The 4 PageConfig entries above, run through buildNavTree.
Static + dynamic merge
Same pages, plus 3 dynamic Places rows appended (simulating a Payload fetch).