forked from CopilotKit/CopilotKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsidebar.tsx
More file actions
98 lines (88 loc) · 3.09 KB
/
Copy pathsidebar.tsx
File metadata and controls
98 lines (88 loc) · 3.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
"use client";
import { useState, useEffect, type ReactNode } from "react";
import { DocsLayoutProps } from "fumadocs-ui/layouts/docs";
import Separator from "../ui/sidebar/separator";
import Page from "../ui/sidebar/page";
import Folder from "../ui/sidebar/folder";
import IntegrationLink from "../ui/sidebar/integration-link";
import IntegrationSelector, {
Integration,
} from "../ui/integrations-sidebar/integration-selector";
import { OpenedFoldersProvider } from "@/lib/hooks/use-opened-folders";
import { INTEGRATION_ORDER } from "@/lib/integrations";
type Node = DocsLayoutProps["tree"]["children"][number] & {
url: string;
index?: { url: string };
};
const NODE_COMPONENTS = {
separator: Separator,
page: Page,
folder: Folder,
integrationLink: IntegrationLink,
};
const isIntegrationFolder = (node: Node): boolean => {
if (node.type !== "folder") return false;
const url = node.index?.url || node.url;
if (!url) return false;
// Integration landing pages are at /{integration} (e.g., /langgraph)
// Check if the URL matches a known integration ID
const integrationId = url.replace(/^\//, "").split("/")[0];
return INTEGRATION_ORDER.includes(
integrationId as (typeof INTEGRATION_ORDER)[number],
);
};
const Sidebar = ({
pageTree,
showIntegrationSelector = true,
headerSlot,
}: {
pageTree: DocsLayoutProps["tree"];
showIntegrationSelector?: boolean;
headerSlot?: ReactNode;
}) => {
const pages = pageTree.children;
const [selectedIntegration, setSelectedIntegration] =
useState<Integration | null>(null);
// Dispatch pageTree update for OpenedFoldersProvider
useEffect(() => {
if (pages.length > 0) {
const event = new CustomEvent("pageTreeUpdate", { detail: pages });
window.dispatchEvent(event);
}
}, [pages]);
return (
<OpenedFoldersProvider>
<aside
id="nd-sidebar"
className={`w-full max-w-[260px] h-full border backdrop-blur-lg border-r-0 border-border rounded-l-2xl pl-3 ${showIntegrationSelector ? "pr-3" : "pr-1"} flex flex-col`}
style={{ backgroundColor: "var(--sidebar)" }}
>
{showIntegrationSelector && (
<IntegrationSelector
selectedIntegration={selectedIntegration}
setSelectedIntegration={setSelectedIntegration}
/>
)}
{headerSlot && <div className="pr-2">{headerSlot}</div>}
<ul
className={`flex overflow-y-auto flex-col pr-1 max-h-full custom-scrollbar`}
>
{pages.map((page, index) => {
const nodeType = isIntegrationFolder(page as Node)
? "integrationLink"
: page.type;
const Component = NODE_COMPONENTS[nodeType];
// Use stable key based on page data to avoid hydration mismatches
const pageUrl =
(page as Node).index?.url ||
(page as Node).url ||
`page-${index}`;
const key = `${nodeType}-${pageUrl}`;
return <Component key={key} node={page as Node} />;
})}
</ul>
</aside>
</OpenedFoldersProvider>
);
};
export default Sidebar;