import React from "react";
import { DocsLayout } from "fumadocs-ui/layouts/docs";
import type * as PageTree from "fumadocs-core/page-tree";
import { MobileTopNav } from "./mobile-top-nav";
import { SidebarScrollPreserver } from "./sidebar-scroll-preserver";
import { SidebarFolderStatePreserver } from "./sidebar-folder-state-preserver";
import GithubIcon from "./icons/github";
import DiscordIcon from "./icons/discord";
import { MobileSidebarFooterTalk } from "./mobile-sidebar-footer-talk";
import { PrimaryDocsTabs } from "./primary-docs-tabs";
// Shared Fumadocs `DocsLayout` chrome used by every shell-docs route.
// All five callers (home overview, framework root, framework-scoped MDX
// via DocsPageView, /reference, /ag-ui) pass the same nav/search/sidebar
// config — only `tree` and `sidebar.banner` vary. Centralizing keeps
// sidebar behavior, mobile nav slot, and container className from drifting
// across routes when one is tweaked.
export function ShellDocsLayout({
tree,
banner,
children,
}: {
tree: PageTree.Root;
banner?: React.ReactNode;
children: React.ReactNode;
}) {
return (
}}
searchToggle={{ enabled: false }}
// Suppress fumadocs's auto-injected ThemeSwitch — `slots.themeSwitch`
// is populated by default even when `themeSwitch` isn't passed, so
// we have to pass `enabled: false` explicitly to keep the
// `iconLinks.length > 0 || slots.themeSwitch` branch in
// `sidebar.js` from rendering the default rounded pill. Our own
// single-toggle `` is mounted in BrandNav instead.
themeSwitch={{ enabled: false }}
// We intentionally do NOT pass `links` here either. Fumadocs would
// funnel `type: "icon"` entries into the same auto-injected pill
// we just disabled — but the icons need to live in our custom
// footer row anyway. Rendering them inline keeps a single source
// of truth (the JSX below) and avoids the auto layout fighting
// our custom one.
sidebar={{
banner: (
),
// Hide Fumadocs's collapse toggle — shell-docs has its own chrome.
collapsible: false,
className: "shell-docs-sidebar",
// Note: `key` is required here because fumadocs's Sidebar
// passes the `footer` ReactNode into a `jsxs(children: [a, b,
// footer])` array, and React's dev-mode warning insists every
// top-level child of an array carry a stable key. We don't
// control fumadocs's render, so we hand it a keyed element
// directly. The key is a literal string — there's only ever
// one footer per sidebar.
footer: (
),
}}
containerProps={{
// The outer .docs-content-wrapper gradient + scroll behavior is
// applied to DocsLayout's container so the scroll context, border,
// and gradient match what shell-docs has always rendered.
className: "docs-content-wrapper",
}}
>
{/* Preserve sidebar scroll across navigations — the sidebar is
* rendered per-page (this layout sits inside each page component
* rather than in a Next.js layout file), so without explicit
* restoration the Radix ScrollAreaViewport resets to 0 every
* time the user clicks a link further down the list. */}
{/* Persist sidebar folder open/closed state across navigations —
* without this, Fumadocs resets each Radix Collapsible to its
* default state on every page mount, undoing the user's
* "I want this section hidden" choice. */}
{children}
);
}