@farming-labs/theme 0.0.2-beta.18 → 0.0.2-beta.20
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-layout.mjs +49 -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/sidebar-search-ai.d.mts +11 -0
- package/dist/sidebar-search-ai.mjs +128 -0
- package/package.json +14 -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
|
@@ -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 };
|
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,32 @@ 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 desktopRootVars = [];
|
|
313
|
+
const desktopGridVars = [];
|
|
314
|
+
if (layout.sidebarWidth) {
|
|
315
|
+
const v = `--fd-sidebar-width: ${layout.sidebarWidth}px`;
|
|
316
|
+
desktopRootVars.push(`${v};`);
|
|
317
|
+
desktopGridVars.push(`${v} !important;`);
|
|
318
|
+
}
|
|
319
|
+
if (layout.contentWidth) rootVars.push(`--fd-content-width: ${layout.contentWidth}px;`);
|
|
320
|
+
if (layout.tocWidth) {
|
|
321
|
+
const v = `--fd-toc-width: ${layout.tocWidth}px`;
|
|
322
|
+
desktopRootVars.push(`${v};`);
|
|
323
|
+
desktopGridVars.push(`${v} !important;`);
|
|
324
|
+
}
|
|
325
|
+
if (rootVars.length === 0 && desktopRootVars.length === 0) return null;
|
|
326
|
+
const parts = [];
|
|
327
|
+
if (rootVars.length > 0) parts.push(`:root {\n ${rootVars.join("\n ")}\n}`);
|
|
328
|
+
if (desktopRootVars.length > 0) {
|
|
329
|
+
const inner = [`:root {\n ${desktopRootVars.join("\n ")}\n }`];
|
|
330
|
+
if (desktopGridVars.length > 0) inner.push(`[style*="fd-sidebar-col"] {\n ${desktopGridVars.join("\n ")}\n }`);
|
|
331
|
+
parts.push(`@media (min-width: 1024px) {\n ${inner.join("\n ")}\n}`);
|
|
332
|
+
}
|
|
333
|
+
return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: parts.join("\n") } });
|
|
334
|
+
}
|
|
303
335
|
function createDocsLayout(config) {
|
|
304
336
|
const tocConfig = config.theme?.ui?.layout?.toc;
|
|
305
337
|
const tocEnabled = tocConfig?.enabled !== false;
|
|
@@ -309,15 +341,22 @@ function createDocsLayout(config) {
|
|
|
309
341
|
const themeSwitch = resolveThemeSwitch(config.themeToggle);
|
|
310
342
|
const toggleConfig = typeof config.themeToggle === "object" ? config.themeToggle : void 0;
|
|
311
343
|
const forcedTheme = themeSwitch.enabled === false && toggleConfig?.default && toggleConfig.default !== "system" ? toggleConfig.default : void 0;
|
|
312
|
-
const
|
|
344
|
+
const resolvedSidebar = resolveSidebar(config.sidebar);
|
|
345
|
+
const sidebarFlat = resolvedSidebar.flat;
|
|
346
|
+
const { flat: _sidebarFlat, ...sidebarProps } = resolvedSidebar;
|
|
313
347
|
const breadcrumbConfig = config.breadcrumb;
|
|
314
348
|
const breadcrumbEnabled = breadcrumbConfig === void 0 || breadcrumbConfig === true || typeof breadcrumbConfig === "object" && breadcrumbConfig.enabled !== false;
|
|
315
349
|
const colors = config.theme?._userColorOverrides;
|
|
316
350
|
const typography = config.theme?.ui?.typography;
|
|
351
|
+
const layoutDimensions = config.theme?.ui?.layout;
|
|
317
352
|
const pageActions = config.pageActions;
|
|
318
353
|
const copyMarkdownEnabled = resolveBool(pageActions?.copyMarkdown);
|
|
319
354
|
const openDocsEnabled = resolveBool(pageActions?.openDocs);
|
|
320
355
|
const pageActionsPosition = pageActions?.position ?? "below-title";
|
|
356
|
+
const pageActionsAlignment = pageActions?.alignment ?? "left";
|
|
357
|
+
const lastUpdatedRaw = config.lastUpdated;
|
|
358
|
+
const lastUpdatedEnabled = lastUpdatedRaw !== false && (typeof lastUpdatedRaw !== "object" || lastUpdatedRaw.enabled !== false);
|
|
359
|
+
const lastUpdatedPosition = typeof lastUpdatedRaw === "object" ? lastUpdatedRaw.position ?? "footer" : "footer";
|
|
321
360
|
const openDocsProviders = (typeof pageActions?.openDocs === "object" && pageActions.openDocs.providers ? pageActions.openDocs.providers : void 0)?.map((p) => ({
|
|
322
361
|
name: p.name,
|
|
323
362
|
urlTemplate: p.urlTemplate,
|
|
@@ -340,16 +379,18 @@ function createDocsLayout(config) {
|
|
|
340
379
|
const descriptionMap = buildDescriptionMap(config.entry);
|
|
341
380
|
return function DocsLayoutWrapper({ children }) {
|
|
342
381
|
return /* @__PURE__ */ jsxs(DocsLayout, {
|
|
343
|
-
tree: buildTree(config),
|
|
382
|
+
tree: buildTree(config, !!sidebarFlat),
|
|
344
383
|
nav: {
|
|
345
384
|
title: navTitle,
|
|
346
385
|
url: navUrl
|
|
347
386
|
},
|
|
348
387
|
themeSwitch,
|
|
349
388
|
sidebar: sidebarProps,
|
|
389
|
+
...aiMode === "sidebar-icon" && aiEnabled ? { searchToggle: { components: { lg: /* @__PURE__ */ jsx(SidebarSearchWithAI, {}) } } } : {},
|
|
350
390
|
children: [
|
|
351
391
|
/* @__PURE__ */ jsx(ColorStyle, { colors }),
|
|
352
392
|
/* @__PURE__ */ jsx(TypographyStyle, { typography }),
|
|
393
|
+
/* @__PURE__ */ jsx(LayoutStyle, { layout: layoutDimensions }),
|
|
353
394
|
forcedTheme && /* @__PURE__ */ jsx(ForcedThemeScript, { theme: forcedTheme }),
|
|
354
395
|
/* @__PURE__ */ jsx(DocsCommandSearch, {}),
|
|
355
396
|
aiEnabled && /* @__PURE__ */ jsx(DocsAIFeatures, {
|
|
@@ -370,10 +411,13 @@ function createDocsLayout(config) {
|
|
|
370
411
|
openDocs: openDocsEnabled,
|
|
371
412
|
openDocsProviders,
|
|
372
413
|
pageActionsPosition,
|
|
414
|
+
pageActionsAlignment,
|
|
373
415
|
githubUrl,
|
|
374
416
|
githubBranch,
|
|
375
417
|
githubDirectory,
|
|
376
418
|
lastModifiedMap,
|
|
419
|
+
lastUpdatedEnabled,
|
|
420
|
+
lastUpdatedPosition,
|
|
377
421
|
descriptionMap,
|
|
378
422
|
children
|
|
379
423
|
})
|
|
@@ -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
|
})
|