@nendlabs/docpage 0.1.1 → 0.1.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/cn.js ADDED
@@ -0,0 +1,5 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ export function cn(...inputs) {
4
+ return twMerge(clsx(inputs));
5
+ }
@@ -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("<", "&lt;");
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
+ }
@@ -0,0 +1,252 @@
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
+ 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, className: cn("group inline-flex items-center rounded-md border border-border/80 px-3 py-1.5 text-xs font-semibold uppercase tracking-[0.2em]", "text-foreground transition hover:border-foreground/60 hover:bg-foreground hover:text-background"), "data-docpage-signin": true, children: [signInLabel, _jsx(ArrowRight, { className: "ml-1 h-4 w-4 transition-transform group-hover:translate-x-0.5" })] }))] }) }) }));
194
+ }
195
+ function Brand({ brand }) {
196
+ 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 }));
197
+ if (!brand.href)
198
+ return content;
199
+ return (_jsx("a", { href: brand.href, className: "inline-flex items-center text-foreground", "data-docpage-brand": true, children: content }));
200
+ }
201
+ function Toc({ entries, activeId, title, }) {
202
+ 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) => {
203
+ const isActive = activeId === entry.id;
204
+ return (_jsx("li", { children: _jsx("a", { href: `#${entry.id}`, className: cn("block rounded px-2 py-1 transition-colors", isActive
205
+ ? "font-semibold text-foreground"
206
+ : "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));
207
+ }) })] }));
208
+ }
209
+ function Header({ config }) {
210
+ 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 sm:text-5xl lg:text-6xl", children: config.header.title }), config.header.subtitle ? (_jsx("p", { className: "max-w-2xl text-lg text-muted-foreground", children: config.header.subtitle })) : null] }) }));
211
+ }
212
+ function renderSection(section, tocMap) {
213
+ const title = getSectionTitle(section, tocMap);
214
+ switch (section.kind) {
215
+ case "prose":
216
+ 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));
217
+ case "tabs":
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, 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));
219
+ case "mdx": {
220
+ const components = buildMdxComponents(section);
221
+ 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));
222
+ }
223
+ default:
224
+ return null;
225
+ }
226
+ }
227
+ function Footer({ config }) {
228
+ const footer = config.footer;
229
+ if (!footer)
230
+ return null;
231
+ const year = new Date().getFullYear();
232
+ 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] })] }) }) }));
233
+ }
234
+ export function DocPage({ config, className }) {
235
+ const tocEntries = useMemo(() => deriveToc(config), [config]);
236
+ const tocIds = tocEntries.map((entry) => entry.id);
237
+ const tocTitle = config.tocTitle ?? "Contents";
238
+ const tocMap = useMemo(() => {
239
+ return new Map(tocEntries.map((entry) => [entry.id, entry.label]));
240
+ }, [tocEntries]);
241
+ const tocActiveId = useActiveScrollSectionIds(tocIds, {
242
+ rootMargin: config.options?.tocRootMargin,
243
+ threshold: config.options?.tocThreshold,
244
+ syncToUrl: config.options?.syncTocToUrl ?? true,
245
+ });
246
+ const tocMinWidthPx = config.options?.tocMinWidthPx ?? 768;
247
+ const isWide = useIsWide(tocMinWidthPx);
248
+ const scheme = useColorScheme();
249
+ const themeStyle = themeToStyle(config);
250
+ const mergedStyle = { ...themeStyle, ...(config.style ?? {}) };
251
+ return (_jsxs("div", { className: cn("docpage min-h-[100dvh] 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: "pb-24", children: _jsx(Prose, { children: config.sections.map((section) => renderSection(section, tocMap)) }) })] }) }), _jsx(Footer, { config: config })] }));
252
+ }
@@ -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
+ }
package/dist/index.js CHANGED
@@ -1,832 +1,7 @@
1
- var __create = Object.create;
2
- var __getProtoOf = Object.getPrototypeOf;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __toESM = (mod, isNodeMode, target) => {
7
- target = mod != null ? __create(__getProtoOf(mod)) : {};
8
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
9
- for (let key of __getOwnPropNames(mod))
10
- if (!__hasOwnProp.call(to, key))
11
- __defProp(to, key, {
12
- get: () => mod[key],
13
- enumerable: true
14
- });
15
- return to;
16
- };
17
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
18
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
19
- }) : x)(function(x) {
20
- if (typeof require !== "undefined")
21
- return require.apply(this, arguments);
22
- throw Error('Dynamic require of "' + x + '" is not supported');
23
- });
24
-
25
- // src/cn.ts
26
- import { clsx } from "clsx";
27
- import { twMerge } from "tailwind-merge";
28
- function cn(...inputs) {
29
- return twMerge(clsx(inputs));
30
- }
31
- // src/docpage.tsx
32
- import { ArrowRight } from "lucide-react";
33
- import {
34
- Children,
35
- isValidElement,
36
- useEffect as useEffect5,
37
- useMemo as useMemo4,
38
- useState as useState4
39
- } from "react";
40
- import { MDXProvider } from "@mdx-js/react";
41
-
42
- // src/components/api-tabs.tsx
43
- import { useMemo as useMemo2, useState } from "react";
44
-
45
- // src/components/code-block.tsx
46
- import hljs from "highlight.js/lib/core";
47
- import bash from "highlight.js/lib/languages/bash";
48
- import diff from "highlight.js/lib/languages/diff";
49
- import javascript from "highlight.js/lib/languages/javascript";
50
- import json from "highlight.js/lib/languages/json";
51
- import typescript from "highlight.js/lib/languages/typescript";
52
- import yaml from "highlight.js/lib/languages/yaml";
53
- import { useEffect, useMemo, useRef } from "react";
54
- import { jsxDEV } from "react/jsx-dev-runtime";
55
- var registered = false;
56
- function registerLanguagesOnce() {
57
- if (registered)
58
- return;
59
- hljs.registerLanguage("bash", bash);
60
- hljs.registerLanguage("sh", bash);
61
- hljs.registerLanguage("shell", bash);
62
- hljs.registerLanguage("diff", diff);
63
- hljs.registerLanguage("javascript", javascript);
64
- hljs.registerLanguage("js", javascript);
65
- hljs.registerLanguage("typescript", typescript);
66
- hljs.registerLanguage("ts", typescript);
67
- hljs.registerLanguage("json", json);
68
- hljs.registerLanguage("yaml", yaml);
69
- hljs.registerLanguage("yml", yaml);
70
- registered = true;
71
- }
72
- function CodeBlock({
73
- code,
74
- language,
75
- caption,
76
- className,
77
- preClassName,
78
- codeClassName
79
- }) {
80
- const codeRef = useRef(null);
81
- const languageClass = useMemo(() => {
82
- if (!language)
83
- return;
84
- return language.startsWith("language-") ? language : `language-${language}`;
85
- }, [language]);
86
- useEffect(() => {
87
- registerLanguagesOnce();
88
- const el = codeRef.current;
89
- if (!el)
90
- return;
91
- try {
92
- if (el.dataset.highlighted) {
93
- delete el.dataset.highlighted;
94
- }
95
- hljs.highlightElement(el);
96
- } catch (error) {
97
- if (import.meta.env?.DEV) {
98
- console.error("docpage: highlight.js failed", error);
99
- }
100
- }
101
- }, [code, languageClass]);
102
- return /* @__PURE__ */ jsxDEV("figure", {
103
- className: cn("docpage-code", className),
104
- children: [
105
- /* @__PURE__ */ jsxDEV("pre", {
106
- className: cn("bg-muted/40 overflow-x-auto whitespace-pre-wrap rounded p-3 text-xs leading-6", preClassName),
107
- children: /* @__PURE__ */ jsxDEV("code", {
108
- ref: codeRef,
109
- className: cn("hljs", languageClass, codeClassName),
110
- children: code
111
- }, undefined, false, undefined, this)
112
- }, undefined, false, undefined, this),
113
- caption ? /* @__PURE__ */ jsxDEV("figcaption", {
114
- className: "mt-2 text-xs text-muted-foreground",
115
- children: caption
116
- }, undefined, false, undefined, this) : null
117
- ]
118
- }, undefined, true, undefined, this);
119
- }
120
-
121
- // src/components/api-tabs.tsx
122
- import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
123
- function getInitialTabId(tabs, defaultTabId) {
124
- if (defaultTabId && tabs.some((tab) => tab.id === defaultTabId)) {
125
- return defaultTabId;
126
- }
127
- return tabs[0]?.id ?? "";
128
- }
129
- function getTabLabel(tab) {
130
- if (tab.label)
131
- return tab.label;
132
- return tab.id.toUpperCase();
133
- }
134
- function ApiTabs({ tabs, defaultTabId, className }) {
135
- const [activeId, setActiveId] = useState(() => getInitialTabId(tabs, defaultTabId));
136
- const activeTab = useMemo2(() => {
137
- return tabs.find((tab) => tab.id === activeId) ?? tabs[0];
138
- }, [tabs, activeId]);
139
- if (!activeTab)
140
- return null;
141
- return /* @__PURE__ */ jsxDEV2("div", {
142
- className: cn("docpage-api space-y-3", className),
143
- children: [
144
- /* @__PURE__ */ jsxDEV2("div", {
145
- className: "inline-flex rounded border border-border/80 bg-background/60 p-1",
146
- children: tabs.map((tab) => {
147
- const isActive = activeTab.id === tab.id;
148
- return /* @__PURE__ */ jsxDEV2("button", {
149
- type: "button",
150
- onClick: () => setActiveId(tab.id),
151
- className: cn("rounded px-3 py-1 text-[11px] font-semibold tracking-[0.12em] transition-colors", isActive ? "bg-foreground text-background" : "text-muted-foreground hover:text-foreground"),
152
- "aria-pressed": isActive,
153
- "data-docpage-tab": tab.id,
154
- children: getTabLabel(tab)
155
- }, tab.id, false, undefined, this);
156
- })
157
- }, undefined, false, undefined, this),
158
- /* @__PURE__ */ jsxDEV2(CodeBlock, {
159
- code: activeTab.code,
160
- language: activeTab.language ?? activeTab.id,
161
- preClassName: "text-xs"
162
- }, undefined, false, undefined, this)
163
- ]
164
- }, undefined, true, undefined, this);
165
- }
166
-
167
- // src/components/mermaid.tsx
168
- import { useEffect as useEffect2, useId, useRef as useRef2 } from "react";
169
- import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
170
- function Mermaid({ chart }) {
171
- const ref = useRef2(null);
172
- const ridRaw = useId();
173
- const rid = ridRaw.replace(/:/g, "");
174
- useEffect2(() => {
175
- let cancelled = false;
176
- const mql = typeof window !== "undefined" && window.matchMedia ? window.matchMedia("(prefers-color-scheme: dark)") : null;
177
- async function render() {
178
- try {
179
- const mermaid = (await import("mermaid")).default;
180
- const theme = mql?.matches ? "dark" : "default";
181
- mermaid.initialize({ startOnLoad: false, theme });
182
- const { svg } = await mermaid.render(`mmd-${rid}`, chart);
183
- if (!cancelled && ref.current) {
184
- ref.current.innerHTML = svg;
185
- }
186
- } catch (error) {
187
- if (ref.current) {
188
- const safeChart = chart.replaceAll("<", "&lt;");
189
- ref.current.innerHTML = `<pre class="text-xs p-3 rounded bg-muted/40 overflow-x-auto">${safeChart}</pre>`;
190
- }
191
- if (import.meta.env?.DEV) {
192
- console.error("docpage: failed to render mermaid", error);
193
- }
194
- }
195
- }
196
- render();
197
- const onChange = () => {
198
- if (!cancelled)
199
- render();
200
- };
201
- mql?.addEventListener?.("change", onChange);
202
- return () => {
203
- cancelled = true;
204
- mql?.removeEventListener?.("change", onChange);
205
- };
206
- }, [chart, rid]);
207
- return /* @__PURE__ */ jsxDEV3("div", {
208
- ref,
209
- "data-docpage-mermaid": true
210
- }, undefined, false, undefined, this);
211
- }
212
-
213
- // src/hooks/use-active-scroll-section-ids.tsx
214
- import { useEffect as useEffect3, useMemo as useMemo3, useState as useState2 } from "react";
215
- function getInitialActiveId(ids) {
216
- if (typeof window === "undefined") {
217
- return ids[0] ?? "";
218
- }
219
- const hash = window.location.hash.replace("#", "");
220
- return hash && ids.includes(hash) ? hash : ids[0] ?? "";
221
- }
222
- function useActiveScrollSectionIds(ids, options) {
223
- const memoIds = useMemo3(() => ids.filter(Boolean), [ids]);
224
- const [active, setActive] = useState2(() => getInitialActiveId(memoIds));
225
- useEffect3(() => {
226
- if (memoIds.length === 0)
227
- return;
228
- const observer = new IntersectionObserver((entries) => {
229
- const visible = entries.filter((entry) => entry.isIntersecting).sort((a, b) => a.boundingClientRect.top > b.boundingClientRect.top ? 1 : -1);
230
- if (!visible[0])
231
- return;
232
- const id = visible[0].target.id;
233
- if (!id)
234
- return;
235
- setActive((prev) => prev === id ? prev : id);
236
- }, {
237
- rootMargin: options?.rootMargin ?? "-20% 0px -70% 0px",
238
- threshold: options?.threshold ?? [0, 1]
239
- });
240
- memoIds.forEach((id) => {
241
- const el = document.getElementById(id);
242
- if (el)
243
- observer.observe(el);
244
- });
245
- return () => observer.disconnect();
246
- }, [memoIds, options?.rootMargin, options?.threshold]);
247
- useEffect3(() => {
248
- if (!options?.syncToUrl || !active)
249
- return;
250
- const current = window.location.hash.replace("#", "");
251
- if (current !== active) {
252
- history.replaceState(null, "", `#${active}`);
253
- }
254
- }, [active, options?.syncToUrl]);
255
- return active;
256
- }
257
-
258
- // src/hooks/use-color-scheme.tsx
259
- import { useEffect as useEffect4, useState as useState3 } from "react";
260
- function getInitialScheme() {
261
- if (typeof window === "undefined" || !window.matchMedia)
262
- return "light";
263
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
264
- }
265
- function useColorScheme() {
266
- const [scheme, setScheme] = useState3(() => getInitialScheme());
267
- useEffect4(() => {
268
- if (!window.matchMedia)
269
- return;
270
- const mql = window.matchMedia("(prefers-color-scheme: dark)");
271
- const update = (event) => {
272
- if (event) {
273
- setScheme(event.matches ? "dark" : "light");
274
- return;
275
- }
276
- setScheme(mql.matches ? "dark" : "light");
277
- };
278
- update();
279
- mql.addEventListener?.("change", update);
280
- return () => mql.removeEventListener?.("change", update);
281
- }, []);
282
- return scheme;
283
- }
284
-
285
- // src/docpage.tsx
286
- import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
287
- var TOKEN_VAR_MAP = {
288
- background: "background",
289
- foreground: "foreground",
290
- card: "card",
291
- cardForeground: "card-foreground",
292
- popover: "popover",
293
- popoverForeground: "popover-foreground",
294
- primary: "primary",
295
- primaryForeground: "primary-foreground",
296
- secondary: "secondary",
297
- secondaryForeground: "secondary-foreground",
298
- muted: "muted",
299
- mutedForeground: "muted-foreground",
300
- accent: "accent",
301
- accentForeground: "accent-foreground",
302
- destructive: "destructive",
303
- destructiveForeground: "destructive-foreground",
304
- border: "border",
305
- input: "input",
306
- ring: "ring",
307
- chart1: "chart-1",
308
- chart2: "chart-2",
309
- chart3: "chart-3",
310
- chart4: "chart-4",
311
- chart5: "chart-5",
312
- radius: "radius"
313
- };
314
- function setTokenVars(style, tokens, scheme) {
315
- if (!tokens)
316
- return;
317
- Object.keys(tokens).forEach((key) => {
318
- const value = tokens[key];
319
- if (!value)
320
- return;
321
- const varKey = TOKEN_VAR_MAP[key];
322
- style[`--docpage-${varKey}-${scheme}`] = value;
323
- });
324
- }
325
- function themeToStyle(config) {
326
- const style = {};
327
- setTokenVars(style, config.theme?.light, "light");
328
- setTokenVars(style, config.theme?.dark, "dark");
329
- return style;
330
- }
331
- function startCaseId(id) {
332
- const withSpaces = id.replace(/[-_]/g, " ").trim();
333
- if (!withSpaces)
334
- return id;
335
- return withSpaces.replace(/\b\w/g, (c) => c.toUpperCase());
336
- }
337
- function deriveToc(config) {
338
- if (config.toc && config.toc.length > 0)
339
- return config.toc;
340
- return config.sections.map((section) => {
341
- const labelFromTitle = typeof section.title === "string" ? section.title : undefined;
342
- return {
343
- id: section.id,
344
- label: section.label ?? labelFromTitle ?? startCaseId(section.id)
345
- };
346
- });
347
- }
348
- function useIsWide(minWidthPx) {
349
- const [isWide, setIsWide] = useState4(() => {
350
- if (typeof window === "undefined" || !window.matchMedia)
351
- return true;
352
- return window.matchMedia(`(min-width: ${minWidthPx}px)`).matches;
353
- });
354
- useEffect5(() => {
355
- if (!window.matchMedia)
356
- return;
357
- const mql = window.matchMedia(`(min-width: ${minWidthPx}px)`);
358
- const update = (event) => {
359
- if (event) {
360
- setIsWide(event.matches);
361
- return;
362
- }
363
- setIsWide(mql.matches);
364
- };
365
- update();
366
- mql.addEventListener?.("change", update);
367
- return () => mql.removeEventListener?.("change", update);
368
- }, [minWidthPx]);
369
- return isWide;
370
- }
371
- function Container({ children }) {
372
- return /* @__PURE__ */ jsxDEV4("div", {
373
- className: "mx-auto w-full max-w-6xl px-4 sm:px-6 lg:px-8",
374
- children
375
- }, undefined, false, undefined, this);
376
- }
377
- function Prose({ children }) {
378
- return /* @__PURE__ */ jsxDEV4("div", {
379
- 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"),
380
- children
381
- }, undefined, false, undefined, this);
382
- }
383
- function getCalloutToneClasses(tone) {
384
- switch (tone) {
385
- case "info":
386
- return "border-primary/30 bg-primary/5";
387
- case "warn":
388
- return "border-amber-500/40 bg-amber-500/10";
389
- case "danger":
390
- return "border-destructive/50 bg-destructive/10";
391
- case "neutral":
392
- default:
393
- return "border-border/70 bg-muted/20";
394
- }
395
- }
396
- function renderBlock(block) {
397
- switch (block.kind) {
398
- case "p":
399
- return /* @__PURE__ */ jsxDEV4("p", {
400
- className: "my-4",
401
- children: block.text
402
- }, undefined, false, undefined, this);
403
- case "ul":
404
- return /* @__PURE__ */ jsxDEV4("ul", {
405
- className: "marker:text-muted-foreground list-disc pl-6",
406
- children: block.items.map((item, index) => /* @__PURE__ */ jsxDEV4("li", {
407
- className: "my-2",
408
- children: item
409
- }, index, false, undefined, this))
410
- }, undefined, false, undefined, this);
411
- case "ol":
412
- return /* @__PURE__ */ jsxDEV4("ol", {
413
- className: "marker:text-muted-foreground list-decimal pl-6",
414
- children: block.items.map((item, index) => /* @__PURE__ */ jsxDEV4("li", {
415
- className: "my-2",
416
- children: item
417
- }, index, false, undefined, this))
418
- }, undefined, false, undefined, this);
419
- case "labeled-list":
420
- return /* @__PURE__ */ jsxDEV4("ul", {
421
- className: "marker:text-muted-foreground list-disc pl-6",
422
- children: block.items.map((item, index) => /* @__PURE__ */ jsxDEV4("li", {
423
- className: "my-2",
424
- children: [
425
- /* @__PURE__ */ jsxDEV4("strong", {
426
- children: [
427
- item.label,
428
- ":"
429
- ]
430
- }, undefined, true, undefined, this),
431
- " ",
432
- item.text
433
- ]
434
- }, index, true, undefined, this))
435
- }, undefined, false, undefined, this);
436
- case "code":
437
- return /* @__PURE__ */ jsxDEV4("div", {
438
- className: "my-6",
439
- children: /* @__PURE__ */ jsxDEV4(CodeBlock, {
440
- code: block.code,
441
- language: block.language,
442
- caption: block.caption
443
- }, undefined, false, undefined, this)
444
- }, undefined, false, undefined, this);
445
- case "pre":
446
- return /* @__PURE__ */ jsxDEV4("pre", {
447
- className: "bg-muted/40 my-6 whitespace-pre-wrap rounded p-3 text-xs",
448
- children: block.code
449
- }, undefined, false, undefined, this);
450
- case "mermaid":
451
- return /* @__PURE__ */ jsxDEV4("figure", {
452
- className: "my-6",
453
- children: [
454
- /* @__PURE__ */ jsxDEV4("div", {
455
- className: "bg-background rounded border border-border/80 p-4",
456
- children: /* @__PURE__ */ jsxDEV4(Mermaid, {
457
- chart: block.chart
458
- }, undefined, false, undefined, this)
459
- }, undefined, false, undefined, this),
460
- block.caption ? /* @__PURE__ */ jsxDEV4("figcaption", {
461
- className: "mt-2 text-xs text-muted-foreground",
462
- children: block.caption
463
- }, undefined, false, undefined, this) : null
464
- ]
465
- }, undefined, true, undefined, this);
466
- case "callout":
467
- return /* @__PURE__ */ jsxDEV4("div", {
468
- className: cn("my-6 rounded-lg border p-4", getCalloutToneClasses(block.tone)),
469
- children: [
470
- block.title ? /* @__PURE__ */ jsxDEV4("div", {
471
- className: "text-sm font-semibold text-foreground",
472
- children: block.title
473
- }, undefined, false, undefined, this) : null,
474
- /* @__PURE__ */ jsxDEV4("div", {
475
- className: cn("text-sm leading-relaxed", block.title ? "mt-1" : undefined),
476
- children: block.body
477
- }, undefined, false, undefined, this)
478
- ]
479
- }, undefined, true, undefined, this);
480
- case "node":
481
- return /* @__PURE__ */ jsxDEV4("div", {
482
- className: "my-4",
483
- children: block.node
484
- }, undefined, false, undefined, this);
485
- default:
486
- return null;
487
- }
488
- }
489
- function getLanguageFromClassName(className) {
490
- if (!className)
491
- return;
492
- if (className.startsWith("language-")) {
493
- return className.replace("language-", "");
494
- }
495
- return className;
496
- }
497
- function toCodeString(value) {
498
- if (typeof value === "string")
499
- return value;
500
- if (typeof value === "number" || typeof value === "boolean")
501
- return String(value);
502
- if (value == null)
503
- return "";
504
- return String(value);
505
- }
506
- function MdxCodeBlockPre(props) {
507
- const child = Children.count(props.children) === 1 ? Children.only(props.children) : null;
508
- if (child && isValidElement(child)) {
509
- const codeChild = child;
510
- const language = getLanguageFromClassName(codeChild.props.className);
511
- const code = toCodeString(codeChild.props.children);
512
- return /* @__PURE__ */ jsxDEV4(CodeBlock, {
513
- code,
514
- language
515
- }, undefined, false, undefined, this);
516
- }
517
- return /* @__PURE__ */ jsxDEV4("pre", {
518
- ...props,
519
- className: cn("bg-muted/40 overflow-x-auto whitespace-pre-wrap rounded p-3 text-xs leading-6", props.className)
520
- }, undefined, false, undefined, this);
521
- }
522
- function buildMdxComponents(section) {
523
- const defaults = {
524
- pre: MdxCodeBlockPre
525
- };
526
- return {
527
- ...defaults,
528
- ...section.components ?? {}
529
- };
530
- }
531
- function getSectionTitle(section, tocMap) {
532
- if (section.title)
533
- return section.title;
534
- const fromToc = tocMap.get(section.id);
535
- return fromToc ?? startCaseId(section.id);
536
- }
537
- function SectionHeading({
538
- title,
539
- titleBorder
540
- }) {
541
- return /* @__PURE__ */ jsxDEV4("h2", {
542
- className: cn("mb-8 mt-12 text-2xl font-semibold tracking-tight first:mt-0", titleBorder ? "border-muted border-b pb-2" : "pb-2"),
543
- children: title
544
- }, undefined, false, undefined, this);
545
- }
546
- function Nav({ config }) {
547
- const [scrolled, setScrolled] = useState4(false);
548
- useEffect5(() => {
549
- const onScroll = () => setScrolled(window.scrollY > 8);
550
- onScroll();
551
- window.addEventListener("scroll", onScroll, { passive: true });
552
- return () => window.removeEventListener("scroll", onScroll);
553
- }, []);
554
- const signInLabel = config.nav?.signInLabel ?? "sign in";
555
- const signInHref = config.nav?.signInHref ?? "/login";
556
- return /* @__PURE__ */ jsxDEV4("div", {
557
- className: cn("sticky top-0 z-40 w-full backdrop-blur", scrolled ? "border-b border-border/80 bg-background/80" : "bg-background/60"),
558
- "data-docpage-nav": true,
559
- children: /* @__PURE__ */ jsxDEV4(Container, {
560
- children: /* @__PURE__ */ jsxDEV4("div", {
561
- className: "flex h-14 items-center justify-between",
562
- children: [
563
- /* @__PURE__ */ jsxDEV4(Brand, {
564
- brand: config.brand
565
- }, undefined, false, undefined, this),
566
- config.nav?.rightSlot ? config.nav.rightSlot : /* @__PURE__ */ jsxDEV4("a", {
567
- href: signInHref,
568
- className: cn("group inline-flex items-center rounded-md border border-border/80 px-3 py-1.5 text-xs font-semibold uppercase tracking-[0.2em]", "text-foreground transition hover:border-foreground/60 hover:bg-foreground hover:text-background"),
569
- "data-docpage-signin": true,
570
- children: [
571
- signInLabel,
572
- /* @__PURE__ */ jsxDEV4(ArrowRight, {
573
- className: "ml-1 h-4 w-4 transition-transform group-hover:translate-x-0.5"
574
- }, undefined, false, undefined, this)
575
- ]
576
- }, undefined, true, undefined, this)
577
- ]
578
- }, undefined, true, undefined, this)
579
- }, undefined, false, undefined, this)
580
- }, undefined, false, undefined, this);
581
- }
582
- function Brand({ brand }) {
583
- const content = brand.logo ? /* @__PURE__ */ jsxDEV4("div", {
584
- className: "flex items-center gap-2",
585
- children: brand.logo
586
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV4("span", {
587
- className: "text-sm font-semibold tracking-[0.2em] uppercase",
588
- children: brand.name
589
- }, undefined, false, undefined, this);
590
- if (!brand.href)
591
- return content;
592
- return /* @__PURE__ */ jsxDEV4("a", {
593
- href: brand.href,
594
- className: "inline-flex items-center text-foreground",
595
- "data-docpage-brand": true,
596
- children: content
597
- }, undefined, false, undefined, this);
598
- }
599
- function Toc({
600
- entries,
601
- activeId,
602
- title
603
- }) {
604
- return /* @__PURE__ */ jsxDEV4("nav", {
605
- "aria-label": "Table of contents",
606
- className: "text-sm",
607
- "data-docpage-toc": true,
608
- children: [
609
- /* @__PURE__ */ jsxDEV4("div", {
610
- className: "mb-2 font-medium text-foreground",
611
- children: title
612
- }, undefined, false, undefined, this),
613
- /* @__PURE__ */ jsxDEV4("ol", {
614
- className: "space-y-1",
615
- children: entries.map((entry) => {
616
- const isActive = activeId === entry.id;
617
- return /* @__PURE__ */ jsxDEV4("li", {
618
- children: /* @__PURE__ */ jsxDEV4("a", {
619
- href: `#${entry.id}`,
620
- className: cn("block rounded px-2 py-1 transition-colors", isActive ? "font-semibold text-foreground" : "text-muted-foreground hover:text-foreground"),
621
- onClick: () => history.replaceState(null, "", `#${entry.id}`),
622
- "data-docpage-toc-link": entry.id,
623
- "aria-current": isActive ? "true" : undefined,
624
- children: entry.label
625
- }, undefined, false, undefined, this)
626
- }, entry.id, false, undefined, this);
627
- })
628
- }, undefined, false, undefined, this)
629
- ]
630
- }, undefined, true, undefined, this);
631
- }
632
- function Header({ config }) {
633
- return /* @__PURE__ */ jsxDEV4(Container, {
634
- children: /* @__PURE__ */ jsxDEV4("header", {
635
- className: "py-12",
636
- "data-docpage-header": true,
637
- children: [
638
- config.header.kicker ? /* @__PURE__ */ jsxDEV4("div", {
639
- className: "mb-3 text-xs font-semibold uppercase tracking-[0.3em] text-muted-foreground",
640
- children: config.header.kicker
641
- }, undefined, false, undefined, this) : null,
642
- /* @__PURE__ */ jsxDEV4("h1", {
643
- className: "mb-2 text-4xl font-bold tracking-tight sm:text-5xl lg:text-6xl",
644
- children: config.header.title
645
- }, undefined, false, undefined, this),
646
- config.header.subtitle ? /* @__PURE__ */ jsxDEV4("p", {
647
- className: "max-w-2xl text-lg text-muted-foreground",
648
- children: config.header.subtitle
649
- }, undefined, false, undefined, this) : null
650
- ]
651
- }, undefined, true, undefined, this)
652
- }, undefined, false, undefined, this);
653
- }
654
- function renderSection(section, tocMap) {
655
- const title = getSectionTitle(section, tocMap);
656
- switch (section.kind) {
657
- case "prose":
658
- return /* @__PURE__ */ jsxDEV4("section", {
659
- id: section.id,
660
- className: cn("scroll-mt-24", section.className),
661
- "data-docpage-section": section.id,
662
- children: [
663
- !section.hideTitle ? /* @__PURE__ */ jsxDEV4(SectionHeading, {
664
- title,
665
- titleBorder: section.titleBorder
666
- }, undefined, false, undefined, this) : null,
667
- /* @__PURE__ */ jsxDEV4("div", {
668
- className: "space-y-4",
669
- children: section.blocks.map((block, index) => /* @__PURE__ */ jsxDEV4("div", {
670
- children: renderBlock(block)
671
- }, index, false, undefined, this))
672
- }, undefined, false, undefined, this)
673
- ]
674
- }, section.id, true, undefined, this);
675
- case "tabs":
676
- return /* @__PURE__ */ jsxDEV4("section", {
677
- id: section.id,
678
- className: cn("scroll-mt-24", section.className),
679
- "data-docpage-section": section.id,
680
- children: [
681
- !section.hideTitle ? /* @__PURE__ */ jsxDEV4(SectionHeading, {
682
- title,
683
- titleBorder: section.titleBorder
684
- }, undefined, false, undefined, this) : null,
685
- section.intro ? /* @__PURE__ */ jsxDEV4("p", {
686
- className: "mt-4",
687
- children: section.intro
688
- }, undefined, false, undefined, this) : null,
689
- /* @__PURE__ */ jsxDEV4("div", {
690
- className: "mt-4",
691
- children: /* @__PURE__ */ jsxDEV4(ApiTabs, {
692
- tabs: section.tabs,
693
- defaultTabId: section.defaultTabId
694
- }, undefined, false, undefined, this)
695
- }, undefined, false, undefined, this)
696
- ]
697
- }, section.id, true, undefined, this);
698
- case "mdx": {
699
- const components = buildMdxComponents(section);
700
- return /* @__PURE__ */ jsxDEV4("section", {
701
- id: section.id,
702
- className: cn("scroll-mt-24", section.className),
703
- "data-docpage-section": section.id,
704
- children: [
705
- !section.hideTitle ? /* @__PURE__ */ jsxDEV4(SectionHeading, {
706
- title,
707
- titleBorder: section.titleBorder
708
- }, undefined, false, undefined, this) : null,
709
- /* @__PURE__ */ jsxDEV4("div", {
710
- className: "docpage-mdx",
711
- children: /* @__PURE__ */ jsxDEV4(MDXProvider, {
712
- components,
713
- children: /* @__PURE__ */ jsxDEV4(section.Component, {}, undefined, false, undefined, this)
714
- }, undefined, false, undefined, this)
715
- }, undefined, false, undefined, this)
716
- ]
717
- }, section.id, true, undefined, this);
718
- }
719
- default:
720
- return null;
721
- }
722
- }
723
- function Footer({ config }) {
724
- const footer = config.footer;
725
- if (!footer)
726
- return null;
727
- const year = new Date().getFullYear();
728
- return /* @__PURE__ */ jsxDEV4("footer", {
729
- className: "border-t border-border/80 py-10 text-sm text-muted-foreground",
730
- "data-docpage-footer": true,
731
- children: /* @__PURE__ */ jsxDEV4(Container, {
732
- children: /* @__PURE__ */ jsxDEV4("div", {
733
- className: "flex flex-col items-start justify-between gap-4 md:flex-row md:items-center",
734
- children: [
735
- footer.links && footer.links.length > 0 ? /* @__PURE__ */ jsxDEV4("div", {
736
- className: "flex flex-wrap items-center gap-4",
737
- children: footer.links.map((link) => /* @__PURE__ */ jsxDEV4("a", {
738
- className: "hover:text-foreground",
739
- href: link.href,
740
- children: link.label
741
- }, link.href, false, undefined, this))
742
- }, undefined, false, undefined, this) : null,
743
- /* @__PURE__ */ jsxDEV4("div", {
744
- className: "flex flex-col gap-1 md:items-end",
745
- children: [
746
- /* @__PURE__ */ jsxDEV4("div", {
747
- children: [
748
- "© ",
749
- year,
750
- " ",
751
- footer.copyrightName ?? config.brand.name
752
- ]
753
- }, undefined, true, undefined, this),
754
- footer.note ? /* @__PURE__ */ jsxDEV4("div", {
755
- className: "text-xs",
756
- children: footer.note
757
- }, undefined, false, undefined, this) : null
758
- ]
759
- }, undefined, true, undefined, this)
760
- ]
761
- }, undefined, true, undefined, this)
762
- }, undefined, false, undefined, this)
763
- }, undefined, false, undefined, this);
764
- }
765
- function DocPage({ config, className }) {
766
- const tocEntries = useMemo4(() => deriveToc(config), [config]);
767
- const tocIds = tocEntries.map((entry) => entry.id);
768
- const tocTitle = config.tocTitle ?? "Contents";
769
- const tocMap = useMemo4(() => {
770
- return new Map(tocEntries.map((entry) => [entry.id, entry.label]));
771
- }, [tocEntries]);
772
- const tocActiveId = useActiveScrollSectionIds(tocIds, {
773
- rootMargin: config.options?.tocRootMargin,
774
- threshold: config.options?.tocThreshold,
775
- syncToUrl: config.options?.syncTocToUrl ?? true
776
- });
777
- const tocMinWidthPx = config.options?.tocMinWidthPx ?? 768;
778
- const isWide = useIsWide(tocMinWidthPx);
779
- const scheme = useColorScheme();
780
- const themeStyle = themeToStyle(config);
781
- const mergedStyle = { ...themeStyle, ...config.style ?? {} };
782
- return /* @__PURE__ */ jsxDEV4("div", {
783
- className: cn("docpage min-h-[100dvh] bg-background text-foreground", config.className, className),
784
- style: mergedStyle,
785
- "data-docpage": true,
786
- "data-docpage-theme": scheme,
787
- children: [
788
- /* @__PURE__ */ jsxDEV4(Nav, {
789
- config
790
- }, undefined, false, undefined, this),
791
- /* @__PURE__ */ jsxDEV4(Header, {
792
- config
793
- }, undefined, false, undefined, this),
794
- /* @__PURE__ */ jsxDEV4(Container, {
795
- children: /* @__PURE__ */ jsxDEV4("div", {
796
- className: "grid grid-cols-1 gap-10 md:grid-cols-[260px_minmax(0,1fr)]",
797
- "data-docpage-body": true,
798
- children: [
799
- /* @__PURE__ */ jsxDEV4("aside", {
800
- className: "hidden max-h-[calc(100vh-7rem)] overflow-auto pr-4 md:sticky md:top-24 md:block",
801
- style: isWide ? undefined : { display: "none" },
802
- children: /* @__PURE__ */ jsxDEV4(Toc, {
803
- entries: tocEntries,
804
- activeId: tocActiveId,
805
- title: tocTitle
806
- }, undefined, false, undefined, this)
807
- }, undefined, false, undefined, this),
808
- /* @__PURE__ */ jsxDEV4("main", {
809
- className: "pb-24",
810
- children: /* @__PURE__ */ jsxDEV4(Prose, {
811
- children: config.sections.map((section) => renderSection(section, tocMap))
812
- }, undefined, false, undefined, this)
813
- }, undefined, false, undefined, this)
814
- ]
815
- }, undefined, true, undefined, this)
816
- }, undefined, false, undefined, this),
817
- /* @__PURE__ */ jsxDEV4(Footer, {
818
- config
819
- }, undefined, false, undefined, this)
820
- ]
821
- }, undefined, true, undefined, this);
822
- }
823
- export {
824
- useColorScheme,
825
- useActiveScrollSectionIds,
826
- cn,
827
- Mermaid,
828
- MdxCodeBlockPre,
829
- DocPage,
830
- CodeBlock,
831
- ApiTabs
832
- };
1
+ export { cn } from "./cn";
2
+ export { DocPage, MdxCodeBlockPre } from "./docpage";
3
+ export { ApiTabs } from "./components/api-tabs";
4
+ export { CodeBlock } from "./components/code-block";
5
+ export { Mermaid } from "./components/mermaid";
6
+ export { useActiveScrollSectionIds } from "./hooks/use-active-scroll-section-ids";
7
+ export { useColorScheme } from "./hooks/use-color-scheme";
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nendlabs/docpage",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "an interactive paper-style project page renderer",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -24,8 +24,8 @@
24
24
  "types": "./dist/index.d.ts",
25
25
  "scripts": {
26
26
  "clean": "rm -rf dist",
27
- "build:js": "bun build ./src/index.ts --outdir ./dist --target browser --format esm --external react --external react-dom --packages external",
28
- "build:types": "tsc -p ./tsconfig.build.json",
27
+ "build:js": "tsc -p ./tsconfig.build.js.json",
28
+ "build:types": "tsc -p ./tsconfig.build.types.json",
29
29
  "build:styles": "mkdir -p dist/styles && ./node_modules/.bin/tailwindcss -i ./styles/tailwind.css -o ./dist/styles/docpage.css",
30
30
  "build:docs": "mkdir -p dist && cp docs/package.md dist/package.md",
31
31
  "build": "bun run clean && bun run build:js && bun run build:types && bun run build:styles && bun run build:docs",