Skip to content

Commit 1053b71

Browse files
authored
docs: Add Inkeep Searchbar (#3597)
1 parent 0e5285a commit 1053b71

File tree

10 files changed

+2129
-694
lines changed

10 files changed

+2129
-694
lines changed

docs/app/components/inkeep-script.tsx

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"use client";
2+
3+
import {
4+
InkeepChatButton as ChatButton,
5+
InkeepChatButtonProps,
6+
} from "@inkeep/cxkit-react";
7+
import { useInkeepConfig } from "./useInkeepConfig";
8+
9+
export function InkeepChatButton() {
10+
const inkeepConfig = useInkeepConfig();
11+
return <ChatButton {...(inkeepConfig as InkeepChatButtonProps)} />;
12+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"use client";
2+
3+
import type { SharedProps } from "fumadocs-ui/components/dialog/search";
4+
import {
5+
InkeepModalSearchAndChat,
6+
type InkeepModalSearchAndChatProps,
7+
} from "@inkeep/cxkit-react";
8+
import { useInkeepConfig } from "./useInkeepConfig";
9+
10+
export default function CustomDialog(props: SharedProps) {
11+
const baseConfig = useInkeepConfig();
12+
const { open, onOpenChange } = props;
13+
14+
const config = {
15+
...baseConfig,
16+
modalSettings: {
17+
isOpen: open,
18+
onOpenChange,
19+
},
20+
};
21+
22+
return (
23+
<InkeepModalSearchAndChat {...(config as InkeepModalSearchAndChatProps)} />
24+
);
25+
}
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
import { useEffect, useState } from "react";
2+
import type {
3+
InkeepBaseSettings,
4+
InkeepAIChatSettings,
5+
InkeepSearchSettings,
6+
} from "@inkeep/cxkit-react";
7+
8+
const baseSettings: InkeepBaseSettings = {
9+
apiKey: "815f8fee7a5da7d98c681626dfbd268bdf7f7dc7cb80f618",
10+
primaryBrandColor: "#9945ff",
11+
theme: {
12+
styles: [
13+
{
14+
key: "custom-theme",
15+
type: "style",
16+
value: `
17+
#inkeep-widget-root {
18+
font-size: 1rem;
19+
}
20+
.ikp-search-bar__container {
21+
margin: 0 0 0 16px;
22+
}
23+
.ikp-search-bar__button {
24+
padding: 0px 8px;
25+
}
26+
@media (min-width: 768px) {
27+
.ikp-search-bar__container {
28+
min-width: 0px;
29+
}
30+
}
31+
@media (max-width: 768px) {
32+
.ikp-search-bar__icon {
33+
font-size: 24px;
34+
color: var(--docsearch-text-color);
35+
}
36+
.ikp-search-bar__button {
37+
border-color: transparent;
38+
}
39+
.ikp-search-bar__text {
40+
display: none;
41+
}
42+
.ikp-search-bar__kbd-wrapper {
43+
display: none;
44+
}
45+
.search-bar__content-wrapper {
46+
gap: 0;
47+
}
48+
}
49+
`,
50+
},
51+
],
52+
},
53+
transformSource: source => {
54+
const urlPatterns = {
55+
anchorDocs: "https://www.anchor-lang.com/docs",
56+
solanaDocs: "solana.com",
57+
stackExchange: "https://solana.stackexchange.com/",
58+
anzaDocs: "https://docs.anza.xyz/",
59+
github: "github.com",
60+
} as const;
61+
62+
const tabConfig = {
63+
[urlPatterns.anchorDocs]: {
64+
tab: "Anchor Docs",
65+
icon: undefined,
66+
shouldOpenInNewTab: false,
67+
getBreadcrumbs: (crumbs: string[]) => crumbs,
68+
},
69+
[urlPatterns.solanaDocs]: {
70+
tab: "Solana Docs",
71+
icon: undefined,
72+
shouldOpenInNewTab: true,
73+
getBreadcrumbs: (crumbs: string[]) => ["Docs", ...crumbs.slice(1)],
74+
},
75+
[urlPatterns.anzaDocs]: {
76+
tab: "Anza Docs",
77+
icon: undefined,
78+
shouldOpenInNewTab: true,
79+
getBreadcrumbs: (crumbs: string[]) => crumbs,
80+
},
81+
[urlPatterns.stackExchange]: {
82+
tab: "Stack Exchange",
83+
icon: undefined,
84+
shouldOpenInNewTab: true,
85+
getBreadcrumbs: (crumbs: string[]) => crumbs,
86+
},
87+
[urlPatterns.github]: {
88+
tab: "GitHub",
89+
icon: "FaGithub",
90+
shouldOpenInNewTab: true,
91+
getBreadcrumbs: (crumbs: string[]) => crumbs,
92+
},
93+
} as const;
94+
95+
// Find matching config based on URL
96+
const matchingPattern = Object.keys(tabConfig).find(pattern =>
97+
source.url.includes(pattern),
98+
);
99+
const config = matchingPattern
100+
? tabConfig[matchingPattern as keyof typeof tabConfig]
101+
: null;
102+
103+
if (!config) {
104+
return source;
105+
}
106+
107+
const breadcrumbs = config.getBreadcrumbs(source.breadcrumbs);
108+
const existingTabs = source.tabs ?? [];
109+
110+
// Check if tab already exists
111+
const tabExists = existingTabs.some(existingTab =>
112+
typeof existingTab === "string"
113+
? existingTab === config.tab
114+
: Array.isArray(existingTab) && existingTab[0] === config.tab,
115+
);
116+
117+
const tabs = tabExists
118+
? existingTabs
119+
: [
120+
...existingTabs,
121+
[
122+
config.tab,
123+
{
124+
breadcrumbs:
125+
breadcrumbs[0] === config.tab
126+
? breadcrumbs.slice(1)
127+
: breadcrumbs,
128+
},
129+
] as const,
130+
];
131+
132+
return {
133+
...source,
134+
breadcrumbs,
135+
tabs,
136+
shouldOpenInNewTab: config.shouldOpenInNewTab,
137+
icon: config.icon ? { builtIn: config.icon } : undefined,
138+
};
139+
},
140+
};
141+
142+
const searchSettings: InkeepSearchSettings = {
143+
placeholder: "Search",
144+
tabs: [
145+
"All",
146+
"Anchor Docs",
147+
"Solana Docs",
148+
"Anza Docs",
149+
"Stack Exchange",
150+
"GitHub",
151+
],
152+
};
153+
154+
const aiChatSettings: InkeepAIChatSettings = {
155+
chatSubjectName: "Anchor",
156+
introMessage:
157+
"I'm an AI assistant trained on documentation, github repos, and other content. Ask me anything about `Solana`.",
158+
aiAssistantAvatar: "https://solana.com/favicon.png",
159+
disclaimerSettings: {
160+
isEnabled: true,
161+
label: "",
162+
},
163+
toolbarButtonLabels: {
164+
getHelp: "Get Support",
165+
},
166+
getHelpOptions: [
167+
{
168+
name: "Discord",
169+
action: {
170+
type: "open_link",
171+
url: "https://discord.com/invite/NHHGSXAnXk",
172+
},
173+
icon: {
174+
builtIn: "FaDiscord",
175+
},
176+
},
177+
{
178+
name: "Stack Exchange",
179+
action: {
180+
type: "open_link",
181+
url: "https://solana.stackexchange.com/",
182+
},
183+
icon: {
184+
builtIn: "FaStackOverflow",
185+
},
186+
},
187+
],
188+
exampleQuestions: [
189+
"How to quickly install Solana dependencies for local development?",
190+
"What is the Anchor Framework?",
191+
"How to build an Anchor Program?",
192+
],
193+
};
194+
195+
export function useInkeepConfig() {
196+
const [syncTarget, setSyncTarget] = useState<HTMLElement | null>(null);
197+
198+
// We do this because document is not available in the server
199+
useEffect(() => {
200+
setSyncTarget(document.documentElement);
201+
}, []);
202+
return {
203+
baseSettings: {
204+
...baseSettings,
205+
colorMode: {
206+
sync: {
207+
target: syncTarget,
208+
attributes: ["class"],
209+
isDarkMode: (attributes: { class: string | string[] }) =>
210+
!!attributes.class?.includes("dark"),
211+
},
212+
},
213+
},
214+
searchSettings,
215+
aiChatSettings,
216+
};
217+
}

docs/app/inkeep-settings.ts

Lines changed: 0 additions & 85 deletions
This file was deleted.

0 commit comments

Comments
 (0)