@farming-labs/theme 0.0.2-beta.17 → 0.0.2-beta.19
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 +16 -1
- package/dist/ai-search-dialog.mjs +90 -1
- package/dist/darkbold/index.d.mts +80 -0
- package/dist/darkbold/index.mjs +84 -0
- package/dist/docs-ai-features.d.mts +1 -1
- package/dist/docs-ai-features.mjs +60 -3
- package/dist/docs-command-search.mjs +2 -1
- package/dist/docs-layout.mjs +43 -5
- package/dist/docs-page-client.d.mts +9 -0
- package/dist/docs-page-client.mjs +62 -14
- package/dist/greentree/index.d.mts +80 -0
- package/dist/greentree/index.mjs +84 -0
- package/dist/page-actions.mjs +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 +20 -2
- package/styles/ai.css +24 -0
- package/styles/base.css +77 -0
- package/styles/darkbold.css +569 -0
- package/styles/darksharp.css +1 -0
- package/styles/greentree.css +708 -0
- package/styles/omni.css +11 -0
- package/styles/shiny.css +488 -0
|
@@ -33,5 +33,20 @@ declare function FloatingAIChat({
|
|
|
33
33
|
aiLabel?: string;
|
|
34
34
|
loadingComponentHtml?: string;
|
|
35
35
|
}): any;
|
|
36
|
+
declare function AIModalDialog({
|
|
37
|
+
open,
|
|
38
|
+
onOpenChange,
|
|
39
|
+
api,
|
|
40
|
+
suggestedQuestions,
|
|
41
|
+
aiLabel,
|
|
42
|
+
loadingComponentHtml
|
|
43
|
+
}: {
|
|
44
|
+
open: boolean;
|
|
45
|
+
onOpenChange: (open: boolean) => void;
|
|
46
|
+
api?: string;
|
|
47
|
+
suggestedQuestions?: string[];
|
|
48
|
+
aiLabel?: string;
|
|
49
|
+
loadingComponentHtml?: string;
|
|
50
|
+
}): any;
|
|
36
51
|
//#endregion
|
|
37
|
-
export { DocsSearchDialog, FloatingAIChat };
|
|
52
|
+
export { AIModalDialog, DocsSearchDialog, FloatingAIChat };
|
|
@@ -932,6 +932,95 @@ function TrashIcon() {
|
|
|
932
932
|
]
|
|
933
933
|
});
|
|
934
934
|
}
|
|
935
|
+
function AIModalDialog({ open, onOpenChange, api = "/api/docs", suggestedQuestions, aiLabel, loadingComponentHtml }) {
|
|
936
|
+
const [messages, setMessages] = useState([]);
|
|
937
|
+
const [aiInput, setAiInput] = useState("");
|
|
938
|
+
const [isStreaming, setIsStreaming] = useState(false);
|
|
939
|
+
useEffect(() => {
|
|
940
|
+
if (!open) return;
|
|
941
|
+
const handler = (e) => {
|
|
942
|
+
if (e.key === "Escape") onOpenChange(false);
|
|
943
|
+
};
|
|
944
|
+
document.addEventListener("keydown", handler);
|
|
945
|
+
return () => document.removeEventListener("keydown", handler);
|
|
946
|
+
}, [open, onOpenChange]);
|
|
947
|
+
useEffect(() => {
|
|
948
|
+
if (open) document.body.style.overflow = "hidden";
|
|
949
|
+
else document.body.style.overflow = "";
|
|
950
|
+
return () => {
|
|
951
|
+
document.body.style.overflow = "";
|
|
952
|
+
};
|
|
953
|
+
}, [open]);
|
|
954
|
+
if (!open) return null;
|
|
955
|
+
const aiName = aiLabel || "AI";
|
|
956
|
+
return createPortal(/* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
957
|
+
onClick: () => onOpenChange(false),
|
|
958
|
+
className: "fd-ai-overlay"
|
|
959
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
960
|
+
role: "dialog",
|
|
961
|
+
"aria-modal": "true",
|
|
962
|
+
onClick: (e) => e.stopPropagation(),
|
|
963
|
+
className: "fd-ai-dialog fd-ai-modal-pure",
|
|
964
|
+
style: {
|
|
965
|
+
left: "50%",
|
|
966
|
+
top: "50%",
|
|
967
|
+
transform: "translate(-50%, -50%)",
|
|
968
|
+
width: "min(680px, calc(100vw - 32px))",
|
|
969
|
+
height: "min(560px, calc(100vh - 64px))",
|
|
970
|
+
animation: "fd-ai-float-center-in 200ms ease-out"
|
|
971
|
+
},
|
|
972
|
+
children: [
|
|
973
|
+
/* @__PURE__ */ jsxs("div", {
|
|
974
|
+
className: "fd-ai-header",
|
|
975
|
+
children: [
|
|
976
|
+
/* @__PURE__ */ jsx(SparklesIcon, { size: 16 }),
|
|
977
|
+
/* @__PURE__ */ jsxs("span", {
|
|
978
|
+
className: "fd-ai-header-title",
|
|
979
|
+
children: ["Ask ", aiName]
|
|
980
|
+
}),
|
|
981
|
+
/* @__PURE__ */ jsx("kbd", {
|
|
982
|
+
className: "fd-ai-esc",
|
|
983
|
+
children: "ESC"
|
|
984
|
+
}),
|
|
985
|
+
/* @__PURE__ */ jsx("button", {
|
|
986
|
+
onClick: () => onOpenChange(false),
|
|
987
|
+
className: "fd-ai-close-btn",
|
|
988
|
+
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
989
|
+
})
|
|
990
|
+
]
|
|
991
|
+
}),
|
|
992
|
+
/* @__PURE__ */ jsx(AIChat, {
|
|
993
|
+
api,
|
|
994
|
+
messages,
|
|
995
|
+
setMessages,
|
|
996
|
+
aiInput,
|
|
997
|
+
setAiInput,
|
|
998
|
+
isStreaming,
|
|
999
|
+
setIsStreaming,
|
|
1000
|
+
suggestedQuestions,
|
|
1001
|
+
aiLabel,
|
|
1002
|
+
loadingComponentHtml
|
|
1003
|
+
}),
|
|
1004
|
+
/* @__PURE__ */ jsx("div", {
|
|
1005
|
+
className: "fd-ai-modal-footer",
|
|
1006
|
+
children: messages.length > 0 ? /* @__PURE__ */ jsxs("button", {
|
|
1007
|
+
className: "fd-ai-fm-clear-btn",
|
|
1008
|
+
onClick: () => {
|
|
1009
|
+
if (!isStreaming) {
|
|
1010
|
+
setMessages([]);
|
|
1011
|
+
setAiInput("");
|
|
1012
|
+
}
|
|
1013
|
+
},
|
|
1014
|
+
"aria-disabled": isStreaming,
|
|
1015
|
+
children: [/* @__PURE__ */ jsx(TrashIcon, {}), /* @__PURE__ */ jsx("span", { children: "Clear chat" })]
|
|
1016
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
1017
|
+
className: "fd-ai-modal-footer-hint",
|
|
1018
|
+
children: "AI can be inaccurate, please verify the information."
|
|
1019
|
+
})
|
|
1020
|
+
})
|
|
1021
|
+
]
|
|
1022
|
+
})] }), document.body);
|
|
1023
|
+
}
|
|
935
1024
|
|
|
936
1025
|
//#endregion
|
|
937
|
-
export { DocsSearchDialog, FloatingAIChat };
|
|
1026
|
+
export { AIModalDialog, DocsSearchDialog, FloatingAIChat };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as _farming_labs_docs0 from "@farming-labs/docs";
|
|
2
|
+
|
|
3
|
+
//#region src/darkbold/index.d.ts
|
|
4
|
+
declare const DarkBoldUIDefaults: {
|
|
5
|
+
colors: {
|
|
6
|
+
primary: string;
|
|
7
|
+
background: string;
|
|
8
|
+
muted: string;
|
|
9
|
+
border: string;
|
|
10
|
+
};
|
|
11
|
+
typography: {
|
|
12
|
+
font: {
|
|
13
|
+
style: {
|
|
14
|
+
sans: string;
|
|
15
|
+
mono: string;
|
|
16
|
+
};
|
|
17
|
+
h1: {
|
|
18
|
+
size: string;
|
|
19
|
+
weight: number;
|
|
20
|
+
lineHeight: string;
|
|
21
|
+
letterSpacing: string;
|
|
22
|
+
};
|
|
23
|
+
h2: {
|
|
24
|
+
size: string;
|
|
25
|
+
weight: number;
|
|
26
|
+
lineHeight: string;
|
|
27
|
+
letterSpacing: string;
|
|
28
|
+
};
|
|
29
|
+
h3: {
|
|
30
|
+
size: string;
|
|
31
|
+
weight: number;
|
|
32
|
+
lineHeight: string;
|
|
33
|
+
letterSpacing: string;
|
|
34
|
+
};
|
|
35
|
+
h4: {
|
|
36
|
+
size: string;
|
|
37
|
+
weight: number;
|
|
38
|
+
lineHeight: string;
|
|
39
|
+
};
|
|
40
|
+
body: {
|
|
41
|
+
size: string;
|
|
42
|
+
weight: number;
|
|
43
|
+
lineHeight: string;
|
|
44
|
+
};
|
|
45
|
+
small: {
|
|
46
|
+
size: string;
|
|
47
|
+
weight: number;
|
|
48
|
+
lineHeight: string;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
layout: {
|
|
53
|
+
contentWidth: number;
|
|
54
|
+
sidebarWidth: number;
|
|
55
|
+
toc: {
|
|
56
|
+
enabled: boolean;
|
|
57
|
+
depth: number;
|
|
58
|
+
style: "default";
|
|
59
|
+
};
|
|
60
|
+
header: {
|
|
61
|
+
height: number;
|
|
62
|
+
sticky: boolean;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
components: {
|
|
66
|
+
Callout: {
|
|
67
|
+
variant: string;
|
|
68
|
+
icon: boolean;
|
|
69
|
+
};
|
|
70
|
+
CodeBlock: {
|
|
71
|
+
showCopyButton: boolean;
|
|
72
|
+
};
|
|
73
|
+
Tabs: {
|
|
74
|
+
style: string;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
declare const darkbold: (overrides?: Partial<_farming_labs_docs0.DocsTheme>) => _farming_labs_docs0.DocsTheme;
|
|
79
|
+
//#endregion
|
|
80
|
+
export { DarkBoldUIDefaults, darkbold };
|
|
@@ -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,7 +2,7 @@ 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;
|
|
@@ -1,18 +1,20 @@
|
|
|
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`.
|
|
@@ -23,6 +25,11 @@ function DocsAIFeatures({ mode, position = "bottom-right", floatingStyle = "pane
|
|
|
23
25
|
aiLabel,
|
|
24
26
|
loadingComponentHtml
|
|
25
27
|
});
|
|
28
|
+
if (mode === "sidebar-icon") return /* @__PURE__ */ jsx(SidebarIconModeAI, {
|
|
29
|
+
suggestedQuestions,
|
|
30
|
+
aiLabel,
|
|
31
|
+
loadingComponentHtml
|
|
32
|
+
});
|
|
26
33
|
return /* @__PURE__ */ jsx(FloatingAIChat, {
|
|
27
34
|
api: "/api/docs",
|
|
28
35
|
position,
|
|
@@ -76,6 +83,56 @@ function SearchModeAI({ suggestedQuestions, aiLabel, loadingComponentHtml }) {
|
|
|
76
83
|
loadingComponentHtml
|
|
77
84
|
});
|
|
78
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Sidebar-icon mode: injects a sparkle icon button next to the search bar
|
|
88
|
+
* in the sidebar header. The search button opens the Cmd+K search dialog,
|
|
89
|
+
* and the AI sparkle button opens a pure AI modal (no search tabs).
|
|
90
|
+
*/
|
|
91
|
+
function SidebarIconModeAI({ suggestedQuestions, aiLabel, loadingComponentHtml }) {
|
|
92
|
+
const [searchOpen, setSearchOpen] = useState(false);
|
|
93
|
+
const [aiOpen, setAiOpen] = useState(false);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
function handler(e) {
|
|
96
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
e.stopPropagation();
|
|
99
|
+
e.stopImmediatePropagation();
|
|
100
|
+
setSearchOpen(true);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
document.addEventListener("keydown", handler, true);
|
|
104
|
+
return () => document.removeEventListener("keydown", handler, true);
|
|
105
|
+
}, []);
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
function onSearch() {
|
|
108
|
+
setSearchOpen(true);
|
|
109
|
+
}
|
|
110
|
+
function onAI() {
|
|
111
|
+
setAiOpen(true);
|
|
112
|
+
}
|
|
113
|
+
window.addEventListener("fd-open-search", onSearch);
|
|
114
|
+
window.addEventListener("fd-open-ai", onAI);
|
|
115
|
+
return () => {
|
|
116
|
+
window.removeEventListener("fd-open-search", onSearch);
|
|
117
|
+
window.removeEventListener("fd-open-ai", onAI);
|
|
118
|
+
};
|
|
119
|
+
}, []);
|
|
120
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(DocsSearchDialog, {
|
|
121
|
+
open: searchOpen,
|
|
122
|
+
onOpenChange: setSearchOpen,
|
|
123
|
+
api: "/api/docs",
|
|
124
|
+
suggestedQuestions,
|
|
125
|
+
aiLabel,
|
|
126
|
+
loadingComponentHtml
|
|
127
|
+
}), /* @__PURE__ */ jsx(AIModalDialog, {
|
|
128
|
+
open: aiOpen,
|
|
129
|
+
onOpenChange: setAiOpen,
|
|
130
|
+
api: "/api/docs",
|
|
131
|
+
suggestedQuestions,
|
|
132
|
+
aiLabel,
|
|
133
|
+
loadingComponentHtml
|
|
134
|
+
})] });
|
|
135
|
+
}
|
|
79
136
|
|
|
80
137
|
//#endregion
|
|
81
138
|
export { DocsAIFeatures };
|
|
@@ -336,7 +336,8 @@ function DocsCommandSearch() {
|
|
|
336
336
|
const button = e.target.closest("button");
|
|
337
337
|
if (!button) return;
|
|
338
338
|
const text = button.textContent || "";
|
|
339
|
-
|
|
339
|
+
const ariaLabel = (button.getAttribute("aria-label") || "").toLowerCase();
|
|
340
|
+
if (text.includes("Search") && (text.includes("⌘") || text.includes("K")) || ariaLabel.includes("search") || text === "Open Search") {
|
|
340
341
|
e.preventDefault();
|
|
341
342
|
e.stopPropagation();
|
|
342
343
|
e.stopImmediatePropagation();
|
package/dist/docs-layout.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { DocsAIFeatures } from "./docs-ai-features.mjs";
|
|
|
2
2
|
import { DocsCommandSearch } from "./docs-command-search.mjs";
|
|
3
3
|
import { serializeIcon } from "./serialize-icon.mjs";
|
|
4
4
|
import { DocsPageClient } from "./docs-page-client.mjs";
|
|
5
|
+
import { SidebarSearchWithAI } from "./sidebar-search-ai.mjs";
|
|
5
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
7
|
import fs from "node:fs";
|
|
7
8
|
import path from "node:path";
|
|
@@ -32,7 +33,7 @@ function hasChildPages(dir) {
|
|
|
32
33
|
}
|
|
33
34
|
return false;
|
|
34
35
|
}
|
|
35
|
-
function buildTree(config) {
|
|
36
|
+
function buildTree(config, flat = false) {
|
|
36
37
|
const docsDir = path.join(process.cwd(), "app", config.entry);
|
|
37
38
|
const icons = config.icons;
|
|
38
39
|
const ordering = config.ordering;
|
|
@@ -68,7 +69,11 @@ function buildTree(config) {
|
|
|
68
69
|
url,
|
|
69
70
|
icon
|
|
70
71
|
},
|
|
71
|
-
children: folderChildren
|
|
72
|
+
children: folderChildren,
|
|
73
|
+
...flat ? {
|
|
74
|
+
collapsible: false,
|
|
75
|
+
defaultOpen: true
|
|
76
|
+
} : {}
|
|
72
77
|
};
|
|
73
78
|
}
|
|
74
79
|
return {
|
|
@@ -227,7 +232,8 @@ function resolveSidebar(sidebar) {
|
|
|
227
232
|
component: sidebar.component,
|
|
228
233
|
footer: sidebar.footer,
|
|
229
234
|
banner: sidebar.banner,
|
|
230
|
-
collapsible: sidebar.collapsible
|
|
235
|
+
collapsible: sidebar.collapsible,
|
|
236
|
+
flat: sidebar.flat
|
|
231
237
|
};
|
|
232
238
|
}
|
|
233
239
|
const COLOR_MAP = {
|
|
@@ -300,6 +306,26 @@ function TypographyStyle({ typography }) {
|
|
|
300
306
|
if (!css) return null;
|
|
301
307
|
return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: css } });
|
|
302
308
|
}
|
|
309
|
+
function LayoutStyle({ layout }) {
|
|
310
|
+
if (!layout) return null;
|
|
311
|
+
const rootVars = [];
|
|
312
|
+
const gridVars = [];
|
|
313
|
+
if (layout.sidebarWidth) {
|
|
314
|
+
const v = `--fd-sidebar-width: ${layout.sidebarWidth}px`;
|
|
315
|
+
rootVars.push(`${v};`);
|
|
316
|
+
gridVars.push(`${v} !important;`);
|
|
317
|
+
}
|
|
318
|
+
if (layout.contentWidth) rootVars.push(`--fd-content-width: ${layout.contentWidth}px;`);
|
|
319
|
+
if (layout.tocWidth) {
|
|
320
|
+
const v = `--fd-toc-width: ${layout.tocWidth}px`;
|
|
321
|
+
rootVars.push(`${v};`);
|
|
322
|
+
gridVars.push(`${v} !important;`);
|
|
323
|
+
}
|
|
324
|
+
if (rootVars.length === 0) return null;
|
|
325
|
+
const parts = [`:root {\n ${rootVars.join("\n ")}\n}`];
|
|
326
|
+
if (gridVars.length > 0) parts.push(`[style*="fd-sidebar-col"] {\n ${gridVars.join("\n ")}\n}`);
|
|
327
|
+
return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: parts.join("\n") } });
|
|
328
|
+
}
|
|
303
329
|
function createDocsLayout(config) {
|
|
304
330
|
const tocConfig = config.theme?.ui?.layout?.toc;
|
|
305
331
|
const tocEnabled = tocConfig?.enabled !== false;
|
|
@@ -309,15 +335,22 @@ function createDocsLayout(config) {
|
|
|
309
335
|
const themeSwitch = resolveThemeSwitch(config.themeToggle);
|
|
310
336
|
const toggleConfig = typeof config.themeToggle === "object" ? config.themeToggle : void 0;
|
|
311
337
|
const forcedTheme = themeSwitch.enabled === false && toggleConfig?.default && toggleConfig.default !== "system" ? toggleConfig.default : void 0;
|
|
312
|
-
const
|
|
338
|
+
const resolvedSidebar = resolveSidebar(config.sidebar);
|
|
339
|
+
const sidebarFlat = resolvedSidebar.flat;
|
|
340
|
+
const { flat: _sidebarFlat, ...sidebarProps } = resolvedSidebar;
|
|
313
341
|
const breadcrumbConfig = config.breadcrumb;
|
|
314
342
|
const breadcrumbEnabled = breadcrumbConfig === void 0 || breadcrumbConfig === true || typeof breadcrumbConfig === "object" && breadcrumbConfig.enabled !== false;
|
|
315
343
|
const colors = config.theme?._userColorOverrides;
|
|
316
344
|
const typography = config.theme?.ui?.typography;
|
|
345
|
+
const layoutDimensions = config.theme?.ui?.layout;
|
|
317
346
|
const pageActions = config.pageActions;
|
|
318
347
|
const copyMarkdownEnabled = resolveBool(pageActions?.copyMarkdown);
|
|
319
348
|
const openDocsEnabled = resolveBool(pageActions?.openDocs);
|
|
320
349
|
const pageActionsPosition = pageActions?.position ?? "below-title";
|
|
350
|
+
const pageActionsAlignment = pageActions?.alignment ?? "left";
|
|
351
|
+
const lastUpdatedRaw = config.lastUpdated;
|
|
352
|
+
const lastUpdatedEnabled = lastUpdatedRaw !== false && (typeof lastUpdatedRaw !== "object" || lastUpdatedRaw.enabled !== false);
|
|
353
|
+
const lastUpdatedPosition = typeof lastUpdatedRaw === "object" ? lastUpdatedRaw.position ?? "footer" : "footer";
|
|
321
354
|
const openDocsProviders = (typeof pageActions?.openDocs === "object" && pageActions.openDocs.providers ? pageActions.openDocs.providers : void 0)?.map((p) => ({
|
|
322
355
|
name: p.name,
|
|
323
356
|
urlTemplate: p.urlTemplate,
|
|
@@ -340,16 +373,18 @@ function createDocsLayout(config) {
|
|
|
340
373
|
const descriptionMap = buildDescriptionMap(config.entry);
|
|
341
374
|
return function DocsLayoutWrapper({ children }) {
|
|
342
375
|
return /* @__PURE__ */ jsxs(DocsLayout, {
|
|
343
|
-
tree: buildTree(config),
|
|
376
|
+
tree: buildTree(config, !!sidebarFlat),
|
|
344
377
|
nav: {
|
|
345
378
|
title: navTitle,
|
|
346
379
|
url: navUrl
|
|
347
380
|
},
|
|
348
381
|
themeSwitch,
|
|
349
382
|
sidebar: sidebarProps,
|
|
383
|
+
...aiMode === "sidebar-icon" && aiEnabled ? { searchToggle: { components: { lg: /* @__PURE__ */ jsx(SidebarSearchWithAI, {}) } } } : {},
|
|
350
384
|
children: [
|
|
351
385
|
/* @__PURE__ */ jsx(ColorStyle, { colors }),
|
|
352
386
|
/* @__PURE__ */ jsx(TypographyStyle, { typography }),
|
|
387
|
+
/* @__PURE__ */ jsx(LayoutStyle, { layout: layoutDimensions }),
|
|
353
388
|
forcedTheme && /* @__PURE__ */ jsx(ForcedThemeScript, { theme: forcedTheme }),
|
|
354
389
|
/* @__PURE__ */ jsx(DocsCommandSearch, {}),
|
|
355
390
|
aiEnabled && /* @__PURE__ */ jsx(DocsAIFeatures, {
|
|
@@ -370,10 +405,13 @@ function createDocsLayout(config) {
|
|
|
370
405
|
openDocs: openDocsEnabled,
|
|
371
406
|
openDocsProviders,
|
|
372
407
|
pageActionsPosition,
|
|
408
|
+
pageActionsAlignment,
|
|
373
409
|
githubUrl,
|
|
374
410
|
githubBranch,
|
|
375
411
|
githubDirectory,
|
|
376
412
|
lastModifiedMap,
|
|
413
|
+
lastUpdatedEnabled,
|
|
414
|
+
lastUpdatedPosition,
|
|
377
415
|
descriptionMap,
|
|
378
416
|
children
|
|
379
417
|
})
|
|
@@ -19,6 +19,8 @@ interface DocsPageClientProps {
|
|
|
19
19
|
openDocsProviders?: SerializedProvider[];
|
|
20
20
|
/** Where to render page actions relative to the title */
|
|
21
21
|
pageActionsPosition?: "above-title" | "below-title";
|
|
22
|
+
/** Horizontal alignment of page action buttons */
|
|
23
|
+
pageActionsAlignment?: "left" | "right";
|
|
22
24
|
/** GitHub repository URL (e.g. "https://github.com/user/repo") */
|
|
23
25
|
githubUrl?: string;
|
|
24
26
|
/** GitHub branch name @default "main" */
|
|
@@ -27,6 +29,10 @@ interface DocsPageClientProps {
|
|
|
27
29
|
githubDirectory?: string;
|
|
28
30
|
/** Map of pathname → formatted last-modified date string */
|
|
29
31
|
lastModifiedMap?: Record<string, string>;
|
|
32
|
+
/** Whether to show "Last updated" at all */
|
|
33
|
+
lastUpdatedEnabled?: boolean;
|
|
34
|
+
/** Where to show the "Last updated" date: "footer" (next to Edit on GitHub) or "below-title" */
|
|
35
|
+
lastUpdatedPosition?: "footer" | "below-title";
|
|
30
36
|
/** Map of pathname → frontmatter description */
|
|
31
37
|
descriptionMap?: Record<string, string>;
|
|
32
38
|
/** Frontmatter description to display below the page title (overrides descriptionMap) */
|
|
@@ -42,10 +48,13 @@ declare function DocsPageClient({
|
|
|
42
48
|
openDocs,
|
|
43
49
|
openDocsProviders,
|
|
44
50
|
pageActionsPosition,
|
|
51
|
+
pageActionsAlignment,
|
|
45
52
|
githubUrl,
|
|
46
53
|
githubBranch,
|
|
47
54
|
githubDirectory,
|
|
48
55
|
lastModifiedMap,
|
|
56
|
+
lastUpdatedEnabled,
|
|
57
|
+
lastUpdatedPosition,
|
|
49
58
|
descriptionMap,
|
|
50
59
|
description,
|
|
51
60
|
children
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { PageActions } from "./page-actions.mjs";
|
|
4
4
|
import { useEffect, useState } from "react";
|
|
5
|
+
import { createPortal } from "react-dom";
|
|
5
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
7
|
import { DocsBody, DocsPage, EditOnGitHub } from "fumadocs-ui/layouts/docs/page";
|
|
7
8
|
import { usePathname, useRouter } from "next/navigation";
|
|
@@ -62,10 +63,11 @@ function buildGithubFileUrl(githubUrl, branch, pathname, directory) {
|
|
|
62
63
|
const segments = pathname.replace(/^\//, "").replace(/\/$/, "");
|
|
63
64
|
return `${githubUrl}/tree/${branch}/${directory ? `${directory}/` : ""}app/${segments}/page.mdx`;
|
|
64
65
|
}
|
|
65
|
-
function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled = true, entry = "docs", copyMarkdown = false, openDocs = false, openDocsProviders, pageActionsPosition = "below-title", githubUrl, githubBranch = "main", githubDirectory, lastModifiedMap, descriptionMap, description, children }) {
|
|
66
|
+
function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled = true, entry = "docs", copyMarkdown = false, openDocs = false, openDocsProviders, pageActionsPosition = "below-title", pageActionsAlignment = "left", githubUrl, githubBranch = "main", githubDirectory, lastModifiedMap, lastUpdatedEnabled = true, lastUpdatedPosition = "footer", descriptionMap, description, children }) {
|
|
66
67
|
const fdTocStyle = tocStyle === "directional" ? "clerk" : void 0;
|
|
67
68
|
const [toc, setToc] = useState([]);
|
|
68
69
|
const pathname = usePathname();
|
|
70
|
+
const [actionsPortalTarget, setActionsPortalTarget] = useState(null);
|
|
69
71
|
const pageDescription = description ?? descriptionMap?.[pathname.replace(/\/$/, "") || "/"];
|
|
70
72
|
useEffect(() => {
|
|
71
73
|
if (!tocEnabled) return;
|
|
@@ -104,8 +106,57 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
104
106
|
const showActions = copyMarkdown || openDocs;
|
|
105
107
|
const githubFileUrl = githubUrl ? buildGithubFileUrl(githubUrl, githubBranch, pathname, githubDirectory) : void 0;
|
|
106
108
|
const normalizedPath = pathname.replace(/\/$/, "") || "/";
|
|
107
|
-
const lastModified = lastModifiedMap?.[normalizedPath];
|
|
108
|
-
const
|
|
109
|
+
const lastModified = lastUpdatedEnabled ? lastModifiedMap?.[normalizedPath] : void 0;
|
|
110
|
+
const showLastUpdatedBelowTitle = !!lastModified && lastUpdatedPosition === "below-title";
|
|
111
|
+
const showLastUpdatedInFooter = !!lastModified && lastUpdatedPosition === "footer";
|
|
112
|
+
const showFooter = !!githubFileUrl || showLastUpdatedInFooter;
|
|
113
|
+
const needsBelowTitleBlock = showLastUpdatedBelowTitle || showActions;
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (!needsBelowTitleBlock) return;
|
|
116
|
+
const timer = requestAnimationFrame(() => {
|
|
117
|
+
const container = document.getElementById("nd-page");
|
|
118
|
+
if (!container) return;
|
|
119
|
+
container.querySelectorAll(".fd-below-title-block").forEach((el) => el.remove());
|
|
120
|
+
const h1 = container.querySelector("h1");
|
|
121
|
+
if (!h1) return;
|
|
122
|
+
let insertAfter = h1;
|
|
123
|
+
const desc = container.querySelector(".fd-page-description");
|
|
124
|
+
if (desc) insertAfter = desc;
|
|
125
|
+
const wrapper = document.createElement("div");
|
|
126
|
+
wrapper.className = "fd-below-title-block not-prose";
|
|
127
|
+
if (showLastUpdatedBelowTitle) {
|
|
128
|
+
const lastUpdatedEl = document.createElement("p");
|
|
129
|
+
lastUpdatedEl.className = "fd-last-updated-inline";
|
|
130
|
+
lastUpdatedEl.textContent = `Last updated ${lastModified}`;
|
|
131
|
+
wrapper.appendChild(lastUpdatedEl);
|
|
132
|
+
}
|
|
133
|
+
if (showLastUpdatedBelowTitle || showActions) {
|
|
134
|
+
const hr = document.createElement("hr");
|
|
135
|
+
hr.className = "fd-title-separator";
|
|
136
|
+
wrapper.appendChild(hr);
|
|
137
|
+
}
|
|
138
|
+
if (showActions) {
|
|
139
|
+
const portalEl = document.createElement("div");
|
|
140
|
+
portalEl.className = "fd-actions-portal";
|
|
141
|
+
portalEl.setAttribute("data-actions-alignment", pageActionsAlignment);
|
|
142
|
+
wrapper.appendChild(portalEl);
|
|
143
|
+
setActionsPortalTarget(portalEl);
|
|
144
|
+
}
|
|
145
|
+
insertAfter.insertAdjacentElement("afterend", wrapper);
|
|
146
|
+
});
|
|
147
|
+
return () => {
|
|
148
|
+
cancelAnimationFrame(timer);
|
|
149
|
+
setActionsPortalTarget(null);
|
|
150
|
+
document.querySelectorAll("#nd-page .fd-below-title-block").forEach((el) => el.remove());
|
|
151
|
+
};
|
|
152
|
+
}, [
|
|
153
|
+
lastModified,
|
|
154
|
+
needsBelowTitleBlock,
|
|
155
|
+
showLastUpdatedBelowTitle,
|
|
156
|
+
showActions,
|
|
157
|
+
pageActionsAlignment,
|
|
158
|
+
pathname
|
|
159
|
+
]);
|
|
109
160
|
return /* @__PURE__ */ jsxs(DocsPage, {
|
|
110
161
|
toc,
|
|
111
162
|
tableOfContent: {
|
|
@@ -122,14 +173,11 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
122
173
|
pathname,
|
|
123
174
|
entry
|
|
124
175
|
}),
|
|
125
|
-
showActions && /* @__PURE__ */ jsx(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
providers: openDocsProviders
|
|
131
|
-
})
|
|
132
|
-
}),
|
|
176
|
+
showActions && actionsPortalTarget && createPortal(/* @__PURE__ */ jsx(PageActions, {
|
|
177
|
+
copyMarkdown,
|
|
178
|
+
openDocs,
|
|
179
|
+
providers: openDocsProviders
|
|
180
|
+
}), actionsPortalTarget),
|
|
133
181
|
/* @__PURE__ */ jsxs(DocsBody, {
|
|
134
182
|
style: {
|
|
135
183
|
display: "flex",
|
|
@@ -140,9 +188,9 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
|
|
|
140
188
|
children
|
|
141
189
|
}), showFooter && /* @__PURE__ */ jsxs("div", {
|
|
142
190
|
className: "not-prose fd-page-footer",
|
|
143
|
-
children: [githubFileUrl && /* @__PURE__ */ jsx(EditOnGitHub, { href: githubFileUrl }), lastModified && /* @__PURE__ */ jsxs("span", {
|
|
144
|
-
className: "fd-last-updated",
|
|
145
|
-
children: ["Last updated
|
|
191
|
+
children: [githubFileUrl && /* @__PURE__ */ jsx(EditOnGitHub, { href: githubFileUrl }), showLastUpdatedInFooter && lastModified && /* @__PURE__ */ jsxs("span", {
|
|
192
|
+
className: "fd-last-updated-footer",
|
|
193
|
+
children: ["Last updated ", lastModified]
|
|
146
194
|
})]
|
|
147
195
|
})]
|
|
148
196
|
})
|