@farming-labs/theme 0.0.2-beta.9 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-search-dialog.d.mts +23 -1
- package/dist/ai-search-dialog.mjs +247 -37
- package/dist/colorful/index.d.mts +78 -0
- package/dist/colorful/index.mjs +82 -0
- package/dist/darkbold/index.d.mts +80 -0
- package/dist/darkbold/index.mjs +84 -0
- package/dist/docs-ai-features.d.mts +3 -1
- package/dist/docs-ai-features.mjs +63 -10
- package/dist/docs-api.d.mts +7 -6
- package/dist/docs-api.mjs +73 -4
- package/dist/docs-command-search.d.mts +10 -0
- package/dist/docs-command-search.mjs +654 -0
- package/dist/docs-layout.d.mts +20 -11
- package/dist/docs-layout.mjs +230 -50
- package/dist/docs-page-client.d.mts +20 -0
- package/dist/docs-page-client.mjs +119 -25
- package/dist/greentree/index.d.mts +80 -0
- package/dist/greentree/index.mjs +84 -0
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +3 -2
- package/dist/page-actions.d.mts +4 -1
- package/dist/page-actions.mjs +8 -4
- package/dist/search.d.mts +1 -1
- package/dist/shiny/index.d.mts +79 -0
- package/dist/shiny/index.mjs +83 -0
- package/dist/sidebar-search-ai.d.mts +11 -0
- package/dist/sidebar-search-ai.mjs +128 -0
- package/package.json +38 -14
- package/styles/ai.css +408 -81
- package/styles/base.css +211 -14
- package/styles/colorful.css +266 -0
- package/styles/darkbold.css +575 -0
- package/styles/darksharp.css +27 -11
- package/styles/default.css +22 -1
- package/styles/greentree.css +719 -0
- package/styles/omni.css +359 -0
- package/styles/pixel-border.css +115 -52
- package/styles/shiny.css +505 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { createTheme } from "@farming-labs/docs";
|
|
2
|
+
|
|
3
|
+
//#region src/darkbold/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* DarkBold theme preset.
|
|
6
|
+
* Pure monochrome design, Geist typography, clean minimalism.
|
|
7
|
+
*
|
|
8
|
+
* CSS: `@import "@farming-labs/theme/darkbold/css";`
|
|
9
|
+
*/
|
|
10
|
+
const DarkBoldUIDefaults = {
|
|
11
|
+
colors: {
|
|
12
|
+
primary: "#000",
|
|
13
|
+
background: "#fff",
|
|
14
|
+
muted: "#666",
|
|
15
|
+
border: "#eaeaea"
|
|
16
|
+
},
|
|
17
|
+
typography: { font: {
|
|
18
|
+
style: {
|
|
19
|
+
sans: "Geist, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
|
20
|
+
mono: "Geist Mono, ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace"
|
|
21
|
+
},
|
|
22
|
+
h1: {
|
|
23
|
+
size: "2.5rem",
|
|
24
|
+
weight: 600,
|
|
25
|
+
lineHeight: "1.2",
|
|
26
|
+
letterSpacing: "-0.06em"
|
|
27
|
+
},
|
|
28
|
+
h2: {
|
|
29
|
+
size: "2rem",
|
|
30
|
+
weight: 600,
|
|
31
|
+
lineHeight: "1.25",
|
|
32
|
+
letterSpacing: "-0.04em"
|
|
33
|
+
},
|
|
34
|
+
h3: {
|
|
35
|
+
size: "1.5rem",
|
|
36
|
+
weight: 600,
|
|
37
|
+
lineHeight: "1.3",
|
|
38
|
+
letterSpacing: "-0.02em"
|
|
39
|
+
},
|
|
40
|
+
h4: {
|
|
41
|
+
size: "1.25rem",
|
|
42
|
+
weight: 600,
|
|
43
|
+
lineHeight: "1.4"
|
|
44
|
+
},
|
|
45
|
+
body: {
|
|
46
|
+
size: "1rem",
|
|
47
|
+
weight: 400,
|
|
48
|
+
lineHeight: "1.6"
|
|
49
|
+
},
|
|
50
|
+
small: {
|
|
51
|
+
size: "0.875rem",
|
|
52
|
+
weight: 400,
|
|
53
|
+
lineHeight: "1.5"
|
|
54
|
+
}
|
|
55
|
+
} },
|
|
56
|
+
layout: {
|
|
57
|
+
contentWidth: 768,
|
|
58
|
+
sidebarWidth: 260,
|
|
59
|
+
toc: {
|
|
60
|
+
enabled: true,
|
|
61
|
+
depth: 3,
|
|
62
|
+
style: "default"
|
|
63
|
+
},
|
|
64
|
+
header: {
|
|
65
|
+
height: 64,
|
|
66
|
+
sticky: true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
components: {
|
|
70
|
+
Callout: {
|
|
71
|
+
variant: "soft",
|
|
72
|
+
icon: true
|
|
73
|
+
},
|
|
74
|
+
CodeBlock: { showCopyButton: true },
|
|
75
|
+
Tabs: { style: "default" }
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const darkbold = createTheme({
|
|
79
|
+
name: "darkbold",
|
|
80
|
+
ui: DarkBoldUIDefaults
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
//#endregion
|
|
84
|
+
export { DarkBoldUIDefaults, darkbold };
|
|
@@ -2,12 +2,13 @@ import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
|
2
2
|
|
|
3
3
|
//#region src/docs-ai-features.d.ts
|
|
4
4
|
interface DocsAIFeaturesProps {
|
|
5
|
-
mode: "search" | "floating";
|
|
5
|
+
mode: "search" | "floating" | "sidebar-icon";
|
|
6
6
|
position?: "bottom-right" | "bottom-left" | "bottom-center";
|
|
7
7
|
floatingStyle?: "panel" | "modal" | "popover" | "full-modal";
|
|
8
8
|
triggerComponentHtml?: string;
|
|
9
9
|
suggestedQuestions?: string[];
|
|
10
10
|
aiLabel?: string;
|
|
11
|
+
loaderVariant?: string;
|
|
11
12
|
loadingComponentHtml?: string;
|
|
12
13
|
}
|
|
13
14
|
declare function DocsAIFeatures({
|
|
@@ -17,6 +18,7 @@ declare function DocsAIFeatures({
|
|
|
17
18
|
triggerComponentHtml,
|
|
18
19
|
suggestedQuestions,
|
|
19
20
|
aiLabel,
|
|
21
|
+
loaderVariant,
|
|
20
22
|
loadingComponentHtml
|
|
21
23
|
}: DocsAIFeaturesProps): react_jsx_runtime0.JSX.Element;
|
|
22
24
|
//#endregion
|
|
@@ -1,26 +1,35 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { DocsSearchDialog, FloatingAIChat } from "./ai-search-dialog.mjs";
|
|
3
|
+
import { AIModalDialog, DocsSearchDialog, FloatingAIChat } from "./ai-search-dialog.mjs";
|
|
4
4
|
import { useEffect, useState } from "react";
|
|
5
|
-
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
|
|
7
7
|
//#region src/docs-ai-features.tsx
|
|
8
8
|
/**
|
|
9
9
|
* Client component injected by `createDocsLayout` when `ai` is configured.
|
|
10
10
|
*
|
|
11
|
-
* Handles
|
|
11
|
+
* Handles multiple modes:
|
|
12
12
|
* - "search": Intercepts Cmd+K / Ctrl+K and opens the custom search dialog
|
|
13
13
|
* with Search + Ask AI tabs (prevents fumadocs' default dialog from opening).
|
|
14
14
|
* - "floating": Renders the floating chat widget with configurable position,
|
|
15
15
|
* style, and trigger component.
|
|
16
|
+
* - "sidebar-icon": Injects an AI trigger icon button next to the search bar
|
|
17
|
+
* in the sidebar header area (Mintlify-style).
|
|
16
18
|
*
|
|
17
19
|
* This component is rendered inside the docs layout so the user's root layout
|
|
18
20
|
* never needs to be modified — AI features work purely from `docs.config.tsx`.
|
|
19
21
|
*/
|
|
20
|
-
function DocsAIFeatures({ mode, position = "bottom-right", floatingStyle = "panel", triggerComponentHtml, suggestedQuestions, aiLabel, loadingComponentHtml }) {
|
|
22
|
+
function DocsAIFeatures({ mode, position = "bottom-right", floatingStyle = "panel", triggerComponentHtml, suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml }) {
|
|
21
23
|
if (mode === "search") return /* @__PURE__ */ jsx(SearchModeAI, {
|
|
22
24
|
suggestedQuestions,
|
|
23
25
|
aiLabel,
|
|
26
|
+
loaderVariant,
|
|
27
|
+
loadingComponentHtml
|
|
28
|
+
});
|
|
29
|
+
if (mode === "sidebar-icon") return /* @__PURE__ */ jsx(SidebarIconModeAI, {
|
|
30
|
+
suggestedQuestions,
|
|
31
|
+
aiLabel,
|
|
32
|
+
loaderVariant,
|
|
24
33
|
loadingComponentHtml
|
|
25
34
|
});
|
|
26
35
|
return /* @__PURE__ */ jsx(FloatingAIChat, {
|
|
@@ -30,15 +39,11 @@ function DocsAIFeatures({ mode, position = "bottom-right", floatingStyle = "pane
|
|
|
30
39
|
triggerComponentHtml,
|
|
31
40
|
suggestedQuestions,
|
|
32
41
|
aiLabel,
|
|
42
|
+
loaderVariant,
|
|
33
43
|
loadingComponentHtml
|
|
34
44
|
});
|
|
35
45
|
}
|
|
36
|
-
|
|
37
|
-
* Search mode: intercepts Cmd+K / Ctrl+K globally and opens the
|
|
38
|
-
* custom search dialog (with Search + Ask AI tabs) instead of
|
|
39
|
-
* fumadocs' built-in search dialog.
|
|
40
|
-
*/
|
|
41
|
-
function SearchModeAI({ suggestedQuestions, aiLabel, loadingComponentHtml }) {
|
|
46
|
+
function SearchModeAI({ suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml }) {
|
|
42
47
|
const [open, setOpen] = useState(false);
|
|
43
48
|
useEffect(() => {
|
|
44
49
|
function handler(e) {
|
|
@@ -73,9 +78,57 @@ function SearchModeAI({ suggestedQuestions, aiLabel, loadingComponentHtml }) {
|
|
|
73
78
|
api: "/api/docs",
|
|
74
79
|
suggestedQuestions,
|
|
75
80
|
aiLabel,
|
|
81
|
+
loaderVariant,
|
|
76
82
|
loadingComponentHtml
|
|
77
83
|
});
|
|
78
84
|
}
|
|
85
|
+
function SidebarIconModeAI({ suggestedQuestions, aiLabel, loaderVariant, loadingComponentHtml }) {
|
|
86
|
+
const [searchOpen, setSearchOpen] = useState(false);
|
|
87
|
+
const [aiOpen, setAiOpen] = useState(false);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
function handler(e) {
|
|
90
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
|
91
|
+
e.preventDefault();
|
|
92
|
+
e.stopPropagation();
|
|
93
|
+
e.stopImmediatePropagation();
|
|
94
|
+
setSearchOpen(true);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
document.addEventListener("keydown", handler, true);
|
|
98
|
+
return () => document.removeEventListener("keydown", handler, true);
|
|
99
|
+
}, []);
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
function onSearch() {
|
|
102
|
+
setSearchOpen(true);
|
|
103
|
+
}
|
|
104
|
+
function onAI() {
|
|
105
|
+
setAiOpen(true);
|
|
106
|
+
}
|
|
107
|
+
window.addEventListener("fd-open-search", onSearch);
|
|
108
|
+
window.addEventListener("fd-open-ai", onAI);
|
|
109
|
+
return () => {
|
|
110
|
+
window.removeEventListener("fd-open-search", onSearch);
|
|
111
|
+
window.removeEventListener("fd-open-ai", onAI);
|
|
112
|
+
};
|
|
113
|
+
}, []);
|
|
114
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(DocsSearchDialog, {
|
|
115
|
+
open: searchOpen,
|
|
116
|
+
onOpenChange: setSearchOpen,
|
|
117
|
+
api: "/api/docs",
|
|
118
|
+
suggestedQuestions,
|
|
119
|
+
aiLabel,
|
|
120
|
+
loaderVariant,
|
|
121
|
+
loadingComponentHtml
|
|
122
|
+
}), /* @__PURE__ */ jsx(AIModalDialog, {
|
|
123
|
+
open: aiOpen,
|
|
124
|
+
onOpenChange: setAiOpen,
|
|
125
|
+
api: "/api/docs",
|
|
126
|
+
suggestedQuestions,
|
|
127
|
+
aiLabel,
|
|
128
|
+
loaderVariant,
|
|
129
|
+
loadingComponentHtml
|
|
130
|
+
})] });
|
|
131
|
+
}
|
|
79
132
|
|
|
80
133
|
//#endregion
|
|
81
134
|
export { DocsAIFeatures };
|
package/dist/docs-api.d.mts
CHANGED
|
@@ -38,12 +38,14 @@ interface DocsAPIOptions {
|
|
|
38
38
|
* Create a unified docs API route handler.
|
|
39
39
|
*
|
|
40
40
|
* Returns `{ GET, POST }` for use in a Next.js route handler:
|
|
41
|
-
* - **GET
|
|
42
|
-
* - **
|
|
41
|
+
* - **GET ?query=…** → full-text search
|
|
42
|
+
* - **GET ?format=llms** → llms.txt (concise page listing)
|
|
43
|
+
* - **GET ?format=llms-full** → llms-full.txt (full page content)
|
|
44
|
+
* - **POST** → AI-powered chat with RAG
|
|
43
45
|
*
|
|
44
46
|
* @example
|
|
45
47
|
* ```ts
|
|
46
|
-
* // app/api/docs/route.ts
|
|
48
|
+
* // app/api/docs/route.ts (auto-generated by withDocs)
|
|
47
49
|
* import { createDocsAPI } from "@farming-labs/theme/api";
|
|
48
50
|
* export const { GET, POST } = createDocsAPI();
|
|
49
51
|
* export const revalidate = false;
|
|
@@ -53,10 +55,9 @@ interface DocsAPIOptions {
|
|
|
53
55
|
*/
|
|
54
56
|
declare function createDocsAPI(options?: DocsAPIOptions): {
|
|
55
57
|
/**
|
|
56
|
-
* GET handler — full
|
|
57
|
-
* Query: `?query=search+term`
|
|
58
|
+
* GET handler — search, llms.txt, or llms-full.txt depending on query params.
|
|
58
59
|
*/
|
|
59
|
-
GET
|
|
60
|
+
GET(request: Request): Response | Promise<Response>;
|
|
60
61
|
/**
|
|
61
62
|
* POST handler — AI chat with RAG.
|
|
62
63
|
* Body: `{ messages: [{ role: "user", content: "How do I …?" }] }`
|
package/dist/docs-api.mjs
CHANGED
|
@@ -163,16 +163,64 @@ async function handleAskAI(request, indexes, searchServer, aiConfig) {
|
|
|
163
163
|
Connection: "keep-alive"
|
|
164
164
|
} });
|
|
165
165
|
}
|
|
166
|
+
function readLlmsTxtConfig(root) {
|
|
167
|
+
for (const ext of FILE_EXTS) {
|
|
168
|
+
const configPath = path.join(root, `docs.config.${ext}`);
|
|
169
|
+
if (fs.existsSync(configPath)) try {
|
|
170
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
171
|
+
if (!content.includes("llmsTxt")) return { enabled: false };
|
|
172
|
+
if (/llmsTxt\s*:\s*true/.test(content)) return { enabled: true };
|
|
173
|
+
const enabledMatch = content.match(/llmsTxt\s*:\s*\{[^}]*enabled\s*:\s*(true|false)/s);
|
|
174
|
+
if (enabledMatch && enabledMatch[1] === "false") return { enabled: false };
|
|
175
|
+
const baseUrlMatch = content.match(/llmsTxt\s*:\s*\{[^}]*baseUrl\s*:\s*["']([^"']+)["']/s);
|
|
176
|
+
const siteTitleMatch = content.match(/llmsTxt\s*:\s*\{[^}]*siteTitle\s*:\s*["']([^"']+)["']/s);
|
|
177
|
+
const siteDescMatch = content.match(/llmsTxt\s*:\s*\{[^}]*siteDescription\s*:\s*["']([^"']+)["']/s);
|
|
178
|
+
const navTitleMatch = content.match(/nav\s*:\s*\{[^}]*title\s*:\s*["']([^"']+)["']/s);
|
|
179
|
+
return {
|
|
180
|
+
enabled: true,
|
|
181
|
+
baseUrl: baseUrlMatch?.[1],
|
|
182
|
+
siteTitle: siteTitleMatch?.[1] ?? navTitleMatch?.[1],
|
|
183
|
+
siteDescription: siteDescMatch?.[1]
|
|
184
|
+
};
|
|
185
|
+
} catch {}
|
|
186
|
+
}
|
|
187
|
+
return { enabled: false };
|
|
188
|
+
}
|
|
189
|
+
function generateLlmsTxt(indexes, options) {
|
|
190
|
+
const { siteTitle = "Documentation", siteDescription, baseUrl = "" } = options;
|
|
191
|
+
let llmsTxt = `# ${siteTitle}\n\n`;
|
|
192
|
+
if (siteDescription) llmsTxt += `> ${siteDescription}\n\n`;
|
|
193
|
+
llmsTxt += `## Pages\n\n`;
|
|
194
|
+
for (const page of indexes) {
|
|
195
|
+
llmsTxt += `- [${page.title}](${baseUrl}${page.url})`;
|
|
196
|
+
if (page.description) llmsTxt += `: ${page.description}`;
|
|
197
|
+
llmsTxt += `\n`;
|
|
198
|
+
}
|
|
199
|
+
let llmsFullTxt = `# ${siteTitle}\n\n`;
|
|
200
|
+
if (siteDescription) llmsFullTxt += `> ${siteDescription}\n\n`;
|
|
201
|
+
for (const page of indexes) {
|
|
202
|
+
llmsFullTxt += `## ${page.title}\n\n`;
|
|
203
|
+
llmsFullTxt += `URL: ${baseUrl}${page.url}\n\n`;
|
|
204
|
+
if (page.description) llmsFullTxt += `${page.description}\n\n`;
|
|
205
|
+
llmsFullTxt += `${page.content}\n\n---\n\n`;
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
llmsTxt,
|
|
209
|
+
llmsFullTxt
|
|
210
|
+
};
|
|
211
|
+
}
|
|
166
212
|
/**
|
|
167
213
|
* Create a unified docs API route handler.
|
|
168
214
|
*
|
|
169
215
|
* Returns `{ GET, POST }` for use in a Next.js route handler:
|
|
170
|
-
* - **GET
|
|
171
|
-
* - **
|
|
216
|
+
* - **GET ?query=…** → full-text search
|
|
217
|
+
* - **GET ?format=llms** → llms.txt (concise page listing)
|
|
218
|
+
* - **GET ?format=llms-full** → llms-full.txt (full page content)
|
|
219
|
+
* - **POST** → AI-powered chat with RAG
|
|
172
220
|
*
|
|
173
221
|
* @example
|
|
174
222
|
* ```ts
|
|
175
|
-
* // app/api/docs/route.ts
|
|
223
|
+
* // app/api/docs/route.ts (auto-generated by withDocs)
|
|
176
224
|
* import { createDocsAPI } from "@farming-labs/theme/api";
|
|
177
225
|
* export const { GET, POST } = createDocsAPI();
|
|
178
226
|
* export const revalidate = false;
|
|
@@ -186,13 +234,34 @@ function createDocsAPI(options) {
|
|
|
186
234
|
const docsDir = path.join(root, "app", entry);
|
|
187
235
|
const language = options?.language ?? "english";
|
|
188
236
|
const aiConfig = options?.ai ?? readAIConfig(root);
|
|
237
|
+
const llmsConfig = readLlmsTxtConfig(root);
|
|
189
238
|
const indexes = scanDocsDir(docsDir, entry);
|
|
239
|
+
let _llmsCache = null;
|
|
240
|
+
function getLlmsContent() {
|
|
241
|
+
if (!_llmsCache) _llmsCache = generateLlmsTxt(indexes, {
|
|
242
|
+
siteTitle: llmsConfig.siteTitle ?? "Documentation",
|
|
243
|
+
siteDescription: llmsConfig.siteDescription,
|
|
244
|
+
baseUrl: llmsConfig.baseUrl ?? ""
|
|
245
|
+
});
|
|
246
|
+
return _llmsCache;
|
|
247
|
+
}
|
|
190
248
|
const searchAPI = createSearchAPI("simple", {
|
|
191
249
|
language,
|
|
192
250
|
indexes
|
|
193
251
|
});
|
|
194
252
|
return {
|
|
195
|
-
GET
|
|
253
|
+
GET(request) {
|
|
254
|
+
const format = new URL(request.url).searchParams.get("format");
|
|
255
|
+
if (format === "llms") return new Response(getLlmsContent().llmsTxt, { headers: {
|
|
256
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
257
|
+
"Cache-Control": "public, max-age=3600"
|
|
258
|
+
} });
|
|
259
|
+
if (format === "llms-full") return new Response(getLlmsContent().llmsFullTxt, { headers: {
|
|
260
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
261
|
+
"Cache-Control": "public, max-age=3600"
|
|
262
|
+
} });
|
|
263
|
+
return searchAPI.GET(request);
|
|
264
|
+
},
|
|
196
265
|
async POST(request) {
|
|
197
266
|
if (!aiConfig.enabled) return Response.json({ error: "AI is not enabled. Set `ai: { enabled: true }` in your docs.config to enable it." }, { status: 404 });
|
|
198
267
|
return handleAskAI(request, indexes, searchAPI, aiConfig);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/docs-command-search.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Built-in docs search command palette.
|
|
4
|
+
* Intercepts Cmd+K and sidebar search button to provide an advanced
|
|
5
|
+
* fuzzy-search experience. Styled entirely via omni-* CSS classes
|
|
6
|
+
* so each theme provides its own visual variant.
|
|
7
|
+
*/
|
|
8
|
+
declare function DocsCommandSearch(): any;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { DocsCommandSearch };
|