{
  "name": "AppShell",
  "package": "@magicblocksai/ui",
  "file": "packages/ui/src/components/AppShell.tsx",
  "chapterTag": "10 Page Templates",
  "chapter": "11-patterns-and-specials.html",
  "sectionId": null,
  "elName": null,
  "demoUrl": null,
  "hasLiveDemo": false,
  "description": "Full-viewport app chrome — sidebar + main column with an optional sticky topbar.\nSibling to `<DashboardShell>`, but built for whole-app surfaces (`100vh`,\nno border, no border-radius) rather than embedded marketing demos.",
  "useClient": true,
  "interactivity": "interactive",
  "namedExports": [
    {
      "name": "AppShell",
      "isPrincipal": true,
      "isType": false
    },
    {
      "name": "AppShellProps",
      "isPrincipal": false,
      "isType": true
    }
  ],
  "importStatement": "import { AppShell } from \"@magicblocksai/ui\";",
  "props": [
    {
      "name": "sidebar",
      "optional": true,
      "type": "ReactNode",
      "doc": "Sidebar / left-rail content (typically nav). Renders inside `.app-shell-side`."
    },
    {
      "name": "topbar",
      "optional": true,
      "type": "ReactNode",
      "doc": "Optional sticky top bar in the main column (search, quick-add, profile, notifications)."
    },
    {
      "name": "children",
      "optional": false,
      "type": "ReactNode",
      "doc": "Main column content."
    },
    {
      "name": "sideWidth",
      "optional": true,
      "type": "string",
      "doc": "Override the sidebar width. Default `var(--app-shell-side, 248px)`. Also exposed as the CSS variable `--app-shell-side-w` on the root for consumer fit-to-viewport calcs."
    },
    {
      "name": "sidebarMode",
      "optional": true,
      "type": "SidebarMode",
      "doc": "Sidebar mode — controlled. `\"expanded\"` (default behaviour) or `\"collapsed\"` (icon-only rail). When set, the kit propagates the value via React context so `<DashboardNavGroup>` and other nav primitives can switch their own render. The toggle button is consumer-owned. v1.61.0 (app-team R2)."
    },
    {
      "name": "sidebarWidthExpanded",
      "optional": true,
      "type": "string",
      "doc": "Sidebar width when `sidebarMode === \"expanded\"`. Default `\"200px\"`. Pass any CSS length. v1.61.0."
    },
    {
      "name": "sidebarWidthCollapsed",
      "optional": true,
      "type": "string",
      "doc": "Sidebar width when `sidebarMode === \"collapsed\"`. Default `\"64px\"`. Pass any CSS length. v1.61.0."
    },
    {
      "name": "topbarHeight",
      "optional": true,
      "type": "string",
      "doc": "Override the topbar height (used internally by `--app-topbar-h`). Default `56px` — matches the kit's own topbar slot at default padding. Override if your topbar renders taller, so consumer code can use `calc(100vh - var(--app-topbar-h))` without magic numbers. v1.8.0 — Spark Round 5 R5-4."
    },
    {
      "name": "scrollMode",
      "optional": true,
      "type": "\"columns\" | \"page\"",
      "doc": "How scrolling is bounded. - `\"columns\"` (default) — sidebar + main each have their own `overflow-y: auto`; the root is `100vh; overflow: hidden`. Three independent scroll regions (sidebar, topbar-pinned-to-main, main). Best for dashboards with always-visible chrome. - `\"page\"` — the whole page scrolls (`<html>`/`<body>` own the scrollbar). Sidebar becomes `position: sticky; top: 0; align-self: start; max-height: 100vh; overflow-y: auto`. Topbar stays sticky in the main column. Best for long content where browser scroll-restoration, \"scroll-to-top,\" and mobile scroll-into-view should work the way users expect from a webpage. v1.7.0 — Spark Round 3 R3-4."
    },
    {
      "name": "pageWash",
      "optional": true,
      "type": "boolean",
      "doc": "Use a recessed page wash so cards visually lift off the background. Defaults to `false` — page bg matches card bg (`--bg-paper`), which is the back-compat behaviour. When `true`, page bg flips to `--warm-1` (light) / `--ink` (dark) — the standard \"cards pop off a recessed page\" pattern every CRM-style consumer reaches for. Pairs naturally with the v1.9.0 `<Card>` shadow default — cards already feel like cards, the page wash gives them a surface to float over. v1.9.0 — Spark Round 6 R6-1."
    },
    {
      "name": "mobileNavOpen",
      "optional": true,
      "type": "boolean",
      "doc": "Mobile drawer state — when `true`, the sidebar slides in from the left as a fixed drawer with a scrim overlay. Default `false`. The drawer + scrim CSS only activates at the mobile breakpoint (≤960px); on desktop the prop has no effect (sidebar stays in-grid). Consumer-controlled — pair with `useState` and your hamburger trigger: ```tsx const [navOpen, setNavOpen] = useState(false); <AppShell sidebar={<MyNav />} topbar={<button onClick={() => setNavOpen(true)}>≡</button>} mobileNavOpen={navOpen} onMobileNavOpenChange={setNavOpen} > … </AppShell> ``` v1.12.0 — Spark Round 13 R12-2."
    },
    {
      "name": "onMobileNavOpenChange",
      "optional": true,
      "type": "(open: boolean) => void",
      "doc": "Fires when the user clicks the scrim or otherwise closes the drawer. Pair with `mobileNavOpen` for controlled state."
    },
    {
      "name": "drawerWidth",
      "optional": true,
      "type": "string",
      "doc": "Drawer width override. Default `min(280px, 84vw)`. Exposed as `--app-shell-drawer-w` on the root."
    },
    {
      "name": "sidebarTone",
      "optional": true,
      "type": "\"paper\" | \"warm\"",
      "doc": "Sidebar background tone. - `\"paper\"` (**default** since v2.0.0) — the paper/white surface; a crisp white rail against the warm main column. - `\"warm\"` — opt back into the warm-cream pin (`--warm-3` in light mode, matches `.dash-side`). This was the default in v1.x. Also settable per-instance via the `--app-shell-side-bg` CSS variable (any value there wins over the preset). v1.72.0 (prop) · v2.0.0 (default flipped warm → paper — app-team request 0)."
    },
    {
      "name": "className",
      "optional": true,
      "type": "string",
      "doc": "Allow consumers to drop their own className on the root."
    }
  ],
  "classesUsed": [
    "app-shell",
    "app-shell-main",
    "app-shell-side",
    "app-shell-topbar",
    "page",
    "title"
  ],
  "examples": {
    "react": "<AppShell\n    sidebar={<MyNav />}\n    topbar={<MyTopbar />}\n  >\n    <div style={{ padding: \"var(--s-6)\" }}>…</div>\n  </AppShell>",
    "html": null,
    "css": ".app-shell {\n  display: grid;\n  grid-template-columns: var(--app-shell-side, 248px) 1fr;\n  height: 100vh;\n  width: 100%;\n  background: var(--bg-paper);\n  color: var(--fg);\n  overflow: hidden;\n  /* v1.8.0 (R5-4): expose layout dimensions as CSS custom properties so\n     consumers can write fit-to-viewport calcs without magic numbers.\n     `--app-topbar-h` defaults to 56px (matches the ~56px the kit's own\n     topbar slot renders at with default padding). Override on .app-shell\n     when your topbar is a different height — `<AppShell style={{ '--app-topbar-h': '64px' }\n\n.app-shell-main {\n  display: flex;\n  flex-direction: column;\n  position: relative; /* v3.2.0 — positioning context for a rail-only RouteProgress sliver */\n  overflow-y: auto;\n  overflow-x: hidden;\n  /* v1.9.0 (R6-1): page bg is tokenised so consumers can opt into a\n     recessed page-wash that lifts cards visually.\n     v3.0.0 (warm ladder): the LIGHT default is now `--bg-canvas` (--warm-1)\n     — the recessed warm canvas that cards float on is the standard, not an\n     opt-in. `pageWash` is kept for API stability but resolves to the same\n     `--bg-canvas`, so it's a no-op in light. DARK mode is preserved: the\n     dark default stays `--bg-paper` (#2A3050, flat-on-flat) via the dark\n     override below, and `pageWash` still flips it to `--ink` in dark. */\n  background: var(--app-page-bg, var(--bg-canvas));\n}\n\n.app-shell-side {\n  /* Structural — applies in every theme. */\n  border-right: 1px solid var(--hair);\n  padding: var(--s-4);\n  overflow-y: auto;\n  overflow-x: hidden;\n  /* v1.8.0 (R5-2): always paint a paper background so a repositioned\n     sidebar (slide-in panel, drawer, overlay) doesn't render transparent\n     in dark mode. The light-mode warm-cream override below takes\n     precedence in light theme. Without this, dark-mode sidebars only\n     painted via the surrounding .app-shell's bg — fine in-grid (paper\n     on paper) but invisible when the consumer reparents the sidebar to\n     `position: fixed` for mobile. */\n  background: var(--app-shell-side-bg, var(--bg-paper));\n}\n\n.app-shell-topbar {\n  position: sticky;\n  top: 0;\n  z-index: var(--z-topbar, 30);\n  background: var(--bg-paper);\n  border-bottom: 1px solid var(--hair);\n}\n\n.page {\n  max-width: 1160px;\n  margin: 0 auto;\n  padding: 0 var(--s-7);\n}\n\n.title { font: 700 32px/1.15 var(--f-display); letter-spacing: -0.015em; margin: 0; }"
  }
}
