@nendlabs/docpage 0.1.1 → 0.1.3
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/cn.js +5 -0
- package/dist/components/api-tabs.js +29 -0
- package/dist/components/code-block.js +53 -0
- package/dist/components/mermaid.js +44 -0
- package/dist/docpage.d.ts.map +1 -1
- package/dist/docpage.js +254 -0
- package/dist/hooks/use-active-scroll-section-ids.js +45 -0
- package/dist/hooks/use-color-scheme.js +27 -0
- package/dist/index.js +7 -832
- package/dist/styles/docpage.css +34 -25
- package/dist/types.d.ts +3 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/package.json +3 -3
- package/styles/docpage-theme.css +4 -1
package/dist/cn.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { cn } from "../cn";
|
|
4
|
+
import { CodeBlock } from "./code-block";
|
|
5
|
+
function getInitialTabId(tabs, defaultTabId) {
|
|
6
|
+
if (defaultTabId && tabs.some((tab) => tab.id === defaultTabId)) {
|
|
7
|
+
return defaultTabId;
|
|
8
|
+
}
|
|
9
|
+
return tabs[0]?.id ?? "";
|
|
10
|
+
}
|
|
11
|
+
function getTabLabel(tab) {
|
|
12
|
+
if (tab.label)
|
|
13
|
+
return tab.label;
|
|
14
|
+
return tab.id.toUpperCase();
|
|
15
|
+
}
|
|
16
|
+
export function ApiTabs({ tabs, defaultTabId, className }) {
|
|
17
|
+
const [activeId, setActiveId] = useState(() => getInitialTabId(tabs, defaultTabId));
|
|
18
|
+
const activeTab = useMemo(() => {
|
|
19
|
+
return tabs.find((tab) => tab.id === activeId) ?? tabs[0];
|
|
20
|
+
}, [tabs, activeId]);
|
|
21
|
+
if (!activeTab)
|
|
22
|
+
return null;
|
|
23
|
+
return (_jsxs("div", { className: cn("docpage-api space-y-3", className), children: [_jsx("div", { className: "inline-flex rounded border border-border/80 bg-background/60 p-1", children: tabs.map((tab) => {
|
|
24
|
+
const isActive = activeTab.id === tab.id;
|
|
25
|
+
return (_jsx("button", { type: "button", onClick: () => setActiveId(tab.id), className: cn("rounded px-3 py-1 text-[11px] font-semibold tracking-[0.12em] transition-colors", isActive
|
|
26
|
+
? "bg-foreground text-background"
|
|
27
|
+
: "text-muted-foreground hover:text-foreground"), "aria-pressed": isActive, "data-docpage-tab": tab.id, children: getTabLabel(tab) }, tab.id));
|
|
28
|
+
}) }), _jsx(CodeBlock, { code: activeTab.code, language: activeTab.language ?? activeTab.id, preClassName: "text-xs" })] }));
|
|
29
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import hljs from "highlight.js/lib/core";
|
|
3
|
+
import bash from "highlight.js/lib/languages/bash";
|
|
4
|
+
import diff from "highlight.js/lib/languages/diff";
|
|
5
|
+
import javascript from "highlight.js/lib/languages/javascript";
|
|
6
|
+
import json from "highlight.js/lib/languages/json";
|
|
7
|
+
import typescript from "highlight.js/lib/languages/typescript";
|
|
8
|
+
import yaml from "highlight.js/lib/languages/yaml";
|
|
9
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
10
|
+
import { cn } from "../cn";
|
|
11
|
+
let registered = false;
|
|
12
|
+
function registerLanguagesOnce() {
|
|
13
|
+
if (registered)
|
|
14
|
+
return;
|
|
15
|
+
hljs.registerLanguage("bash", bash);
|
|
16
|
+
hljs.registerLanguage("sh", bash);
|
|
17
|
+
hljs.registerLanguage("shell", bash);
|
|
18
|
+
hljs.registerLanguage("diff", diff);
|
|
19
|
+
hljs.registerLanguage("javascript", javascript);
|
|
20
|
+
hljs.registerLanguage("js", javascript);
|
|
21
|
+
hljs.registerLanguage("typescript", typescript);
|
|
22
|
+
hljs.registerLanguage("ts", typescript);
|
|
23
|
+
hljs.registerLanguage("json", json);
|
|
24
|
+
hljs.registerLanguage("yaml", yaml);
|
|
25
|
+
hljs.registerLanguage("yml", yaml);
|
|
26
|
+
registered = true;
|
|
27
|
+
}
|
|
28
|
+
export function CodeBlock({ code, language, caption, className, preClassName, codeClassName, }) {
|
|
29
|
+
const codeRef = useRef(null);
|
|
30
|
+
const languageClass = useMemo(() => {
|
|
31
|
+
if (!language)
|
|
32
|
+
return undefined;
|
|
33
|
+
return language.startsWith("language-") ? language : `language-${language}`;
|
|
34
|
+
}, [language]);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
registerLanguagesOnce();
|
|
37
|
+
const el = codeRef.current;
|
|
38
|
+
if (!el)
|
|
39
|
+
return;
|
|
40
|
+
try {
|
|
41
|
+
if (el.dataset.highlighted) {
|
|
42
|
+
delete el.dataset.highlighted;
|
|
43
|
+
}
|
|
44
|
+
hljs.highlightElement(el);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (import.meta.env?.DEV) {
|
|
48
|
+
console.error("docpage: highlight.js failed", error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}, [code, languageClass]);
|
|
52
|
+
return (_jsxs("figure", { className: cn("docpage-code", className), children: [_jsx("pre", { className: cn("bg-muted/40 overflow-x-auto whitespace-pre-wrap rounded p-3 text-xs leading-6", preClassName), children: _jsx("code", { ref: codeRef, className: cn("hljs", languageClass, codeClassName), children: code }) }), caption ? (_jsx("figcaption", { className: "mt-2 text-xs text-muted-foreground", children: caption })) : null] }));
|
|
53
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useRef } from "react";
|
|
3
|
+
export function Mermaid({ chart }) {
|
|
4
|
+
const ref = useRef(null);
|
|
5
|
+
const ridRaw = useId();
|
|
6
|
+
const rid = ridRaw.replace(/:/g, "");
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
let cancelled = false;
|
|
9
|
+
const mql = typeof window !== "undefined" && window.matchMedia
|
|
10
|
+
? window.matchMedia("(prefers-color-scheme: dark)")
|
|
11
|
+
: null;
|
|
12
|
+
async function render() {
|
|
13
|
+
try {
|
|
14
|
+
const mermaid = (await import("mermaid")).default;
|
|
15
|
+
const theme = mql?.matches ? "dark" : "default";
|
|
16
|
+
mermaid.initialize({ startOnLoad: false, theme });
|
|
17
|
+
const { svg } = await mermaid.render(`mmd-${rid}`, chart);
|
|
18
|
+
if (!cancelled && ref.current) {
|
|
19
|
+
ref.current.innerHTML = svg;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (ref.current) {
|
|
24
|
+
const safeChart = chart.replaceAll("<", "<");
|
|
25
|
+
ref.current.innerHTML = `<pre class="text-xs p-3 rounded bg-muted/40 overflow-x-auto">${safeChart}</pre>`;
|
|
26
|
+
}
|
|
27
|
+
if (import.meta.env?.DEV) {
|
|
28
|
+
console.error("docpage: failed to render mermaid", error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
void render();
|
|
33
|
+
const onChange = () => {
|
|
34
|
+
if (!cancelled)
|
|
35
|
+
void render();
|
|
36
|
+
};
|
|
37
|
+
mql?.addEventListener?.("change", onChange);
|
|
38
|
+
return () => {
|
|
39
|
+
cancelled = true;
|
|
40
|
+
mql?.removeEventListener?.("change", onChange);
|
|
41
|
+
};
|
|
42
|
+
}, [chart, rid]);
|
|
43
|
+
return _jsx("div", { ref: ref, "data-docpage-mermaid": true });
|
|
44
|
+
}
|
package/dist/docpage.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docpage.d.ts","sourceRoot":"","sources":["../src/docpage.tsx"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,cAAc,EAEnB,KAAK,SAAS,EAIf,MAAM,OAAO,CAAC;AAUf,OAAO,KAAK,EAKV,YAAY,EAIb,MAAM,SAAS,CAAC;AA6OjB,KAAK,WAAW,GAAG,cAAc,CAAC,cAAc,CAAC,GAAG;IAClD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,CAAC;AAiBF,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,2CAmBjD;
|
|
1
|
+
{"version":3,"file":"docpage.d.ts","sourceRoot":"","sources":["../src/docpage.tsx"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,cAAc,EAEnB,KAAK,SAAS,EAIf,MAAM,OAAO,CAAC;AAUf,OAAO,KAAK,EAKV,YAAY,EAIb,MAAM,SAAS,CAAC;AA6OjB,KAAK,WAAW,GAAG,cAAc,CAAC,cAAc,CAAC,GAAG;IAClD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,CAAC;AAiBF,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,2CAmBjD;AA0PD,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,YAAY,2CAqD1D"}
|
package/dist/docpage.js
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ArrowRight } from "lucide-react";
|
|
3
|
+
import { Children, isValidElement, useEffect, useMemo, useState, } from "react";
|
|
4
|
+
import { MDXProvider } from "@mdx-js/react";
|
|
5
|
+
import { cn } from "./cn";
|
|
6
|
+
import { ApiTabs } from "./components/api-tabs";
|
|
7
|
+
import { CodeBlock } from "./components/code-block";
|
|
8
|
+
import { Mermaid } from "./components/mermaid";
|
|
9
|
+
import { useActiveScrollSectionIds } from "./hooks/use-active-scroll-section-ids";
|
|
10
|
+
import { useColorScheme } from "./hooks/use-color-scheme";
|
|
11
|
+
const TOKEN_VAR_MAP = {
|
|
12
|
+
background: "background",
|
|
13
|
+
foreground: "foreground",
|
|
14
|
+
card: "card",
|
|
15
|
+
cardForeground: "card-foreground",
|
|
16
|
+
popover: "popover",
|
|
17
|
+
popoverForeground: "popover-foreground",
|
|
18
|
+
primary: "primary",
|
|
19
|
+
primaryForeground: "primary-foreground",
|
|
20
|
+
secondary: "secondary",
|
|
21
|
+
secondaryForeground: "secondary-foreground",
|
|
22
|
+
muted: "muted",
|
|
23
|
+
mutedForeground: "muted-foreground",
|
|
24
|
+
accent: "accent",
|
|
25
|
+
accentForeground: "accent-foreground",
|
|
26
|
+
destructive: "destructive",
|
|
27
|
+
destructiveForeground: "destructive-foreground",
|
|
28
|
+
border: "border",
|
|
29
|
+
input: "input",
|
|
30
|
+
ring: "ring",
|
|
31
|
+
chart1: "chart-1",
|
|
32
|
+
chart2: "chart-2",
|
|
33
|
+
chart3: "chart-3",
|
|
34
|
+
chart4: "chart-4",
|
|
35
|
+
chart5: "chart-5",
|
|
36
|
+
radius: "radius",
|
|
37
|
+
};
|
|
38
|
+
function setTokenVars(style, tokens, scheme) {
|
|
39
|
+
if (!tokens)
|
|
40
|
+
return;
|
|
41
|
+
Object.keys(tokens).forEach((key) => {
|
|
42
|
+
const value = tokens[key];
|
|
43
|
+
if (!value)
|
|
44
|
+
return;
|
|
45
|
+
const varKey = TOKEN_VAR_MAP[key];
|
|
46
|
+
style[`--docpage-${varKey}-${scheme}`] = value;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function themeToStyle(config) {
|
|
50
|
+
const style = {};
|
|
51
|
+
setTokenVars(style, config.theme?.light, "light");
|
|
52
|
+
setTokenVars(style, config.theme?.dark, "dark");
|
|
53
|
+
return style;
|
|
54
|
+
}
|
|
55
|
+
function startCaseId(id) {
|
|
56
|
+
const withSpaces = id.replace(/[-_]/g, " ").trim();
|
|
57
|
+
if (!withSpaces)
|
|
58
|
+
return id;
|
|
59
|
+
return withSpaces.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
60
|
+
}
|
|
61
|
+
function deriveToc(config) {
|
|
62
|
+
if (config.toc && config.toc.length > 0)
|
|
63
|
+
return config.toc;
|
|
64
|
+
return config.sections.map((section) => {
|
|
65
|
+
const labelFromTitle = typeof section.title === "string" ? section.title : undefined;
|
|
66
|
+
return {
|
|
67
|
+
id: section.id,
|
|
68
|
+
label: section.label ?? labelFromTitle ?? startCaseId(section.id),
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function useIsWide(minWidthPx) {
|
|
73
|
+
const [isWide, setIsWide] = useState(() => {
|
|
74
|
+
if (typeof window === "undefined" || !window.matchMedia)
|
|
75
|
+
return true;
|
|
76
|
+
return window.matchMedia(`(min-width: ${minWidthPx}px)`).matches;
|
|
77
|
+
});
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!window.matchMedia)
|
|
80
|
+
return;
|
|
81
|
+
const mql = window.matchMedia(`(min-width: ${minWidthPx}px)`);
|
|
82
|
+
const update = (event) => {
|
|
83
|
+
if (event) {
|
|
84
|
+
setIsWide(event.matches);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
setIsWide(mql.matches);
|
|
88
|
+
};
|
|
89
|
+
update();
|
|
90
|
+
mql.addEventListener?.("change", update);
|
|
91
|
+
return () => mql.removeEventListener?.("change", update);
|
|
92
|
+
}, [minWidthPx]);
|
|
93
|
+
return isWide;
|
|
94
|
+
}
|
|
95
|
+
function Container({ children }) {
|
|
96
|
+
return (_jsx("div", { className: "mx-auto w-full max-w-6xl px-4 sm:px-6 lg:px-8", children: children }));
|
|
97
|
+
}
|
|
98
|
+
function Prose({ children }) {
|
|
99
|
+
return (_jsx("div", { className: cn("docpage-prose mx-auto max-w-3xl text-base leading-7 text-foreground/80", "[&>section>h2]:text-foreground [&_p]:text-foreground/80 [&_li]:text-foreground/80", "[&>section>h2]:mb-6 [&>section>h2]:text-2xl [&>section>h2]:font-semibold [&>section>h2]:tracking-tight", "[&>section>p]:my-4 [&_code]:font-mono [&_code]:text-[0.85em]", "[&_pre]:rounded [&_pre]:p-0 [&_pre]:text-xs [&_pre]:leading-6"), children: children }));
|
|
100
|
+
}
|
|
101
|
+
function getCalloutToneClasses(tone) {
|
|
102
|
+
switch (tone) {
|
|
103
|
+
case "info":
|
|
104
|
+
return "border-primary/30 bg-primary/5";
|
|
105
|
+
case "warn":
|
|
106
|
+
return "border-amber-500/40 bg-amber-500/10";
|
|
107
|
+
case "danger":
|
|
108
|
+
return "border-destructive/50 bg-destructive/10";
|
|
109
|
+
case "neutral":
|
|
110
|
+
default:
|
|
111
|
+
return "border-border/70 bg-muted/20";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function renderBlock(block) {
|
|
115
|
+
switch (block.kind) {
|
|
116
|
+
case "p":
|
|
117
|
+
return _jsx("p", { className: "my-4", children: block.text });
|
|
118
|
+
case "ul":
|
|
119
|
+
return (_jsx("ul", { className: "marker:text-muted-foreground list-disc pl-6", children: block.items.map((item, index) => (_jsx("li", { className: "my-2", children: item }, index))) }));
|
|
120
|
+
case "ol":
|
|
121
|
+
return (_jsx("ol", { className: "marker:text-muted-foreground list-decimal pl-6", children: block.items.map((item, index) => (_jsx("li", { className: "my-2", children: item }, index))) }));
|
|
122
|
+
case "labeled-list":
|
|
123
|
+
return (_jsx("ul", { className: "marker:text-muted-foreground list-disc pl-6", children: block.items.map((item, index) => (_jsxs("li", { className: "my-2", children: [_jsxs("strong", { children: [item.label, ":"] }), " ", item.text] }, index))) }));
|
|
124
|
+
case "code":
|
|
125
|
+
return (_jsx("div", { className: "my-6", children: _jsx(CodeBlock, { code: block.code, language: block.language, caption: block.caption }) }));
|
|
126
|
+
case "pre":
|
|
127
|
+
return (_jsx("pre", { className: "bg-muted/40 my-6 whitespace-pre-wrap rounded p-3 text-xs", children: block.code }));
|
|
128
|
+
case "mermaid":
|
|
129
|
+
return (_jsxs("figure", { className: "my-6", children: [_jsx("div", { className: "bg-background rounded border border-border/80 p-4", children: _jsx(Mermaid, { chart: block.chart }) }), block.caption ? (_jsx("figcaption", { className: "mt-2 text-xs text-muted-foreground", children: block.caption })) : null] }));
|
|
130
|
+
case "callout":
|
|
131
|
+
return (_jsxs("div", { className: cn("my-6 rounded-lg border p-4", getCalloutToneClasses(block.tone)), children: [block.title ? (_jsx("div", { className: "text-sm font-semibold text-foreground", children: block.title })) : null, _jsx("div", { className: cn("text-sm leading-relaxed", block.title ? "mt-1" : undefined), children: block.body })] }));
|
|
132
|
+
case "node":
|
|
133
|
+
return _jsx("div", { className: "my-4", children: block.node });
|
|
134
|
+
default:
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function getLanguageFromClassName(className) {
|
|
139
|
+
if (!className)
|
|
140
|
+
return undefined;
|
|
141
|
+
if (className.startsWith("language-")) {
|
|
142
|
+
return className.replace("language-", "");
|
|
143
|
+
}
|
|
144
|
+
return className;
|
|
145
|
+
}
|
|
146
|
+
function toCodeString(value) {
|
|
147
|
+
if (typeof value === "string")
|
|
148
|
+
return value;
|
|
149
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
150
|
+
return String(value);
|
|
151
|
+
if (value == null)
|
|
152
|
+
return "";
|
|
153
|
+
return String(value);
|
|
154
|
+
}
|
|
155
|
+
export function MdxCodeBlockPre(props) {
|
|
156
|
+
const child = Children.count(props.children) === 1 ? Children.only(props.children) : null;
|
|
157
|
+
if (child && isValidElement(child)) {
|
|
158
|
+
const codeChild = child;
|
|
159
|
+
const language = getLanguageFromClassName(codeChild.props.className);
|
|
160
|
+
const code = toCodeString(codeChild.props.children);
|
|
161
|
+
return _jsx(CodeBlock, { code: code, language: language });
|
|
162
|
+
}
|
|
163
|
+
return (_jsx("pre", { ...props, className: cn("bg-muted/40 overflow-x-auto whitespace-pre-wrap rounded p-3 text-xs leading-6", props.className) }));
|
|
164
|
+
}
|
|
165
|
+
function buildMdxComponents(section) {
|
|
166
|
+
const defaults = {
|
|
167
|
+
pre: MdxCodeBlockPre,
|
|
168
|
+
};
|
|
169
|
+
return {
|
|
170
|
+
...defaults,
|
|
171
|
+
...(section.components ?? {}),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function getSectionTitle(section, tocMap) {
|
|
175
|
+
if (section.title)
|
|
176
|
+
return section.title;
|
|
177
|
+
const fromToc = tocMap.get(section.id);
|
|
178
|
+
return fromToc ?? startCaseId(section.id);
|
|
179
|
+
}
|
|
180
|
+
function SectionHeading({ title, titleBorder, }) {
|
|
181
|
+
return (_jsx("h2", { className: cn("mb-8 mt-12 text-2xl font-semibold tracking-tight first:mt-0", titleBorder ? "border-muted border-b pb-2" : "pb-2"), children: title }));
|
|
182
|
+
}
|
|
183
|
+
function Nav({ config }) {
|
|
184
|
+
const [scrolled, setScrolled] = useState(false);
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
const onScroll = () => setScrolled(window.scrollY > 8);
|
|
187
|
+
onScroll();
|
|
188
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
189
|
+
return () => window.removeEventListener("scroll", onScroll);
|
|
190
|
+
}, []);
|
|
191
|
+
const signInLabel = config.nav?.signInLabel ?? "sign in";
|
|
192
|
+
const signInHref = config.nav?.signInHref ?? "/login";
|
|
193
|
+
const signInTarget = config.nav?.signInTarget;
|
|
194
|
+
const signInRel = config.nav?.signInRel ?? (signInTarget === "_blank" ? "noreferrer" : undefined);
|
|
195
|
+
return (_jsx("div", { className: cn("sticky top-0 z-40 w-full backdrop-blur", scrolled ? "border-b border-border/80 bg-background/80" : "bg-background/60"), "data-docpage-nav": true, children: _jsx(Container, { children: _jsxs("div", { className: "flex h-14 items-center justify-between", children: [_jsx(Brand, { brand: config.brand }), config.nav?.rightSlot ? (config.nav.rightSlot) : (_jsxs("a", { href: signInHref, target: signInTarget, rel: signInRel, className: cn("group inline-flex h-8 items-center justify-center gap-2 whitespace-nowrap rounded-md px-3 text-xs font-medium shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", "bg-primary text-primary-foreground hover:bg-primary/90"), "data-docpage-signin": true, children: [signInLabel, _jsx(ArrowRight, { className: "ml-1 h-4 w-4 transition-transform group-hover:translate-x-0.5" })] }))] }) }) }));
|
|
196
|
+
}
|
|
197
|
+
function Brand({ brand }) {
|
|
198
|
+
const content = brand.logo ? (_jsx("div", { className: "flex items-center gap-2", children: brand.logo })) : (_jsx("span", { className: "text-sm font-semibold tracking-[0.2em] uppercase", children: brand.name }));
|
|
199
|
+
if (!brand.href)
|
|
200
|
+
return content;
|
|
201
|
+
return (_jsx("a", { href: brand.href, className: "inline-flex items-center text-foreground", "data-docpage-brand": true, children: content }));
|
|
202
|
+
}
|
|
203
|
+
function Toc({ entries, activeId, title, }) {
|
|
204
|
+
return (_jsxs("nav", { "aria-label": "Table of contents", className: "text-sm", "data-docpage-toc": true, children: [_jsx("div", { className: "mb-2 font-medium text-foreground", children: title }), _jsx("ol", { className: "space-y-1", children: entries.map((entry) => {
|
|
205
|
+
const isActive = activeId === entry.id;
|
|
206
|
+
return (_jsx("li", { children: _jsx("a", { href: `#${entry.id}`, className: cn("block rounded px-2 py-1 transition-colors", isActive
|
|
207
|
+
? "font-semibold text-foreground"
|
|
208
|
+
: "text-muted-foreground hover:text-foreground"), onClick: () => history.replaceState(null, "", `#${entry.id}`), "data-docpage-toc-link": entry.id, "aria-current": isActive ? "true" : undefined, children: entry.label }) }, entry.id));
|
|
209
|
+
}) })] }));
|
|
210
|
+
}
|
|
211
|
+
function Header({ config }) {
|
|
212
|
+
return (_jsx(Container, { children: _jsxs("header", { className: "py-12", "data-docpage-header": true, children: [config.header.kicker ? (_jsx("div", { className: "mb-3 text-xs font-semibold uppercase tracking-[0.3em] text-muted-foreground", children: config.header.kicker })) : null, _jsx("h1", { className: "mb-2 text-4xl font-bold tracking-tight", children: config.header.title }), config.header.subtitle ? (_jsx("p", { className: "max-w-2xl text-lg text-muted-foreground", children: config.header.subtitle })) : null] }) }));
|
|
213
|
+
}
|
|
214
|
+
function renderSection(section, tocMap) {
|
|
215
|
+
const title = getSectionTitle(section, tocMap);
|
|
216
|
+
switch (section.kind) {
|
|
217
|
+
case "prose":
|
|
218
|
+
return (_jsxs("section", { id: section.id, className: cn("scroll-mt-24", section.className), "data-docpage-section": section.id, children: [!section.hideTitle ? (_jsx(SectionHeading, { title: title, titleBorder: section.titleBorder })) : null, _jsx("div", { className: "space-y-4", children: section.blocks.map((block, index) => _jsx("div", { children: renderBlock(block) }, index)) })] }, section.id));
|
|
219
|
+
case "tabs":
|
|
220
|
+
return (_jsxs("section", { id: section.id, className: cn("scroll-mt-24", section.className), "data-docpage-section": section.id, children: [!section.hideTitle ? (_jsx(SectionHeading, { title: title, titleBorder: section.titleBorder })) : null, section.intro ? _jsx("p", { className: "mt-4", children: section.intro }) : null, _jsx("div", { className: "mt-4", children: _jsx(ApiTabs, { tabs: section.tabs, defaultTabId: section.defaultTabId }) })] }, section.id));
|
|
221
|
+
case "mdx": {
|
|
222
|
+
const components = buildMdxComponents(section);
|
|
223
|
+
return (_jsxs("section", { id: section.id, className: cn("scroll-mt-24", section.className), "data-docpage-section": section.id, children: [!section.hideTitle ? (_jsx(SectionHeading, { title: title, titleBorder: section.titleBorder })) : null, _jsx("div", { className: "docpage-mdx", children: _jsx(MDXProvider, { components: components, children: _jsx(section.Component, {}) }) })] }, section.id));
|
|
224
|
+
}
|
|
225
|
+
default:
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function Footer({ config }) {
|
|
230
|
+
const footer = config.footer;
|
|
231
|
+
if (!footer)
|
|
232
|
+
return null;
|
|
233
|
+
const year = new Date().getFullYear();
|
|
234
|
+
return (_jsx("footer", { className: "border-t border-border/80 py-10 text-sm text-muted-foreground", "data-docpage-footer": true, children: _jsx(Container, { children: _jsxs("div", { className: "flex flex-col items-start justify-between gap-4 md:flex-row md:items-center", children: [footer.links && footer.links.length > 0 ? (_jsx("div", { className: "flex flex-wrap items-center gap-4", children: footer.links.map((link) => (_jsx("a", { className: "hover:text-foreground", href: link.href, children: link.label }, link.href))) })) : null, _jsxs("div", { className: "flex flex-col gap-1 md:items-end", children: [_jsxs("div", { children: ["\u00A9 ", year, " ", footer.copyrightName ?? config.brand.name] }), footer.note ? _jsx("div", { className: "text-xs", children: footer.note }) : null] })] }) }) }));
|
|
235
|
+
}
|
|
236
|
+
export function DocPage({ config, className }) {
|
|
237
|
+
const tocEntries = useMemo(() => deriveToc(config), [config]);
|
|
238
|
+
const tocIds = tocEntries.map((entry) => entry.id);
|
|
239
|
+
const tocTitle = config.tocTitle ?? "Contents";
|
|
240
|
+
const tocMap = useMemo(() => {
|
|
241
|
+
return new Map(tocEntries.map((entry) => [entry.id, entry.label]));
|
|
242
|
+
}, [tocEntries]);
|
|
243
|
+
const tocActiveId = useActiveScrollSectionIds(tocIds, {
|
|
244
|
+
rootMargin: config.options?.tocRootMargin,
|
|
245
|
+
threshold: config.options?.tocThreshold,
|
|
246
|
+
syncToUrl: config.options?.syncTocToUrl ?? true,
|
|
247
|
+
});
|
|
248
|
+
const tocMinWidthPx = config.options?.tocMinWidthPx ?? 768;
|
|
249
|
+
const isWide = useIsWide(tocMinWidthPx);
|
|
250
|
+
const scheme = useColorScheme();
|
|
251
|
+
const themeStyle = themeToStyle(config);
|
|
252
|
+
const mergedStyle = { ...themeStyle, ...(config.style ?? {}) };
|
|
253
|
+
return (_jsxs("div", { className: cn("docpage min-h-[100dvh] overflow-x-hidden bg-background text-foreground", config.className, className), style: mergedStyle, "data-docpage": true, "data-docpage-theme": scheme, children: [_jsx(Nav, { config: config }), _jsx(Header, { config: config }), _jsx(Container, { children: _jsxs("div", { className: "grid grid-cols-1 gap-10 md:grid-cols-[260px_minmax(0,1fr)]", "data-docpage-body": true, children: [_jsx("aside", { className: "hidden max-h-[calc(100vh-7rem)] overflow-auto pr-4 md:sticky md:top-24 md:block", style: isWide ? undefined : { display: "none" }, children: _jsx(Toc, { entries: tocEntries, activeId: tocActiveId, title: tocTitle }) }), _jsx("main", { className: "min-w-0 pb-24", children: _jsx(Prose, { children: config.sections.map((section) => renderSection(section, tocMap)) }) })] }) }), _jsx(Footer, { config: config })] }));
|
|
254
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
function getInitialActiveId(ids) {
|
|
3
|
+
if (typeof window === "undefined") {
|
|
4
|
+
return ids[0] ?? "";
|
|
5
|
+
}
|
|
6
|
+
const hash = window.location.hash.replace("#", "");
|
|
7
|
+
return hash && ids.includes(hash) ? hash : ids[0] ?? "";
|
|
8
|
+
}
|
|
9
|
+
export function useActiveScrollSectionIds(ids, options) {
|
|
10
|
+
const memoIds = useMemo(() => ids.filter(Boolean), [ids]);
|
|
11
|
+
const [active, setActive] = useState(() => getInitialActiveId(memoIds));
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (memoIds.length === 0)
|
|
14
|
+
return;
|
|
15
|
+
const observer = new IntersectionObserver((entries) => {
|
|
16
|
+
const visible = entries
|
|
17
|
+
.filter((entry) => entry.isIntersecting)
|
|
18
|
+
.sort((a, b) => a.boundingClientRect.top > b.boundingClientRect.top ? 1 : -1);
|
|
19
|
+
if (!visible[0])
|
|
20
|
+
return;
|
|
21
|
+
const id = visible[0].target.id;
|
|
22
|
+
if (!id)
|
|
23
|
+
return;
|
|
24
|
+
setActive((prev) => (prev === id ? prev : id));
|
|
25
|
+
}, {
|
|
26
|
+
rootMargin: options?.rootMargin ?? "-20% 0px -70% 0px",
|
|
27
|
+
threshold: options?.threshold ?? [0, 1],
|
|
28
|
+
});
|
|
29
|
+
memoIds.forEach((id) => {
|
|
30
|
+
const el = document.getElementById(id);
|
|
31
|
+
if (el)
|
|
32
|
+
observer.observe(el);
|
|
33
|
+
});
|
|
34
|
+
return () => observer.disconnect();
|
|
35
|
+
}, [memoIds, options?.rootMargin, options?.threshold]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!options?.syncToUrl || !active)
|
|
38
|
+
return;
|
|
39
|
+
const current = window.location.hash.replace("#", "");
|
|
40
|
+
if (current !== active) {
|
|
41
|
+
history.replaceState(null, "", `#${active}`);
|
|
42
|
+
}
|
|
43
|
+
}, [active, options?.syncToUrl]);
|
|
44
|
+
return active;
|
|
45
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
function getInitialScheme() {
|
|
3
|
+
if (typeof window === "undefined" || !window.matchMedia)
|
|
4
|
+
return "light";
|
|
5
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
6
|
+
? "dark"
|
|
7
|
+
: "light";
|
|
8
|
+
}
|
|
9
|
+
export function useColorScheme() {
|
|
10
|
+
const [scheme, setScheme] = useState(() => getInitialScheme());
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!window.matchMedia)
|
|
13
|
+
return;
|
|
14
|
+
const mql = window.matchMedia("(prefers-color-scheme: dark)");
|
|
15
|
+
const update = (event) => {
|
|
16
|
+
if (event) {
|
|
17
|
+
setScheme(event.matches ? "dark" : "light");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
setScheme(mql.matches ? "dark" : "light");
|
|
21
|
+
};
|
|
22
|
+
update();
|
|
23
|
+
mql.addEventListener?.("change", update);
|
|
24
|
+
return () => mql.removeEventListener?.("change", update);
|
|
25
|
+
}, []);
|
|
26
|
+
return scheme;
|
|
27
|
+
}
|