Skip to content

Commit 0256835

Browse files
authored
Docs/feature sidebar integrations (CopilotKit#2994)
1 parent 517be65 commit 0256835

17 files changed

Lines changed: 332 additions & 91 deletions

File tree

docs/.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,7 @@ yarn-error.log*
2626
# others
2727
.env*.local
2828
.vercel
29-
next-env.d.ts
29+
next-env.d.ts
30+
31+
# Claude session context
32+
SESSION_CONTEXT.md

docs/app/(home)/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { baseOptions } from "../layout.config"
55
import { source } from "@/app/source"
66
import ConditionalSidebar from "@/components/layout/conditional-sidebar"
77
import Navbar from "@/components/layout/navbar"
8+
import { ScrollReset } from "@/components/layout/scroll-reset"
89
import { patchPageTree } from "@/lib/patch-pagetree"
910

1011
export default function Layout({ children }: { children: ReactNode }) {
@@ -16,6 +17,7 @@ export default function Layout({ children }: { children: ReactNode }) {
1617
<HomeLayout {...baseOptions} nav={{ enabled: false }}>
1718
<ConditionalSidebar pageTree={patchedPageTree} />
1819
<div className="docs-content-wrapper">
20+
<ScrollReset />
1921
<DocsLayout
2022
tree={patchedPageTree}
2123
searchToggle={{ enabled: false }}

docs/app/global.css

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@
144144
--sidebar-ring: oklch(0.65 0.15 285);
145145
--sidebar-separator: oklch(0.6119 0.0089 286.13);
146146
--scrollbar-color: oklch(1 0 0 / 30.2%);
147-
--scrollbar-track: #182326;
148-
--glass-background: #182326;
147+
--scrollbar-track: #292D2F;
148+
--glass-background: rgba(255, 255, 255, 0.1);
149149
--icon: oklch(1 0 0);
150150
}
151151

@@ -250,7 +250,7 @@ figure > div.bg-fd-secondary.rounded-lg.border.text-\[13px\].py-3\.5.overflow-au
250250
}
251251

252252
.dark .docs-content-wrapper {
253-
background: linear-gradient(to right, rgba(1, 5, 7, 0.10), rgba(1, 5, 7, 0)), var(--glass-background) !important;
253+
background: linear-gradient(to right, rgba(1, 5, 7, 0.10), rgba(1, 5, 7, 0)), var(--sidebar) !important;
254254
}
255255

256256
.dark .docs-content-wrapper #nd-docs-layout,

docs/app/integrations/layout.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@ import { baseOptions } from "../layout.config"
55
import { source } from "@/app/source"
66
import IntegrationsSidebar from "@/components/layout/integrations-sidebar"
77
import Navbar from "@/components/layout/navbar"
8+
import { ScrollReset } from "@/components/layout/scroll-reset"
89
import { patchPageTree } from "@/lib/patch-pagetree"
910

1011
export default function Layout({ children }: { children: ReactNode }) {
1112
const patchedPageTree = patchPageTree(source.pageTree);
12-
13+
1314
return (
1415
<>
1516
<Navbar pageTree={patchedPageTree} />
1617
<HomeLayout {...baseOptions} nav={{ enabled: false }}>
1718
<IntegrationsSidebar pageTree={patchedPageTree} />
1819
<div className="docs-content-wrapper">
20+
<ScrollReset />
1921
<DocsLayout
2022
tree={patchedPageTree}
2123
searchToggle={{ enabled: false }}

docs/app/logo.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client"
2+
13
import Image from "next/image"
24
import Link from "fumadocs-core/link"
35
import { cn } from "@/lib/utils"
@@ -18,9 +20,18 @@ export function Logo({
1820
width = DESKTOP_WIDTH,
1921
height = DESKTOP_HEIGHT,
2022
}: LogoProps) {
23+
const handleLogoClick = (e: React.MouseEvent) => {
24+
// Clear the integration selection from localStorage
25+
localStorage.removeItem('selectedIntegration')
26+
localStorage.setItem('lastDocsPath', '/')
27+
// Dispatch custom event to notify IntegrationSelector
28+
window.dispatchEvent(new CustomEvent('clearIntegrationSelection'))
29+
}
30+
2131
return (
2232
<Link
2333
href="/"
34+
onClick={handleLogoClick}
2435
className={cn("flex justify-center items-center", className)}
2536
>
2637
<Image

docs/components/layout/banners.tsx

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,14 @@ import { SiCrewai } from "@icons-pack/react-simple-icons";
88
import { Sparkles, Rocket } from "lucide-react";
99
import { useState, useEffect } from "react";
1010

11+
// Time in milliseconds before a dismissed banner reappears
12+
const BANNER_REAPPEAR_DELAY = 3 * 24 * 60 * 60 * 1000; // 3 days
13+
const BANNER_DISMISSED_KEY = "nd-banner-rotating-banner";
14+
const BANNER_DISMISSED_TIME_KEY = "nd-banner-rotating-banner-dismissed-at";
15+
1116
export function Banners() {
1217
const [currentBanner, setCurrentBanner] = useState(0);
18+
const [key, setKey] = useState(0); // Force re-render to show banner again
1319

1420
const bannerContent = [
1521
{
@@ -28,6 +34,7 @@ export function Banners() {
2834
}
2935
];
3036

37+
// Rotate banners
3138
useEffect(() => {
3239
const interval = setInterval(() => {
3340
setCurrentBanner((prev) => (prev + 1) % bannerContent.length);
@@ -36,10 +43,66 @@ export function Banners() {
3643
return () => clearInterval(interval);
3744
}, []);
3845

46+
// Check for dismissed banner and handle reappearance based on timestamp
47+
useEffect(() => {
48+
const checkBannerExpiry = () => {
49+
const isDismissed = localStorage.getItem(BANNER_DISMISSED_KEY) === "true";
50+
51+
if (isDismissed) {
52+
const dismissedAt = localStorage.getItem(BANNER_DISMISSED_TIME_KEY);
53+
54+
if (dismissedAt) {
55+
const elapsed = Date.now() - parseInt(dismissedAt, 10);
56+
57+
if (elapsed >= BANNER_REAPPEAR_DELAY) {
58+
// Time has passed - show banner again
59+
localStorage.removeItem(BANNER_DISMISSED_KEY);
60+
localStorage.removeItem(BANNER_DISMISSED_TIME_KEY);
61+
setKey((prev) => prev + 1);
62+
} else {
63+
// Schedule check for when delay expires
64+
const remaining = BANNER_REAPPEAR_DELAY - elapsed;
65+
const timeout = setTimeout(() => {
66+
localStorage.removeItem(BANNER_DISMISSED_KEY);
67+
localStorage.removeItem(BANNER_DISMISSED_TIME_KEY);
68+
setKey((prev) => prev + 1);
69+
}, remaining);
70+
return () => clearTimeout(timeout);
71+
}
72+
} else {
73+
// Banner was just dismissed - record the time
74+
localStorage.setItem(BANNER_DISMISSED_TIME_KEY, Date.now().toString());
75+
}
76+
}
77+
};
78+
79+
checkBannerExpiry();
80+
81+
// Also listen for storage changes (in case dismissed in this or another tab)
82+
const handleStorage = () => {
83+
const isDismissed = localStorage.getItem(BANNER_DISMISSED_KEY) === "true";
84+
const hasTimestamp = localStorage.getItem(BANNER_DISMISSED_TIME_KEY);
85+
86+
if (isDismissed && !hasTimestamp) {
87+
localStorage.setItem(BANNER_DISMISSED_TIME_KEY, Date.now().toString());
88+
}
89+
};
90+
91+
window.addEventListener("storage", handleStorage);
92+
93+
// Also check periodically in case the banner was dismissed while on this page
94+
const interval = setInterval(checkBannerExpiry, 1000);
95+
96+
return () => {
97+
window.removeEventListener("storage", handleStorage);
98+
clearInterval(interval);
99+
};
100+
}, [key]);
101+
39102
const content = bannerContent[currentBanner];
40103

41104
return (
42-
<div className="w-full px-1 mt-1 xl:px-2 xl:mt-2">
105+
<div key={key} className="w-full px-1 mt-1 xl:px-2 xl:mt-2">
43106
<Banner className="w-full text-foreground bg-secondary/80 backdrop-blur-sm border border-border rounded-2xl py-1.5 md:py-2" id="rotating-banner">
44107
<div className="flex flex-row items-center justify-center gap-1.5 md:gap-3 w-full px-1 md:px-4">
45108
<div

docs/components/layout/conditional-sidebar.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@ export default function ConditionalSidebar({ pageTree }: ConditionalSidebarProps
5454
if (isIntegrationRoute) {
5555
return <IntegrationsSidebar pageTree={pageTree} />
5656
}
57-
57+
5858
if (isReferenceRoute && referencePageTree) {
59-
return <Sidebar pageTree={referencePageTree} />
59+
return <Sidebar pageTree={referencePageTree} showIntegrationSelector={false} />
6060
}
61-
61+
6262
return <Sidebar pageTree={pageTree} />
6363
}

docs/components/layout/mobile-sidebar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { Integration } from "../ui/integrations-sidebar/integration-selector"
2323
import { INTEGRATION_ORDER, INTEGRATION_METADATA } from "@/lib/integrations"
2424
import { normalizeUrl } from "@/lib/analytics-utils"
2525

26+
2627
interface MobileSidebarProps {
2728
pageTree: DocsLayoutProps["tree"]
2829
setIsOpen: (isOpen: boolean) => void
@@ -239,7 +240,7 @@ const MobileSidebar = ({
239240

240241
<Dropdown onSelect={handleClose} />
241242

242-
{isIntegrationRoute && (
243+
{!isReferenceRoute && (
243244
<IntegrationSelector
244245
selectedIntegration={selectedIntegration}
245246
setSelectedIntegration={setSelectedIntegration}

docs/components/layout/navbar.tsx

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import SearchDialogButton from "@/components/ui/search-button"
1111
import MobileSidebar from "@/components/layout/mobile-sidebar"
1212
// Icons
1313
import RocketIcon from "@/components/ui/icons/rocket"
14-
import PuzzleIcon from "@/components/ui/icons/puzzle"
1514
import ConsoleIcon from "@/components/ui/icons/console"
1615
import CloudIcon from "@/components/ui/icons/cloud"
1716
import GithubIcon from "@/components/ui/icons/github"
@@ -34,14 +33,9 @@ interface NavbarProps {
3433
export const LEFT_LINKS: NavbarLink[] = [
3534
{
3635
icon: <RocketIcon />,
37-
label: "Overview",
36+
label: "Documentation",
3837
href: "/",
3938
},
40-
{
41-
icon: <PuzzleIcon />,
42-
label: "Integrations",
43-
href: "/integrations",
44-
},
4539
{
4640
icon: <ConsoleIcon />,
4741
label: "API Reference",
@@ -57,11 +51,6 @@ export const LEFT_LINKS: NavbarLink[] = [
5751
]
5852

5953
const RIGHT_LINKS: NavbarLink[] = [
60-
{
61-
icon: <ConsoleIcon />,
62-
href: "/reference",
63-
label: "API Reference",
64-
},
6554
{
6655
icon: <CloudIcon />,
6756
href: "https://cloud.copilotkit.ai",
@@ -82,8 +71,28 @@ const RIGHT_LINKS: NavbarLink[] = [
8271

8372
const Navbar = ({ pageTree }: NavbarProps) => {
8473
const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false)
74+
const [lastDocsPath, setLastDocsPath] = useState<string | null>(null)
8575
const pathname = usePathname()
86-
const activeRoute = pathname === "/" ? "/" : `/${pathname.split("/")[1]}`
76+
77+
// Read localStorage on client only to avoid hydration mismatch
78+
useEffect(() => {
79+
setLastDocsPath(localStorage.getItem('lastDocsPath'))
80+
}, [])
81+
82+
// Determine active route based on current path
83+
const firstSegment = pathname === "/" ? "/" : `/${pathname.split("/")[1]}`
84+
const isReferencePage = firstSegment === "/reference"
85+
// Reference pages → /reference, Everything else (root + integrations) → /
86+
const activeRoute = isReferencePage ? "/reference" : "/"
87+
88+
// Get the appropriate href for Documentation link
89+
const getDocumentationHref = () => {
90+
// If we're on a reference page, try to restore last docs path
91+
if (isReferencePage && lastDocsPath) {
92+
return lastDocsPath
93+
}
94+
return '/'
95+
}
8796

8897
// Close mobile sidebar when viewport expands beyond mobile breakpoint (md: 768px)
8998
useEffect(() => {
@@ -126,15 +135,17 @@ const Navbar = ({ pageTree }: NavbarProps) => {
126135
<Logo className="pl-6" />
127136
<ul className="hidden gap-6 items-center h-full md:flex">
128137
{LEFT_LINKS.map((link) => {
129-
// Hide API Reference and Copilot Cloud at narrow widths
130-
const hideAtNarrow = link.label === "API Reference" || link.label === "Copilot Cloud";
131-
// Hide icons for Overview and Integrations at very narrow widths
132-
const hideIconAtNarrow = link.label === "Overview" || link.label === "Integrations";
133-
138+
// Hide only Copilot Cloud at narrow widths
139+
const hideAtNarrow = link.label === "Copilot Cloud";
140+
// Hide icons for Documentation and API Reference at very narrow widths
141+
const hideIconAtNarrow = link.label === "Documentation" || link.label === "API Reference";
142+
// Use dynamic href for Documentation link
143+
const href = link.label === "Documentation" ? getDocumentationHref() : link.href;
144+
134145
return (
135-
<li key={link.href} className={`relative h-full group ${hideAtNarrow ? '[@media(width<1112px)]:hidden' : ''}`}>
146+
<li key={link.href} className={`relative h-full group ${hideAtNarrow ? '[@media(width<1028px)]:hidden' : ''}`}>
136147
<Link
137-
href={link.href}
148+
href={href}
138149
target={link.target}
139150
className={`h-full ${
140151
activeRoute === link.href ? "opacity-100" : "opacity-50"
@@ -201,15 +212,15 @@ const Navbar = ({ pageTree }: NavbarProps) => {
201212
style={{ backgroundColor: 'var(--sidebar)' }}
202213
>
203214
{RIGHT_LINKS.map((link) => {
204-
// For API Reference and Copilot Cloud, only show at narrow widths (between 768px and 1112px)
205-
const isIconOnlyLink = link.label === "API Reference" || link.label === "Copilot Cloud";
206-
215+
// Only show Copilot Cloud at narrow widths (between 768px and 1028px)
216+
const isIconOnlyLink = link.label === "Copilot Cloud";
217+
207218
return (
208219
<Link
209220
key={link.href}
210221
href={link.href}
211222
target={link.target}
212-
className={`${isIconOnlyLink ? '[@media(width>=1112px)]:hidden [@media(width<768px)]:hidden' : 'hidden'} justify-center items-center w-11 h-full md:flex`}
223+
className={`${isIconOnlyLink ? '[@media(width>=1028px)]:hidden [@media(width<768px)]:hidden' : 'hidden'} justify-center items-center w-11 h-full md:flex`}
213224
title={link.label}
214225
suppressHydrationWarning={link.target === "_blank"}
215226
>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"use client"
2+
3+
import { useEffect } from "react"
4+
import { usePathname } from "next/navigation"
5+
6+
/**
7+
* Resets scroll position of the parent scroll container when navigating to a new page.
8+
* Fixes issue where scroll position persists when using collapsed TOC navigation.
9+
*/
10+
export function ScrollReset() {
11+
const pathname = usePathname()
12+
13+
useEffect(() => {
14+
// Find the scroll container (docs-content-wrapper)
15+
const scrollContainer = document.querySelector('.docs-content-wrapper')
16+
if (scrollContainer) {
17+
scrollContainer.scrollTop = 0
18+
}
19+
}, [pathname]) // Run whenever pathname changes
20+
21+
return null // This component doesn't render anything
22+
}

0 commit comments

Comments
 (0)