mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
docs: apply sidebar review feedback
This commit is contained in:
@@ -81,23 +81,6 @@ describe("globals.css cookbook sidebar", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("globals.css sidebar Intelligence pin", () => {
|
||||
it("stays muted until hover, then fills like the header Intelligence control", () => {
|
||||
const idleBlock = globalsCss.match(
|
||||
/\.shell-docs-intelligence-entry \{[\s\S]*?\n\}/,
|
||||
)?.[0];
|
||||
expect(idleBlock).toContain("background: transparent;");
|
||||
expect(idleBlock).toContain("color: var(--text-muted);");
|
||||
expect(idleBlock).not.toContain("background: var(--accent);");
|
||||
|
||||
const hoverBlock = globalsCss.match(
|
||||
/\.shell-docs-intelligence-entry:hover,\s*\.shell-docs-intelligence-entry:focus-visible \{[\s\S]*?\n\}/,
|
||||
)?.[0];
|
||||
expect(hoverBlock).toContain("background: var(--accent);");
|
||||
expect(hoverBlock).toContain("color: #fff;");
|
||||
});
|
||||
});
|
||||
|
||||
describe("globals.css sidebar section folders", () => {
|
||||
it("styles collapsible section triggers separately from page links", () => {
|
||||
expect(globalsCss).toContain(".shell-docs-sidebar-section-label");
|
||||
|
||||
@@ -314,63 +314,6 @@
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.shell-docs-intelligence-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
min-height: 2.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: var(--shell-docs-radius-control);
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
background-color 150ms ease-out,
|
||||
color 150ms ease-out,
|
||||
transform 100ms ease-out;
|
||||
}
|
||||
|
||||
.shell-docs-intelligence-entry svg {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.shell-docs-intelligence-entry:hover,
|
||||
.shell-docs-intelligence-entry:focus-visible {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.shell-docs-intelligence-entry:hover svg,
|
||||
.shell-docs-intelligence-entry:focus-visible svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.shell-docs-intelligence-entry:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.shell-docs-intelligence-entry:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
.shell-docs-intelligence-entry-active {
|
||||
background: color-mix(in oklch, var(--accent) 13%, transparent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.shell-docs-intelligence-entry-active:hover,
|
||||
.shell-docs-intelligence-entry-active:focus-visible {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dark .shell-docs-intelligence-entry-active {
|
||||
background: color-mix(in oklch, var(--accent) 20%, transparent);
|
||||
}
|
||||
|
||||
.shell-docs-mega-menu {
|
||||
padding: 1.25rem 1.5rem;
|
||||
}
|
||||
@@ -455,14 +398,9 @@
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.shell-docs-intelligence-entry,
|
||||
.shell-docs-mega-menu-link {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.shell-docs-intelligence-entry:active {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.shell-docs-warning-surface {
|
||||
@@ -583,20 +521,6 @@
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.shell-docs-nav-link-idle.shell-docs-nav-link-intelligence:hover,
|
||||
.shell-docs-nav-link-active.shell-docs-nav-link-intelligence:hover,
|
||||
.shell-docs-nav-link-intelligence:focus-visible {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.dark .shell-docs-nav-link-idle.shell-docs-nav-link-intelligence:hover,
|
||||
.dark .shell-docs-nav-link-active.shell-docs-nav-link-intelligence:hover {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* shell-docs sidebar overrides.
|
||||
*
|
||||
* Fumadocs's DocsLayout sidebar reserves a header row with the home
|
||||
|
||||
@@ -48,6 +48,7 @@ vi.mock("@clerk/nextjs", () => {
|
||||
});
|
||||
|
||||
import { BrandNav, buildDocsAuthEntryHref } from "../brand-nav";
|
||||
import { PrimaryDocsTabs } from "../primary-docs-tabs";
|
||||
import {
|
||||
buildDocsUserMenuHref,
|
||||
DocsAuthFallbackBoundary,
|
||||
@@ -70,28 +71,31 @@ const globalsCss = readFileSync(
|
||||
"utf8",
|
||||
);
|
||||
|
||||
test("BrandNav opens docs from an Explore docs mega menu", () => {
|
||||
test("BrandNav opens Docs from a mega menu", () => {
|
||||
expect(brandNavSource).toContain("DocsMegaMenu");
|
||||
expect(brandNavSource).not.toContain('label: "Docs"');
|
||||
expect(brandNavSource).not.toContain('href: "/"');
|
||||
});
|
||||
|
||||
test("BrandNav puts Intelligence next to Cookbook", () => {
|
||||
test("BrandNav keeps Intelligence out of the primary header links", () => {
|
||||
expect(brandNavSource).toContain('label: "Cookbook"');
|
||||
expect(brandNavSource).toContain('label: "Intelligence"');
|
||||
expect(brandNavSource).toContain("INTELLIGENCE_DOCS_HREF");
|
||||
expect(brandNavSource.indexOf('label: "Cookbook"')).toBeLessThan(
|
||||
brandNavSource.indexOf('label: "Intelligence"'),
|
||||
);
|
||||
expect(brandNavSource).not.toContain('label: "Intelligence"');
|
||||
expect(brandNavSource).not.toContain("INTELLIGENCE_DOCS_HREF");
|
||||
});
|
||||
|
||||
test("PrimaryDocsTabs keeps Intelligence out of the mobile links", () => {
|
||||
const markup = renderToStaticMarkup(<PrimaryDocsTabs />);
|
||||
|
||||
expect(markup).toContain(">Docs<");
|
||||
expect(markup).toContain(">Reference<");
|
||||
expect(markup).toContain(">Cookbook<");
|
||||
expect(markup).not.toContain(">Intelligence<");
|
||||
});
|
||||
|
||||
test("BrandNav keeps space between the center rail and search", () => {
|
||||
expect(brandNavSource).toContain("grid-cols-[auto_minmax(0,1fr)_auto]");
|
||||
expect(brandNavSource).toContain("gap-x-8");
|
||||
expect(brandNavSource).toContain("pl-4");
|
||||
expect(globalsCss).toContain(
|
||||
".shell-docs-nav-link-idle.shell-docs-nav-link-intelligence:hover",
|
||||
);
|
||||
});
|
||||
|
||||
test("BrandNav uses the docs grid desktop layout cap", () => {
|
||||
|
||||
@@ -23,9 +23,9 @@ afterEach(cleanup);
|
||||
test("opens a five-column docs map with Intelligence featured", () => {
|
||||
render(<DocsMegaMenu triggerClassName="shell-docs-nav-link-active" />);
|
||||
|
||||
fireEvent.pointerEnter(screen.getByRole("button", { name: "Explore docs" }));
|
||||
fireEvent.pointerEnter(screen.getByRole("button", { name: "Docs" }));
|
||||
|
||||
expect(screen.getByRole("navigation", { name: "Explore docs" })).toBeTruthy();
|
||||
expect(screen.getByRole("navigation", { name: "Docs" })).toBeTruthy();
|
||||
expect(screen.getByRole("heading", { name: "Start" })).toBeTruthy();
|
||||
expect(screen.getByRole("heading", { name: "Build" })).toBeTruthy();
|
||||
expect(screen.getByRole("heading", { name: "Connect" })).toBeTruthy();
|
||||
@@ -37,6 +37,12 @@ test("opens a five-column docs map with Intelligence featured", () => {
|
||||
expect(intelligence.className).toContain(
|
||||
"shell-docs-mega-menu-link-featured",
|
||||
);
|
||||
expect(intelligence.querySelector("svg")?.getAttribute("viewBox")).toBe(
|
||||
"0 0 24 24",
|
||||
);
|
||||
expect(intelligence.querySelector("svg")?.getAttribute("fill")).toBe(
|
||||
"currentColor",
|
||||
);
|
||||
expect(
|
||||
screen.getByRole("link", { name: "Threads" }).getAttribute("href"),
|
||||
).toBe("/threads");
|
||||
|
||||
@@ -6,16 +6,13 @@ const layoutSource = readFileSync(
|
||||
"utf8",
|
||||
);
|
||||
|
||||
test("sidebar banner puts pickers above the Intelligence pin", () => {
|
||||
test("sidebar banner keeps the pickers above the mobile docs tabs", () => {
|
||||
const bannerIndex = layoutSource.lastIndexOf("{banner}");
|
||||
const intelligenceIndex = layoutSource.lastIndexOf(
|
||||
"<SidebarIntelligenceEntry",
|
||||
);
|
||||
const tabsIndex = layoutSource.indexOf(
|
||||
'<PrimaryDocsTabs className="shell-docs-mobile-sidebar-tabs" />',
|
||||
);
|
||||
|
||||
expect(bannerIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(intelligenceIndex).toBeGreaterThan(bannerIndex);
|
||||
expect(tabsIndex).toBeGreaterThan(intelligenceIndex);
|
||||
expect(tabsIndex).toBeGreaterThan(bannerIndex);
|
||||
expect(layoutSource).not.toContain("SidebarIntelligenceEntry");
|
||||
});
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import React from "react";
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import { afterEach, expect, test, vi } from "vitest";
|
||||
|
||||
const navigation = vi.hoisted(() => ({
|
||||
pathname: "/",
|
||||
}));
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
usePathname: () => navigation.pathname,
|
||||
}));
|
||||
|
||||
vi.mock("next/link", () => ({
|
||||
default: ({ children, href, ...props }: React.ComponentProps<"a">) => (
|
||||
<a href={href} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
}));
|
||||
|
||||
import { SidebarIntelligenceEntry } from "../sidebar-intelligence-entry";
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
test("pins Intelligence at the top of the sidebar as a docs link", () => {
|
||||
navigation.pathname = "/quickstart";
|
||||
render(<SidebarIntelligenceEntry />);
|
||||
|
||||
const link = screen.getByRole("link", { name: "Intelligence" });
|
||||
expect(link.getAttribute("href")).toBe("/intelligence/overview");
|
||||
expect(link.getAttribute("aria-current")).toBeNull();
|
||||
expect(link.className).toContain("shell-docs-intelligence-entry");
|
||||
expect(link.className).not.toContain("shell-docs-intelligence-entry-active");
|
||||
expect(link.querySelector("svg")?.getAttribute("class")).not.toContain(
|
||||
"fill-current",
|
||||
);
|
||||
});
|
||||
|
||||
test("marks the Intelligence pin current on Intelligence docs", () => {
|
||||
navigation.pathname = "/intelligence/overview";
|
||||
render(<SidebarIntelligenceEntry />);
|
||||
|
||||
const link = screen.getByRole("link", { name: "Intelligence" });
|
||||
expect(link.getAttribute("aria-current")).toBe("page");
|
||||
expect(link.className).toContain("shell-docs-intelligence-entry-active");
|
||||
expect(link.querySelector("svg")?.getAttribute("class")).not.toContain(
|
||||
"fill-current",
|
||||
);
|
||||
});
|
||||
@@ -3,18 +3,14 @@
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { usePostHog } from "posthog-js/react";
|
||||
import { CalendarDays, ChefHat, Star } from "lucide-react";
|
||||
import { CalendarDays, ChefHat } from "lucide-react";
|
||||
import { SearchTrigger } from "./search-trigger";
|
||||
import { CopilotKitMark } from "./copilotkit-mark";
|
||||
import { ThemeSwitch } from "./theme-switch";
|
||||
import ConsoleIcon from "./icons/console";
|
||||
import ExternalLinkIcon from "./icons/external-link";
|
||||
import { DocsMegaMenu } from "./docs-mega-menu";
|
||||
import {
|
||||
INTELLIGENCE_DOCS_HREF,
|
||||
isDocsExplorePath,
|
||||
isIntelligenceDocsPath,
|
||||
} from "@/lib/docs-mega-menu";
|
||||
import { isDocsExplorePath } from "@/lib/docs-mega-menu";
|
||||
import {
|
||||
DocsPublicAuthControl,
|
||||
buildDocsAuthEntryHref,
|
||||
@@ -50,11 +46,6 @@ const LEFT_LINKS: LeftLink[] = [
|
||||
label: "Cookbook",
|
||||
href: "/cookbook",
|
||||
},
|
||||
{
|
||||
icon: <Star className="w-4 h-4 text-current" />,
|
||||
label: "Intelligence",
|
||||
href: INTELLIGENCE_DOCS_HREF,
|
||||
},
|
||||
];
|
||||
|
||||
export interface BrandNavProps {
|
||||
@@ -69,18 +60,15 @@ export function BrandNav(_props: BrandNavProps = {}) {
|
||||
const posthog = usePostHog();
|
||||
const authEntryHref = useDocsAuthEntryHref();
|
||||
|
||||
// Active-route detection: Reference, Cookbook, and Intelligence each own
|
||||
// their prefix. Everything else (root and framework-scoped pages)
|
||||
// highlights Explore docs.
|
||||
// Active-route detection: Reference and Cookbook each own their prefix.
|
||||
// Everything else highlights Docs.
|
||||
const firstSegment = pathname === "/" ? "/" : `/${pathname.split("/")[1]}`;
|
||||
const activeRoute =
|
||||
firstSegment === "/reference"
|
||||
? "/reference"
|
||||
: firstSegment === "/cookbook"
|
||||
? "/cookbook"
|
||||
: isIntelligenceDocsPath(pathname)
|
||||
? INTELLIGENCE_DOCS_HREF
|
||||
: "/";
|
||||
: "/";
|
||||
|
||||
const handleTalkToEngineersClick = () => {
|
||||
posthog?.capture("talk_to_us_clicked", { location: "docs_nav" });
|
||||
@@ -122,7 +110,6 @@ export function BrandNav(_props: BrandNavProps = {}) {
|
||||
</li>
|
||||
{LEFT_LINKS.map((link) => {
|
||||
const isActive = activeRoute === link.href;
|
||||
const isIntelligence = link.href === INTELLIGENCE_DOCS_HREF;
|
||||
return (
|
||||
<li key={link.href} className="relative h-full group">
|
||||
<Link
|
||||
@@ -131,7 +118,7 @@ export function BrandNav(_props: BrandNavProps = {}) {
|
||||
isActive
|
||||
? "shell-docs-nav-link-active"
|
||||
: "shell-docs-nav-link-idle"
|
||||
}${isIntelligence ? " shell-docs-nav-link-intelligence" : ""}`}
|
||||
}`}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<span className="[@media(width<808px)]:hidden">
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
export function CopilotKitIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
height="1em"
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M11.7 9.938l-.476 1.59h2.622c.07.054.139.107.207.162l.193.158h-3.162L.679 24.1l-.05.04a.16.16 0 01-.194-.246l10.43-12.285.56-1.862.276.19z" />
|
||||
<path d="M20.654 19.34c.007-.068.097-.09.134-.032l.566.868.009.015c.36.918.15 3.05-1.966 3.05-3.467 0-2.813-3.513-4.423-3.513-2.221 0-1.052 4.333-4.883 4.333-1.565 0-2.875-.738-3.514-2.427l1.231-.173c.138 1.004 1.125 2.463 2.418 2.464 1.962 0 1.552-4.29 4.748-4.29 2.723.001 3.053 3.468 4.423 3.468 1.298 0 1.243-1.763 1.155-2.49a1.021 1.021 0 01-.003-.22l.105-1.053z" />
|
||||
<path d="M20.19 18.118l.001.003h-.002c-.046-.083-.097-.165-.148-.248a12.717 12.717 0 00-.846-1.205 19.993 19.993 0 00-.845-.998 26.91 26.91 0 00-.865-.92 39.347 39.347 0 00-3.24-2.902h3.315l2.63 6.27z" />
|
||||
<path d="M23.186 2.776c-1.244 3.158-2.765 8.818-2.823 15.28 0 .004-.002.008-.003.011 0-.006 0-.013-.003-.02l-2.62-6.25a.158.158 0 00-.052-.257l-.061-.013-2.26-5.39c.322-.103.646-.208.967-.319a47.04 47.04 0 006.302-2.72c.166-.087.296-.158.383-.205l.1-.055.025-.015.008-.004a.096.096 0 00.037-.043z" />
|
||||
<path d="M17.427 11.527h-3.58c-.356-.281-.71-.557-1.063-.819-.365-.27-.729-.527-1.083-.77l.903-3.008c.818-.215 1.697-.46 2.585-.739l2.238 5.336z" />
|
||||
<path d="M12.775.067a.045.045 0 01.035.003c2.8 1.528 6.037 2.213 10.33 2.575.012 0 .017.01.02.021a.094.094 0 00-.1-.006l-.008.004-.025.015-.099.053c-.086.048-.215.117-.38.205a46.846 46.846 0 01-6.276 2.709 42.38 42.38 0 01-.98.32l-1.397-3.333.05-.161a.16.16 0 00-.108-.2.157.157 0 00-.092.003L12.844.124a.092.092 0 00-.12-.049.091.091 0 00-.049.12l.943 2.248-1.316 4.376a65.208 65.208 0 01-3.36.777c-.224.045-.402.08-.522.102l-.138.026-.035.006a.128.128 0 01-.01.001l-.002.001a.093.093 0 00-.074.106c.009.05.057.083.106.074h.003l.01-.002.035-.007.139-.026c.121-.023.3-.057.524-.103a66.222 66.222 0 003.264-.75l-.818 2.722-.043-.03a26.931 26.931 0 00-1.506-.959c-.614-.36-1.17-.647-1.638-.842l-.002-.002-.037-.016a.09.09 0 01-.036-.14c2.092-2.736 3.83-5.444 4.498-7.63a.093.093 0 01.115-.06z" />
|
||||
<path d="M15.118 6.021a56.31 56.31 0 01-2.452.705l1.146-3.819 1.306 3.114z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -23,16 +23,17 @@ import {
|
||||
Server,
|
||||
Sparkles,
|
||||
Terminal,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||
import { CopilotKitIcon } from "./copilotkit-icon";
|
||||
import {
|
||||
DOCS_MEGA_MENU_COLUMNS,
|
||||
isDocsExplorePath,
|
||||
type MegaMenuIconName,
|
||||
} from "@/lib/docs-mega-menu";
|
||||
import type { MegaMenuIconName } from "@/lib/docs-mega-menu";
|
||||
|
||||
const MEGA_MENU_ICONS: Record<MegaMenuIconName, LucideIcon> = {
|
||||
const MEGA_MENU_ICONS: Record<Exclude<MegaMenuIconName, "kite">, LucideIcon> = {
|
||||
book: BookOpen,
|
||||
rocket: Rocket,
|
||||
terminal: Terminal,
|
||||
@@ -118,7 +119,7 @@ export function DocsMegaMenu({
|
||||
className="h-4 w-4 shrink-0 text-current"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>Explore docs</span>
|
||||
<span>Docs</span>
|
||||
<ChevronDown
|
||||
className={`h-3.5 w-3.5 text-current transition-transform duration-150 ${
|
||||
open ? "rotate-180" : ""
|
||||
@@ -142,14 +143,15 @@ export function DocsMegaMenu({
|
||||
if (openedByPointer.current) event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<nav aria-label="Explore docs">
|
||||
<nav aria-label="Docs">
|
||||
<div className="shell-docs-mega-menu-grid">
|
||||
{DOCS_MEGA_MENU_COLUMNS.map((column) => (
|
||||
<section key={column.title} className="min-w-0">
|
||||
<h2 className="shell-docs-mega-menu-heading">{column.title}</h2>
|
||||
<ul role="list" className="shell-docs-mega-menu-list">
|
||||
{column.links.map((link) => {
|
||||
const Icon = MEGA_MENU_ICONS[link.icon];
|
||||
const Icon =
|
||||
link.icon === "kite" ? null : MEGA_MENU_ICONS[link.icon];
|
||||
const featured = link.featured === true;
|
||||
return (
|
||||
<li key={link.href}>
|
||||
@@ -162,10 +164,14 @@ export function DocsMegaMenu({
|
||||
: "shell-docs-mega-menu-link"
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
className="shell-docs-mega-menu-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{Icon ? (
|
||||
<Icon
|
||||
className="shell-docs-mega-menu-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : (
|
||||
<CopilotKitIcon className="shell-docs-mega-menu-icon" />
|
||||
)}
|
||||
<span className="min-w-0">
|
||||
<span className="block truncate font-medium">
|
||||
{link.label}
|
||||
|
||||
@@ -2,14 +2,10 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { ChefHat, Star } from "lucide-react";
|
||||
import { ChefHat } from "lucide-react";
|
||||
import BookIcon from "./icons/book";
|
||||
import { DocsMegaMenu } from "./docs-mega-menu";
|
||||
import {
|
||||
INTELLIGENCE_DOCS_HREF,
|
||||
isDocsExplorePath,
|
||||
isIntelligenceDocsPath,
|
||||
} from "@/lib/docs-mega-menu";
|
||||
import { isDocsExplorePath } from "@/lib/docs-mega-menu";
|
||||
|
||||
const PRIMARY_DOCS_LINKS = [
|
||||
{
|
||||
@@ -27,11 +23,6 @@ const PRIMARY_DOCS_LINKS = [
|
||||
label: "Cookbook",
|
||||
icon: <ChefHat className="h-4 w-4 text-current" />,
|
||||
},
|
||||
{
|
||||
href: INTELLIGENCE_DOCS_HREF,
|
||||
label: "Intelligence",
|
||||
icon: <Star className="h-4 w-4 text-current" />,
|
||||
},
|
||||
];
|
||||
|
||||
function getActiveRoute(pathname: string) {
|
||||
@@ -45,10 +36,6 @@ function getActiveRoute(pathname: string) {
|
||||
return "/cookbook";
|
||||
}
|
||||
|
||||
if (isIntelligenceDocsPath(pathname)) {
|
||||
return INTELLIGENCE_DOCS_HREF;
|
||||
}
|
||||
|
||||
return "/";
|
||||
}
|
||||
|
||||
@@ -87,11 +74,7 @@ export function PrimaryDocsTabs({
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
className={`${tabClassName}${
|
||||
link.href === INTELLIGENCE_DOCS_HREF
|
||||
? " shell-docs-nav-link-intelligence"
|
||||
: ""
|
||||
}`}
|
||||
className={tabClassName}
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
>
|
||||
{link.icon}
|
||||
|
||||
@@ -9,7 +9,6 @@ import GithubIcon from "./icons/github";
|
||||
import DiscordIcon from "./icons/discord";
|
||||
import { MobileSidebarFooterTalk } from "./mobile-sidebar-footer-talk";
|
||||
import { PrimaryDocsTabs } from "./primary-docs-tabs";
|
||||
import { SidebarIntelligenceEntry } from "./sidebar-intelligence-entry";
|
||||
|
||||
// Shared Fumadocs `DocsLayout` chrome used by every shell-docs route.
|
||||
// All five callers (home overview, framework root, framework-scoped MDX
|
||||
@@ -50,7 +49,6 @@ export function ShellDocsLayout({
|
||||
banner: (
|
||||
<div key="shell-docs-sidebar-banner" className="flex flex-col gap-2">
|
||||
{banner}
|
||||
<SidebarIntelligenceEntry />
|
||||
<PrimaryDocsTabs className="shell-docs-mobile-sidebar-tabs" />
|
||||
</div>
|
||||
),
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Star } from "lucide-react";
|
||||
import {
|
||||
INTELLIGENCE_DOCS_HREF,
|
||||
isIntelligenceDocsPath,
|
||||
} from "@/lib/docs-mega-menu";
|
||||
|
||||
export function SidebarIntelligenceEntry() {
|
||||
const pathname = usePathname() ?? "/";
|
||||
const active = isIntelligenceDocsPath(pathname);
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={INTELLIGENCE_DOCS_HREF}
|
||||
className={`shell-docs-intelligence-entry ${
|
||||
active ? "shell-docs-intelligence-entry-active" : ""
|
||||
}`}
|
||||
aria-current={active ? "page" : undefined}
|
||||
>
|
||||
<Star className="h-4 w-4 shrink-0" aria-hidden="true" />
|
||||
<span className="min-w-0 truncate">Intelligence</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: CopilotKit Intelligence
|
||||
icon: "lucide/Star"
|
||||
icon: "custom/copilotkit-kite"
|
||||
description: CopilotKit Intelligence capabilities.
|
||||
---
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: CopilotKit Intelligence
|
||||
icon: "lucide/Star"
|
||||
icon: "custom/copilotkit-kite"
|
||||
description: CopilotKit Intelligence capabilities.
|
||||
---
|
||||
|
||||
<Overview />
|
||||
<Overview />
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: CopilotKit Intelligence
|
||||
icon: "lucide/Star"
|
||||
icon: "custom/copilotkit-kite"
|
||||
description: CopilotKit Intelligence capabilities.
|
||||
---
|
||||
<Overview components={props.components} />
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
---
|
||||
title: CopilotKit Intelligence
|
||||
icon: "lucide/Star"
|
||||
icon: "custom/copilotkit-kite"
|
||||
description: CopilotKit Intelligence capabilities.
|
||||
---
|
||||
|
||||
import Overview from "@/snippets/shared/intelligence/overview.mdx";
|
||||
|
||||
<Overview components={props.components} />
|
||||
<Overview components={props.components} />
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"title": "Intelligence",
|
||||
"icon": "lucide/Star",
|
||||
"icon": "custom/copilotkit-kite",
|
||||
"pages": ["overview", "self-hosting"]
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: CopilotKit Intelligence
|
||||
icon: "lucide/Star"
|
||||
icon: "custom/copilotkit-kite"
|
||||
description: CopilotKit Intelligence capabilities.
|
||||
---
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: CopilotKit Intelligence
|
||||
icon: "lucide/Star"
|
||||
icon: "custom/copilotkit-kite"
|
||||
description: CopilotKit Intelligence capabilities.
|
||||
---
|
||||
<Overview />
|
||||
<Overview />
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: CopilotKit Intelligence
|
||||
icon: "lucide/Star"
|
||||
icon: "custom/copilotkit-kite"
|
||||
description: CopilotKit Intelligence capabilities.
|
||||
---
|
||||
<Overview components={props.components} />
|
||||
<Overview components={props.components} />
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
title: CopilotKit Intelligence
|
||||
icon: "lucide/Star"
|
||||
icon: "custom/copilotkit-kite"
|
||||
description: CopilotKit Intelligence capabilities.
|
||||
---
|
||||
|
||||
import Overview from "@/snippets/shared/intelligence/overview.mdx";
|
||||
|
||||
<Overview components={props.components} />
|
||||
<Overview components={props.components} />
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: CopilotKit Intelligence
|
||||
icon: "lucide/Star"
|
||||
icon: "custom/copilotkit-kite"
|
||||
description: CopilotKit Intelligence capabilities.
|
||||
---
|
||||
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
---
|
||||
title: CopilotKit Intelligence
|
||||
icon: "lucide/Star"
|
||||
icon: "custom/copilotkit-kite"
|
||||
description: CopilotKit Intelligence capabilities.
|
||||
---
|
||||
|
||||
|
||||
<Overview components={props.components} />
|
||||
<Overview components={props.components} />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: CopilotKit Intelligence
|
||||
icon: "lucide/Star"
|
||||
icon: "custom/copilotkit-kite"
|
||||
description: CopilotKit Intelligence overview for CopilotKit — features, cloud-hosted and self-hosted deployment options, threads, hosted inspection, and production operations.
|
||||
doc_type: explanation
|
||||
---
|
||||
|
||||
@@ -54,6 +54,25 @@ function pageSlugs(nodes: NavNode[]): string[] {
|
||||
});
|
||||
}
|
||||
|
||||
function sectionTitles(nodes: NavNode[]): string[] {
|
||||
return nodes.flatMap((node) => (node.type === "section" ? [node.title] : []));
|
||||
}
|
||||
|
||||
function sectionNodes(nodes: NavNode[], section: string): NavNode[] {
|
||||
const sectionIndex = nodes.findIndex(
|
||||
(node) => node.type === "section" && node.title === section,
|
||||
);
|
||||
if (sectionIndex === -1) return [];
|
||||
|
||||
const nextSectionIndex = nodes.findIndex(
|
||||
(node, index) => index > sectionIndex && node.type === "section",
|
||||
);
|
||||
return nodes.slice(
|
||||
sectionIndex + 1,
|
||||
nextSectionIndex === -1 ? undefined : nextSectionIndex,
|
||||
);
|
||||
}
|
||||
|
||||
function getReactNavTree(backendFramework: string | null): NavNode[] {
|
||||
if (!backendFramework) {
|
||||
return buildRootSurfaceNav(getDocsFolder(ROOT_FRAMEWORK));
|
||||
@@ -198,6 +217,53 @@ test("maps every React navigation destination to a published Angular destination
|
||||
}
|
||||
});
|
||||
|
||||
test("uses the normalized sidebar flow for Angular docs", () => {
|
||||
const navTree = getAngularDocsNavTree(null);
|
||||
const titles = sectionTitles(navTree);
|
||||
const gettingStarted = sectionNodes(navTree, "Getting Started");
|
||||
const quickstartIndex = gettingStarted.findIndex(
|
||||
(node) => node.type === "page" && node.title === "Angular quickstart",
|
||||
);
|
||||
const intelligenceLink = gettingStarted[quickstartIndex + 1];
|
||||
const orderedSections = [
|
||||
"Getting Started",
|
||||
"Basics",
|
||||
"Generative UI",
|
||||
"App Control",
|
||||
"Intelligence",
|
||||
"Angular Guides",
|
||||
"Built-in Agent",
|
||||
"Deployment",
|
||||
].filter((title) => titles.includes(title));
|
||||
|
||||
expect(titles.filter((title) => orderedSections.includes(title))).toEqual([
|
||||
"Getting Started",
|
||||
"Basics",
|
||||
"Intelligence",
|
||||
"Angular Guides",
|
||||
"Built-in Agent",
|
||||
"Deployment",
|
||||
]);
|
||||
expect(titles.filter((title) => title === "Getting Started")).toHaveLength(1);
|
||||
expect(titles.indexOf("Angular Guides")).toBeGreaterThan(
|
||||
titles.indexOf("Intelligence"),
|
||||
);
|
||||
expect(titles.indexOf("Deployment")).toBeGreaterThan(
|
||||
titles.indexOf("Angular Guides"),
|
||||
);
|
||||
expect(intelligenceLink).toMatchObject({
|
||||
type: "page",
|
||||
title: "CopilotKit Intelligence",
|
||||
icon: "custom/copilotkit-kite",
|
||||
});
|
||||
expect(
|
||||
sectionNodes(navTree, "Intelligence").some(
|
||||
(node) =>
|
||||
node.type === "page" && node.title === "CopilotKit Intelligence",
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test("resolves canonical contribution aliases through shared Angular content", () => {
|
||||
expect(
|
||||
resolveAngularDoc("claude-sdk-python", "contributing/code-contributions"),
|
||||
|
||||
@@ -28,7 +28,7 @@ describe("docs mega menu data", () => {
|
||||
expect(intelligence).toEqual({
|
||||
href: "/intelligence/overview",
|
||||
label: "Intelligence",
|
||||
icon: "sparkles",
|
||||
icon: "kite",
|
||||
featured: true,
|
||||
});
|
||||
expect(shipColumn?.links.map((link) => [link.label, link.href])).toEqual([
|
||||
@@ -57,12 +57,13 @@ describe("isIntelligenceDocsPath", () => {
|
||||
});
|
||||
|
||||
describe("isDocsExplorePath", () => {
|
||||
it("treats guide pages as Explore docs, not Reference or Cookbook", () => {
|
||||
it("treats Intelligence and guide pages as Docs", () => {
|
||||
expect(isDocsExplorePath("/")).toBe(true);
|
||||
expect(isDocsExplorePath("/quickstart")).toBe(true);
|
||||
expect(isDocsExplorePath("/reference")).toBe(false);
|
||||
expect(isDocsExplorePath("/reference/hooks/useAgent")).toBe(false);
|
||||
expect(isDocsExplorePath("/cookbook")).toBe(false);
|
||||
expect(isDocsExplorePath("/intelligence/overview")).toBe(false);
|
||||
expect(isDocsExplorePath("/intelligence/overview")).toBe(true);
|
||||
expect(isDocsExplorePath("/premium/overview")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ vi.mock("../registry", async (importOriginal) => {
|
||||
import {
|
||||
buildFrameworkNav,
|
||||
buildFrameworkOnlyNav,
|
||||
buildRootSurfaceNav,
|
||||
CONTENT_DIR,
|
||||
inlineSnippets,
|
||||
loadDoc,
|
||||
@@ -114,6 +115,30 @@ function sectionPages(navTree: NavNode[], section: string): string[] {
|
||||
return pages;
|
||||
}
|
||||
|
||||
function sectionTitles(navTree: NavNode[]): string[] {
|
||||
return navTree
|
||||
.filter(
|
||||
(node): node is Extract<NavNode, { type: "section" }> =>
|
||||
node.type === "section",
|
||||
)
|
||||
.map((node) => node.title);
|
||||
}
|
||||
|
||||
function sectionNodes(navTree: NavNode[], section: string): NavNode[] {
|
||||
const sectionIndex = navTree.findIndex(
|
||||
(node) => node.type === "section" && node.title === section,
|
||||
);
|
||||
if (sectionIndex === -1) return [];
|
||||
|
||||
const nextSectionIndex = navTree.findIndex(
|
||||
(node, index) => index > sectionIndex && node.type === "section",
|
||||
);
|
||||
return navTree.slice(
|
||||
sectionIndex + 1,
|
||||
nextSectionIndex === -1 ? undefined : nextSectionIndex,
|
||||
);
|
||||
}
|
||||
|
||||
function collectMdxFiles(dir: string): string[] {
|
||||
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
||||
const filePath = path.join(dir, entry.name);
|
||||
@@ -201,7 +226,7 @@ describe("loadDoc", () => {
|
||||
it("resolves clean URLs to files stored under route-group folders", () => {
|
||||
const doc = loadDoc("integrations/aws-strands/telemetry");
|
||||
|
||||
expect(doc?.filePath).toContain(
|
||||
expect(doc?.filePath.replaceAll("\\", "/")).toContain(
|
||||
"integrations/aws-strands/(other)/telemetry/index.mdx",
|
||||
);
|
||||
});
|
||||
@@ -466,6 +491,82 @@ describe("cookbook nav", () => {
|
||||
});
|
||||
|
||||
describe("framework nav", () => {
|
||||
it("uses the requested section flow on the root docs surface", () => {
|
||||
const navTree = buildRootSurfaceNav("built-in-agent");
|
||||
const titles = sectionTitles(navTree);
|
||||
const gettingStarted = sectionNodes(navTree, "Getting Started");
|
||||
const quickstartIndex = gettingStarted.findIndex(
|
||||
(node) => node.type === "page" && node.slug === "quickstart",
|
||||
);
|
||||
const deployment = sectionNodes(navTree, "Deployment");
|
||||
const runtime = deployment.find(
|
||||
(node) => node.type === "group" && node.title === "Runtime",
|
||||
);
|
||||
|
||||
expect(titles).toEqual([
|
||||
"Getting Started",
|
||||
"Basics",
|
||||
"Generative UI",
|
||||
"App Control",
|
||||
"Intelligence",
|
||||
"Built-in Agent",
|
||||
"Deployment",
|
||||
"Concepts",
|
||||
"Other",
|
||||
]);
|
||||
expect(gettingStarted[quickstartIndex + 1]).toMatchObject({
|
||||
type: "page",
|
||||
title: "CopilotKit Intelligence",
|
||||
icon: "custom/copilotkit-kite",
|
||||
});
|
||||
expect(runtime).toMatchObject({ type: "group", title: "Runtime" });
|
||||
expect(
|
||||
runtime?.type === "group" &&
|
||||
hasPageTitle(runtime.children, "Copilot Runtime"),
|
||||
).toBe(true);
|
||||
expect(titles).not.toContain("Runtime");
|
||||
expect(titles).not.toContain("Deploy");
|
||||
});
|
||||
|
||||
it("uses the same section flow for generated and authored frameworks", () => {
|
||||
const generatedNav = buildFrameworkNav(
|
||||
"langgraph",
|
||||
"LangGraph (Python)",
|
||||
"langgraph-python",
|
||||
);
|
||||
const authoredNav = buildFrameworkOnlyNav("ag2");
|
||||
|
||||
expect(sectionTitles(generatedNav)).toEqual([
|
||||
"Getting Started",
|
||||
"Basics",
|
||||
"Generative UI",
|
||||
"App Control",
|
||||
"Intelligence",
|
||||
"LangGraph (Python)",
|
||||
"Deployment",
|
||||
"Concepts",
|
||||
"Observe & Operate",
|
||||
"Other",
|
||||
]);
|
||||
expect(sectionTitles(authoredNav)).toEqual([
|
||||
"Getting Started",
|
||||
"Basics",
|
||||
"Generative UI",
|
||||
"App Control",
|
||||
"Intelligence",
|
||||
"AG2",
|
||||
"Deployment",
|
||||
"Troubleshooting",
|
||||
"Other",
|
||||
]);
|
||||
expect(hasSectionPage(authoredNav, "Deployment", "Copilot Runtime")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
hasSectionPage(authoredNav, "Getting Started", "CopilotKit Intelligence"),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("leaves Slack and Teams platform guides ungated", () => {
|
||||
const slack = loadDoc("frontends/slack");
|
||||
const teams = loadDoc("frontends/teams");
|
||||
|
||||
@@ -25,6 +25,8 @@ function folderNamed(
|
||||
|
||||
describe("navTreeToPageTree section folders", () => {
|
||||
const navTree: NavNode[] = [
|
||||
{ type: "section", title: "Getting Started", icon: "lucide/Rocket" },
|
||||
{ type: "page", title: "Introduction", slug: "" },
|
||||
{ type: "section", title: "Concepts", icon: "lucide/BookOpen" },
|
||||
{ type: "page", title: "Architecture", slug: "concepts/architecture" },
|
||||
{ type: "page", title: "Which Hook", slug: "concepts/which-hook" },
|
||||
@@ -54,10 +56,12 @@ describe("navTreeToPageTree section folders", () => {
|
||||
|
||||
const pageTree = navTreeToPageTree(navTree, "");
|
||||
|
||||
it("turns labeled sections into collapsed folders", () => {
|
||||
it("opens Getting Started and collapses the other labeled sections", () => {
|
||||
const gettingStarted = folderNamed(pageTree.children, "Getting Started");
|
||||
const concepts = folderNamed(pageTree.children, "Concepts");
|
||||
const basics = folderNamed(pageTree.children, "Basics");
|
||||
|
||||
expect(gettingStarted?.defaultOpen).toBe(true);
|
||||
expect(concepts).toBeDefined();
|
||||
expect(basics).toBeDefined();
|
||||
expect(concepts?.defaultOpen).toBe(false);
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
buildFrameworkOnlyNav,
|
||||
buildRootSurfaceNav,
|
||||
loadDoc,
|
||||
normalizeSidebarNav,
|
||||
} from "./docs-render";
|
||||
import type { NavNode } from "./docs-render";
|
||||
import {
|
||||
@@ -102,11 +103,12 @@ function filterResolvableAngularNodes(
|
||||
if (node.type === "page") {
|
||||
if (node.slug === "" || node.slug === "quickstart") return [];
|
||||
const canonicalSlug = getFrontendCanonicalSlug("angular", node.slug);
|
||||
if (publishedSlugs.has(canonicalSlug)) {
|
||||
const isIntelligenceOverview = canonicalSlug === "intelligence/overview";
|
||||
if (publishedSlugs.has(canonicalSlug) && !isIntelligenceOverview) {
|
||||
return [];
|
||||
}
|
||||
if (!resolveAngularDoc(backendFramework, canonicalSlug)) return [];
|
||||
publishedSlugs.add(canonicalSlug);
|
||||
if (!isIntelligenceOverview) publishedSlugs.add(canonicalSlug);
|
||||
return [{ ...node, slug: canonicalSlug }];
|
||||
}
|
||||
|
||||
@@ -178,5 +180,5 @@ export function getAngularDocsNavTree(
|
||||
),
|
||||
);
|
||||
|
||||
return [...prefixPages, ...backendNodes];
|
||||
return normalizeSidebarNav([...prefixPages, ...backendNodes]);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ export type MegaMenuIconName =
|
||||
| "box"
|
||||
| "radio"
|
||||
| "brain"
|
||||
| "chart";
|
||||
| "chart"
|
||||
| "kite";
|
||||
|
||||
export interface MegaMenuLink {
|
||||
href: string;
|
||||
@@ -69,7 +70,7 @@ export const DOCS_MEGA_MENU_COLUMNS: readonly MegaMenuColumn[] = [
|
||||
{
|
||||
href: INTELLIGENCE_DOCS_HREF,
|
||||
label: "Intelligence",
|
||||
icon: "sparkles",
|
||||
icon: "kite",
|
||||
featured: true,
|
||||
},
|
||||
{ href: "/threads", label: "Threads", icon: "layers" },
|
||||
@@ -109,9 +110,5 @@ export function isIntelligenceDocsPath(pathname: string) {
|
||||
|
||||
export function isDocsExplorePath(pathname: string) {
|
||||
const firstSegment = pathname === "/" ? "/" : `/${pathname.split("/")[1]}`;
|
||||
return (
|
||||
firstSegment !== "/reference" &&
|
||||
firstSegment !== "/cookbook" &&
|
||||
firstSegment !== "/intelligence"
|
||||
);
|
||||
return firstSegment !== "/reference" && firstSegment !== "/cookbook";
|
||||
}
|
||||
|
||||
@@ -83,9 +83,11 @@ const SECTION_ICONS: Record<string, string> = {
|
||||
"add agent powers": "lucide/Wand2",
|
||||
runtime: "lucide/Cpu",
|
||||
"observe & operate": "lucide/SearchCheck",
|
||||
intelligence: "custom/copilotkit-kite",
|
||||
"intelligence platform": "custom/copilotkit-kite",
|
||||
channels: "lucide/MessagesSquare",
|
||||
deploy: "lucide/Cloud",
|
||||
deployment: "lucide/Cloud",
|
||||
other: "lucide/MoreHorizontal",
|
||||
// Built-in Agent (authored) sections — match the section names in
|
||||
// `content/docs/integrations/built-in-agent/meta.json`. Adjust here
|
||||
@@ -654,9 +656,11 @@ export function buildFrameworkOnlyNav(
|
||||
}
|
||||
return node;
|
||||
};
|
||||
return dropEmptySections(
|
||||
appendSharedThreadArchitecturePage(
|
||||
appendSharedRootSections(nodes.map(rewrite), sharedSections),
|
||||
return normalizeSidebarNav(
|
||||
dropEmptySections(
|
||||
appendSharedThreadArchitecturePage(
|
||||
appendSharedRootSections(nodes.map(rewrite), sharedSections),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -757,6 +761,172 @@ function findPageBySlug(navTree: NavNode[], slug: string): NavNode | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
type SidebarSection = {
|
||||
section: Extract<NavNode, { type: "section" }>;
|
||||
children: NavNode[];
|
||||
sourceIndex: number;
|
||||
};
|
||||
|
||||
const SIDEBAR_SECTION_TITLES: Record<string, string> = {
|
||||
"get started": "Getting Started",
|
||||
"getting started": "Getting Started",
|
||||
"build chat uis": "Basics",
|
||||
basics: "Basics",
|
||||
"build generative ui": "Generative UI",
|
||||
"generative ui": "Generative UI",
|
||||
"add agent powers": "App Control",
|
||||
"app control": "App Control",
|
||||
intelligence: "Intelligence",
|
||||
"intelligence platform": "Intelligence",
|
||||
backend: "Runtime",
|
||||
runtime: "Runtime",
|
||||
deploy: "Deployment",
|
||||
deployment: "Deployment",
|
||||
};
|
||||
|
||||
function sidebarSectionRank(title: string): number {
|
||||
switch (title) {
|
||||
case "Getting Started":
|
||||
return 0;
|
||||
case "Basics":
|
||||
return 1;
|
||||
case "Generative UI":
|
||||
return 2;
|
||||
case "App Control":
|
||||
return 3;
|
||||
case "Intelligence":
|
||||
return 4;
|
||||
case "Deployment":
|
||||
return 6;
|
||||
case "Concepts":
|
||||
case "Observe & Operate":
|
||||
case "Platforms":
|
||||
case "Tutorials":
|
||||
case "Troubleshooting":
|
||||
case "Other":
|
||||
return 7;
|
||||
default:
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeSidebarNav(navTree: NavNode[]) {
|
||||
const prefix: NavNode[] = [];
|
||||
const sections: SidebarSection[] = [];
|
||||
const sectionsByTitle = new Map<string, SidebarSection>();
|
||||
let current: SidebarSection | undefined;
|
||||
|
||||
for (const node of navTree) {
|
||||
if (node.type === "section") {
|
||||
const title =
|
||||
SIDEBAR_SECTION_TITLES[node.title.toLowerCase()] ?? node.title;
|
||||
const existing = sectionsByTitle.get(title);
|
||||
if (existing) {
|
||||
current = existing;
|
||||
continue;
|
||||
}
|
||||
current = {
|
||||
section: {
|
||||
...node,
|
||||
title,
|
||||
icon: node.icon ?? sectionIconFor(title),
|
||||
},
|
||||
children: [],
|
||||
sourceIndex: sections.length,
|
||||
};
|
||||
sections.push(current);
|
||||
sectionsByTitle.set(title, current);
|
||||
} else if (current) {
|
||||
current.children.push(node);
|
||||
} else {
|
||||
prefix.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
let gettingStarted = sections.find(
|
||||
({ section }) => section.title === "Getting Started",
|
||||
);
|
||||
if (prefix.length > 0) {
|
||||
if (!gettingStarted) {
|
||||
gettingStarted = {
|
||||
section: {
|
||||
type: "section",
|
||||
title: "Getting Started",
|
||||
icon: sectionIconFor("Getting Started"),
|
||||
},
|
||||
children: [],
|
||||
sourceIndex: -1,
|
||||
};
|
||||
sections.push(gettingStarted);
|
||||
}
|
||||
gettingStarted.children.unshift(...prefix);
|
||||
}
|
||||
|
||||
const runtimeSections = sections.filter(
|
||||
({ section }) => section.title === "Runtime",
|
||||
);
|
||||
const orderedSections = sections.filter(
|
||||
({ section }) => section.title !== "Runtime",
|
||||
);
|
||||
const runtimeChildren = runtimeSections.flatMap(({ children }) => children);
|
||||
if (runtimeChildren.length > 0) {
|
||||
let deployment = orderedSections.find(
|
||||
({ section }) => section.title === "Deployment",
|
||||
);
|
||||
if (!deployment) {
|
||||
deployment = {
|
||||
section: {
|
||||
type: "section",
|
||||
title: "Deployment",
|
||||
icon: sectionIconFor("Deployment"),
|
||||
},
|
||||
children: [],
|
||||
sourceIndex: sections.length,
|
||||
};
|
||||
orderedSections.push(deployment);
|
||||
}
|
||||
deployment.children.push({
|
||||
type: "group",
|
||||
title: "Runtime",
|
||||
slug: "sidebar#runtime",
|
||||
children: runtimeChildren,
|
||||
});
|
||||
}
|
||||
|
||||
const intelligence = orderedSections.find(
|
||||
({ section }) => section.title === "Intelligence",
|
||||
);
|
||||
const intelligenceOverview = intelligence
|
||||
? findPageBySlug(intelligence.children, "intelligence/overview")
|
||||
: null;
|
||||
if (gettingStarted && intelligenceOverview?.type === "page") {
|
||||
const existingIndex = gettingStarted.children.findIndex(
|
||||
(node) => node.type === "page" && node.slug === intelligenceOverview.slug,
|
||||
);
|
||||
if (existingIndex !== -1) gettingStarted.children.splice(existingIndex, 1);
|
||||
|
||||
const quickstartIndex = gettingStarted.children.findIndex(
|
||||
(node) =>
|
||||
node.type === "page" &&
|
||||
(node.slug === "quickstart" ||
|
||||
node.title.toLowerCase().endsWith("quickstart")),
|
||||
);
|
||||
gettingStarted.children.splice(quickstartIndex + 1, 0, {
|
||||
...intelligenceOverview,
|
||||
icon: "custom/copilotkit-kite",
|
||||
});
|
||||
}
|
||||
|
||||
return orderedSections
|
||||
.sort((left, right) => {
|
||||
const rankDifference =
|
||||
sidebarSectionRank(left.section.title) -
|
||||
sidebarSectionRank(right.section.title);
|
||||
return rankDifference || left.sourceIndex - right.sourceIndex;
|
||||
})
|
||||
.flatMap(({ section, children }) => [section, ...children]);
|
||||
}
|
||||
|
||||
function isRichThreadsGroup(
|
||||
node: NavNode,
|
||||
): node is Extract<NavNode, { type: "group" }> {
|
||||
@@ -977,11 +1147,13 @@ export function buildFrameworkNav(
|
||||
frameworkName: string,
|
||||
frameworkSlug: string,
|
||||
): NavNode[] {
|
||||
return mergeFrameworkNav(
|
||||
buildNavTree(CONTENT_DIR),
|
||||
buildFrameworkOverridesNav(docsFolder),
|
||||
frameworkName,
|
||||
frameworkSectionIcon(frameworkSlug),
|
||||
return normalizeSidebarNav(
|
||||
mergeFrameworkNav(
|
||||
buildNavTree(CONTENT_DIR),
|
||||
buildFrameworkOverridesNav(docsFolder),
|
||||
frameworkName,
|
||||
frameworkSectionIcon(frameworkSlug),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -302,7 +302,9 @@ export function wrapSectionSeparatorsAsFolders(
|
||||
...(pending.icon
|
||||
? { icon: keyedNode(pending.icon, "shell-docs-section-icon") }
|
||||
: {}),
|
||||
defaultOpen: false,
|
||||
defaultOpen:
|
||||
typeof pending.name === "string" &&
|
||||
["Get Started", "Getting Started"].includes(pending.name),
|
||||
children: pending.children,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import React from "react";
|
||||
import Image from "next/image";
|
||||
import { LanggraphIcon } from "@/components/icons/framework-icons";
|
||||
import { CopilotKitMark } from "@/components/copilotkit-mark";
|
||||
import { CopilotKitIcon } from "@/components/copilotkit-icon";
|
||||
import { FrontendLogo } from "@/components/frontend-logo";
|
||||
import {
|
||||
// Pages / sections
|
||||
@@ -140,11 +140,10 @@ const ICONS: Record<string, React.ReactNode> = {
|
||||
"lucide/Wrench": <Wrench />,
|
||||
"lucide/Zap": <Zap />,
|
||||
// Custom marks — used by section headers for framework / enterprise
|
||||
// scaffolding. The CopilotKit kite is the inline gradient mark from
|
||||
// BrandNav; the LangGraph mark comes from the framework-icons set so
|
||||
// scaffolding. The LangGraph mark comes from the framework-icons set so
|
||||
// its visual treatment matches the framework picker.
|
||||
"custom/langgraph": <LanggraphIcon />,
|
||||
"custom/copilotkit-kite": <CopilotKitMark />,
|
||||
"custom/copilotkit-kite": <CopilotKitIcon />,
|
||||
"custom/react": <FrontendLogo icon="react" size={16} />,
|
||||
"custom/daytona": (
|
||||
<Image
|
||||
|
||||
Reference in New Issue
Block a user