boltdocs 1.0.0
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/CodeBlock-37XMKCYY.mjs +7 -0
- package/dist/PackageManagerTabs-4NWXLXQO.mjs +314 -0
- package/dist/Playground-OE2OE6B6.mjs +7 -0
- package/dist/SearchDialog-FTOQZ763.mjs +187 -0
- package/dist/SearchDialog-ZAZXYIFX.css +2147 -0
- package/dist/Video-I6QY4X7J.mjs +7 -0
- package/dist/chunk-2YRDWM6O.mjs +56 -0
- package/dist/chunk-PN4GCTYG.mjs +67 -0
- package/dist/chunk-X2TDGMTR.mjs +64 -0
- package/dist/chunk-X6BYQHVC.mjs +12 -0
- package/dist/chunk-Z7JHYNAS.mjs +57 -0
- package/dist/chunk-ZFCOLEXN.mjs +1644 -0
- package/dist/client/index.css +2147 -0
- package/dist/client/index.d.mts +298 -0
- package/dist/client/index.d.ts +298 -0
- package/dist/client/index.js +2793 -0
- package/dist/client/index.mjs +63 -0
- package/dist/client/ssr.css +2147 -0
- package/dist/client/ssr.d.mts +25 -0
- package/dist/client/ssr.d.ts +25 -0
- package/dist/client/ssr.js +2727 -0
- package/dist/client/ssr.mjs +32 -0
- package/dist/config-D2XmHJYe.d.mts +122 -0
- package/dist/config-D2XmHJYe.d.ts +122 -0
- package/dist/index-CRQKWAeo.d.mts +82 -0
- package/dist/index-CRQKWAeo.d.ts +82 -0
- package/dist/node/cli/index.d.mts +1 -0
- package/dist/node/cli/index.d.ts +1 -0
- package/dist/node/cli/index.js +199 -0
- package/dist/node/cli/index.mjs +154 -0
- package/dist/node/index.d.mts +79 -0
- package/dist/node/index.d.ts +79 -0
- package/dist/node/index.js +797 -0
- package/dist/node/index.mjs +719 -0
- package/package.json +79 -0
- package/src/client/app/index.tsx +422 -0
- package/src/client/app/preload.tsx +56 -0
- package/src/client/index.ts +40 -0
- package/src/client/ssr.tsx +50 -0
- package/src/client/theme/components/CodeBlock/CodeBlock.tsx +76 -0
- package/src/client/theme/components/CodeBlock/index.ts +1 -0
- package/src/client/theme/components/PackageManagerTabs/PackageManagerTabs.tsx +154 -0
- package/src/client/theme/components/PackageManagerTabs/index.ts +1 -0
- package/src/client/theme/components/PackageManagerTabs/pkg-tabs.css +64 -0
- package/src/client/theme/components/Playground/Playground.tsx +86 -0
- package/src/client/theme/components/Playground/index.ts +1 -0
- package/src/client/theme/components/Playground/playground.css +168 -0
- package/src/client/theme/components/Video/Video.tsx +84 -0
- package/src/client/theme/components/Video/index.ts +1 -0
- package/src/client/theme/components/Video/video.css +41 -0
- package/src/client/theme/components/mdx/Admonition.tsx +80 -0
- package/src/client/theme/components/mdx/Badge.tsx +31 -0
- package/src/client/theme/components/mdx/Button.tsx +50 -0
- package/src/client/theme/components/mdx/Card.tsx +80 -0
- package/src/client/theme/components/mdx/List.tsx +57 -0
- package/src/client/theme/components/mdx/Tabs.tsx +94 -0
- package/src/client/theme/components/mdx/index.ts +18 -0
- package/src/client/theme/components/mdx/mdx-components.css +405 -0
- package/src/client/theme/icons/bun.tsx +62 -0
- package/src/client/theme/icons/deno.tsx +20 -0
- package/src/client/theme/icons/discord.tsx +12 -0
- package/src/client/theme/icons/github.tsx +15 -0
- package/src/client/theme/icons/npm.tsx +13 -0
- package/src/client/theme/icons/pnpm.tsx +72 -0
- package/src/client/theme/icons/twitter.tsx +12 -0
- package/src/client/theme/styles/home.css +60 -0
- package/src/client/theme/styles/markdown.css +343 -0
- package/src/client/theme/styles/variables.css +162 -0
- package/src/client/theme/styles.css +38 -0
- package/src/client/theme/ui/BackgroundGradient/BackgroundGradient.tsx +10 -0
- package/src/client/theme/ui/BackgroundGradient/index.ts +1 -0
- package/src/client/theme/ui/Breadcrumbs/Breadcrumbs.tsx +68 -0
- package/src/client/theme/ui/Breadcrumbs/index.ts +1 -0
- package/src/client/theme/ui/Footer/footer.css +32 -0
- package/src/client/theme/ui/Head/Head.tsx +69 -0
- package/src/client/theme/ui/Head/index.ts +1 -0
- package/src/client/theme/ui/LanguageSwitcher/LanguageSwitcher.tsx +125 -0
- package/src/client/theme/ui/LanguageSwitcher/index.ts +1 -0
- package/src/client/theme/ui/LanguageSwitcher/language-switcher.css +98 -0
- package/src/client/theme/ui/Layout/Layout.tsx +213 -0
- package/src/client/theme/ui/Layout/base.css +76 -0
- package/src/client/theme/ui/Layout/index.ts +2 -0
- package/src/client/theme/ui/Layout/pagination.css +72 -0
- package/src/client/theme/ui/Layout/responsive.css +40 -0
- package/src/client/theme/ui/Link/Link.tsx +202 -0
- package/src/client/theme/ui/Link/index.ts +2 -0
- package/src/client/theme/ui/Loading/Loading.tsx +10 -0
- package/src/client/theme/ui/Loading/index.ts +1 -0
- package/src/client/theme/ui/Loading/loading.css +30 -0
- package/src/client/theme/ui/Navbar/GithubStars.tsx +27 -0
- package/src/client/theme/ui/Navbar/Navbar.tsx +145 -0
- package/src/client/theme/ui/Navbar/index.ts +2 -0
- package/src/client/theme/ui/Navbar/navbar.css +233 -0
- package/src/client/theme/ui/NotFound/NotFound.tsx +20 -0
- package/src/client/theme/ui/NotFound/index.ts +1 -0
- package/src/client/theme/ui/NotFound/not-found.css +64 -0
- package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +192 -0
- package/src/client/theme/ui/OnThisPage/index.ts +1 -0
- package/src/client/theme/ui/OnThisPage/toc.css +132 -0
- package/src/client/theme/ui/PoweredBy/PoweredBy.tsx +18 -0
- package/src/client/theme/ui/PoweredBy/index.ts +1 -0
- package/src/client/theme/ui/PoweredBy/powered-by.css +76 -0
- package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +199 -0
- package/src/client/theme/ui/SearchDialog/index.ts +1 -0
- package/src/client/theme/ui/SearchDialog/search.css +152 -0
- package/src/client/theme/ui/Sidebar/Sidebar.tsx +200 -0
- package/src/client/theme/ui/Sidebar/index.ts +1 -0
- package/src/client/theme/ui/Sidebar/sidebar.css +269 -0
- package/src/client/theme/ui/ThemeToggle/ThemeToggle.tsx +69 -0
- package/src/client/theme/ui/ThemeToggle/index.ts +1 -0
- package/src/client/theme/ui/VersionSwitcher/VersionSwitcher.tsx +136 -0
- package/src/client/theme/ui/VersionSwitcher/index.ts +1 -0
- package/src/client/utils.ts +26 -0
- package/src/node/cache.ts +94 -0
- package/src/node/cli/commands/config.ts +15 -0
- package/src/node/cli/commands/generate-css.ts +24 -0
- package/src/node/cli/constants.ts +70 -0
- package/src/node/cli/index.ts +22 -0
- package/src/node/config.ts +185 -0
- package/src/node/index.ts +21 -0
- package/src/node/mdx.ts +41 -0
- package/src/node/plugin/entry.ts +58 -0
- package/src/node/plugin/html.ts +55 -0
- package/src/node/plugin/index.ts +190 -0
- package/src/node/plugin/types.ts +11 -0
- package/src/node/routes/cache.ts +24 -0
- package/src/node/routes/index.ts +152 -0
- package/src/node/routes/parser.ts +127 -0
- package/src/node/routes/sorter.ts +42 -0
- package/src/node/routes/types.ts +49 -0
- package/src/node/ssg/index.ts +110 -0
- package/src/node/ssg/meta.ts +34 -0
- package/src/node/ssg/options.ts +13 -0
- package/src/node/ssg/sitemap.ts +54 -0
- package/src/node/utils.ts +134 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +22 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { useLocation } from "react-router-dom";
|
|
3
|
+
|
|
4
|
+
interface HeadProps {
|
|
5
|
+
siteTitle: string;
|
|
6
|
+
siteDescription?: string;
|
|
7
|
+
routes: Array<{ path: string; title: string; description?: string }>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Head({ siteTitle, siteDescription, routes }: HeadProps) {
|
|
11
|
+
const location = useLocation();
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
// Find the current route's metadata
|
|
15
|
+
const currentRoute = routes.find((r) => r.path === location.pathname);
|
|
16
|
+
const pageTitle = currentRoute?.title;
|
|
17
|
+
const pageDescription = currentRoute?.description || siteDescription || "";
|
|
18
|
+
|
|
19
|
+
// Update document title
|
|
20
|
+
document.title = pageTitle ? `${pageTitle} | ${siteTitle}` : siteTitle;
|
|
21
|
+
|
|
22
|
+
// Update or create meta description
|
|
23
|
+
let metaDesc = document.querySelector(
|
|
24
|
+
'meta[name="description"]',
|
|
25
|
+
) as HTMLMetaElement | null;
|
|
26
|
+
if (!metaDesc) {
|
|
27
|
+
metaDesc = document.createElement("meta");
|
|
28
|
+
metaDesc.name = "description";
|
|
29
|
+
document.head.appendChild(metaDesc);
|
|
30
|
+
}
|
|
31
|
+
metaDesc.content = pageDescription;
|
|
32
|
+
|
|
33
|
+
// Update OG tags
|
|
34
|
+
setMetaTag("property", "og:title", document.title);
|
|
35
|
+
setMetaTag("property", "og:description", pageDescription);
|
|
36
|
+
setMetaTag("property", "og:type", "article");
|
|
37
|
+
setMetaTag("property", "og:url", window.location.href);
|
|
38
|
+
|
|
39
|
+
// Twitter card
|
|
40
|
+
setMetaTag("name", "twitter:card", "summary");
|
|
41
|
+
setMetaTag("name", "twitter:title", document.title);
|
|
42
|
+
setMetaTag("name", "twitter:description", pageDescription);
|
|
43
|
+
|
|
44
|
+
// Canonical URL
|
|
45
|
+
let canonical = document.querySelector(
|
|
46
|
+
'link[rel="canonical"]',
|
|
47
|
+
) as HTMLLinkElement | null;
|
|
48
|
+
if (!canonical) {
|
|
49
|
+
canonical = document.createElement("link");
|
|
50
|
+
canonical.rel = "canonical";
|
|
51
|
+
document.head.appendChild(canonical);
|
|
52
|
+
}
|
|
53
|
+
canonical.href = window.location.origin + location.pathname;
|
|
54
|
+
}, [location.pathname, siteTitle, siteDescription, routes]);
|
|
55
|
+
|
|
56
|
+
return null; // This component only manages <head>, no visual output
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function setMetaTag(attr: "name" | "property", key: string, content: string) {
|
|
60
|
+
let tag = document.querySelector(
|
|
61
|
+
`meta[${attr}="${key}"]`,
|
|
62
|
+
) as HTMLMetaElement | null;
|
|
63
|
+
if (!tag) {
|
|
64
|
+
tag = document.createElement("meta");
|
|
65
|
+
tag.setAttribute(attr, key);
|
|
66
|
+
document.head.appendChild(tag);
|
|
67
|
+
}
|
|
68
|
+
tag.content = content;
|
|
69
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Head } from "./Head";
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from "react";
|
|
2
|
+
import { Globe, ChevronDown } from "lucide-react";
|
|
3
|
+
import { useNavigate, useLocation } from "react-router-dom";
|
|
4
|
+
import { BoltdocsI18nConfig } from "../../../../node/config";
|
|
5
|
+
import { ComponentRoute } from "../../../app";
|
|
6
|
+
|
|
7
|
+
function getBaseFilePath(
|
|
8
|
+
filePath: string,
|
|
9
|
+
version: string | undefined,
|
|
10
|
+
locale: string | undefined,
|
|
11
|
+
): string {
|
|
12
|
+
let path = filePath;
|
|
13
|
+
if (version && (path === version || path.startsWith(version + "/"))) {
|
|
14
|
+
path = path === version ? "index.md" : path.slice(version.length + 1);
|
|
15
|
+
}
|
|
16
|
+
if (locale && (path === locale || path.startsWith(locale + "/"))) {
|
|
17
|
+
path = path === locale ? "index.md" : path.slice(locale.length + 1);
|
|
18
|
+
}
|
|
19
|
+
return path;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function LanguageSwitcher({
|
|
23
|
+
i18n,
|
|
24
|
+
currentLocale,
|
|
25
|
+
allRoutes,
|
|
26
|
+
}: {
|
|
27
|
+
i18n: BoltdocsI18nConfig;
|
|
28
|
+
currentLocale: string;
|
|
29
|
+
allRoutes: ComponentRoute[];
|
|
30
|
+
}) {
|
|
31
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
32
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
33
|
+
const navigate = useNavigate();
|
|
34
|
+
const location = useLocation();
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
function handleClickOutside(event: MouseEvent) {
|
|
38
|
+
if (
|
|
39
|
+
dropdownRef.current &&
|
|
40
|
+
!dropdownRef.current.contains(event.target as Node)
|
|
41
|
+
) {
|
|
42
|
+
setIsOpen(false);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
46
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
const handleSelect = (locale: string) => {
|
|
50
|
+
setIsOpen(false);
|
|
51
|
+
if (locale === currentLocale) return;
|
|
52
|
+
|
|
53
|
+
const currentRoute = allRoutes.find((r) => r.path === location.pathname);
|
|
54
|
+
let targetPath = "/";
|
|
55
|
+
|
|
56
|
+
if (currentRoute) {
|
|
57
|
+
const baseFile = getBaseFilePath(
|
|
58
|
+
currentRoute.filePath,
|
|
59
|
+
currentRoute.version,
|
|
60
|
+
currentRoute.locale,
|
|
61
|
+
);
|
|
62
|
+
const targetRoute = allRoutes.find(
|
|
63
|
+
(r) =>
|
|
64
|
+
getBaseFilePath(r.filePath, r.version, r.locale) === baseFile &&
|
|
65
|
+
(r.locale || i18n.defaultLocale) === locale &&
|
|
66
|
+
r.version === currentRoute.version,
|
|
67
|
+
);
|
|
68
|
+
if (targetRoute) {
|
|
69
|
+
targetPath = targetRoute.path;
|
|
70
|
+
} else {
|
|
71
|
+
const defaultIndexRoute = allRoutes.find(
|
|
72
|
+
(r) =>
|
|
73
|
+
getBaseFilePath(r.filePath, r.version, r.locale) === "index.md" &&
|
|
74
|
+
(r.locale || i18n.defaultLocale) === locale &&
|
|
75
|
+
r.version === currentRoute.version,
|
|
76
|
+
);
|
|
77
|
+
targetPath = defaultIndexRoute
|
|
78
|
+
? defaultIndexRoute.path
|
|
79
|
+
: locale === i18n.defaultLocale
|
|
80
|
+
? currentRoute.version
|
|
81
|
+
? `/${currentRoute.version}`
|
|
82
|
+
: "/"
|
|
83
|
+
: currentRoute.version
|
|
84
|
+
? `/${currentRoute.version}/${locale}`
|
|
85
|
+
: `/${locale}`;
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
targetPath = locale === i18n.defaultLocale ? "/" : `/${locale}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
navigate(targetPath);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="boltdocs-language-switcher" ref={dropdownRef}>
|
|
96
|
+
<button
|
|
97
|
+
className="language-btn"
|
|
98
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
99
|
+
aria-label="Switch language"
|
|
100
|
+
aria-expanded={isOpen}
|
|
101
|
+
aria-haspopup="listbox"
|
|
102
|
+
>
|
|
103
|
+
<Globe size={18} />
|
|
104
|
+
<span className="language-label">
|
|
105
|
+
{i18n.locales[currentLocale] || currentLocale}
|
|
106
|
+
</span>
|
|
107
|
+
<ChevronDown size={14} />
|
|
108
|
+
</button>
|
|
109
|
+
|
|
110
|
+
{isOpen && (
|
|
111
|
+
<div className="language-dropdown">
|
|
112
|
+
{Object.entries(i18n.locales).map(([key, label]) => (
|
|
113
|
+
<button
|
|
114
|
+
key={key}
|
|
115
|
+
className={`language-option ${key === currentLocale ? "active" : ""}`}
|
|
116
|
+
onClick={() => handleSelect(key)}
|
|
117
|
+
>
|
|
118
|
+
{label}
|
|
119
|
+
</button>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LanguageSwitcher } from "./LanguageSwitcher";
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
.boltdocs-language-switcher {
|
|
2
|
+
position: relative;
|
|
3
|
+
display: flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.language-btn {
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
gap: 0.35rem;
|
|
11
|
+
padding: 0.4rem 0.6rem;
|
|
12
|
+
background: transparent;
|
|
13
|
+
color: var(--ld-text-muted);
|
|
14
|
+
border: none;
|
|
15
|
+
border-radius: var(--ld-radius-md);
|
|
16
|
+
font-size: 0.8125rem;
|
|
17
|
+
font-weight: 500;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
transition:
|
|
20
|
+
background-color 0.2s,
|
|
21
|
+
color 0.2s;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.language-btn svg {
|
|
25
|
+
width: 15px;
|
|
26
|
+
height: 15px;
|
|
27
|
+
opacity: 0.8;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.language-btn:hover {
|
|
31
|
+
background-color: var(--ld-bg-mute);
|
|
32
|
+
color: var(--ld-text-main);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.language-dropdown {
|
|
36
|
+
position: absolute;
|
|
37
|
+
top: 100%;
|
|
38
|
+
right: 0;
|
|
39
|
+
margin-top: 0.35rem;
|
|
40
|
+
min-width: 130px;
|
|
41
|
+
background-color: rgba(15, 15, 24, 0.85); /* Smooth translucent dark match */
|
|
42
|
+
backdrop-filter: blur(12px);
|
|
43
|
+
-webkit-backdrop-filter: blur(12px);
|
|
44
|
+
border: 1px solid var(--ld-border-strong);
|
|
45
|
+
border-radius: var(--ld-radius-md);
|
|
46
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
|
|
47
|
+
padding: 0.35rem;
|
|
48
|
+
z-index: 200;
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-direction: column;
|
|
51
|
+
gap: 0.15rem;
|
|
52
|
+
animation: languageDropdownIn 0.15s ease-out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@keyframes languageDropdownIn {
|
|
56
|
+
from {
|
|
57
|
+
opacity: 0;
|
|
58
|
+
transform: translateY(-4px);
|
|
59
|
+
}
|
|
60
|
+
to {
|
|
61
|
+
opacity: 1;
|
|
62
|
+
transform: translateY(0);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Light mode fallback */
|
|
67
|
+
[data-theme="light"] .language-dropdown,
|
|
68
|
+
.theme-light .language-dropdown {
|
|
69
|
+
background-color: rgba(255, 255, 255, 0.9);
|
|
70
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.language-option {
|
|
74
|
+
display: block;
|
|
75
|
+
width: 100%;
|
|
76
|
+
text-align: left;
|
|
77
|
+
padding: 0.4rem 0.6rem;
|
|
78
|
+
background: transparent;
|
|
79
|
+
border: none;
|
|
80
|
+
border-radius: var(--ld-radius-sm);
|
|
81
|
+
color: var(--ld-text-muted);
|
|
82
|
+
font-size: 0.8125rem;
|
|
83
|
+
font-weight: 500;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
transition:
|
|
86
|
+
background-color 0.2s,
|
|
87
|
+
color 0.2s;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.language-option:hover {
|
|
91
|
+
background-color: var(--ld-bg-mute);
|
|
92
|
+
color: var(--ld-text-main);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.language-option.active {
|
|
96
|
+
background-color: var(--ld-color-primary-muted);
|
|
97
|
+
color: var(--ld-color-primary);
|
|
98
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { useLocation } from "react-router-dom";
|
|
3
|
+
import { Link } from "../Link";
|
|
4
|
+
import { ChevronLeft, ChevronRight, Menu } from "lucide-react";
|
|
5
|
+
import { usePreload } from "../../../app/preload";
|
|
6
|
+
import { BoltdocsConfig } from "../../../../node/config";
|
|
7
|
+
import { ComponentRoute } from "../../../app";
|
|
8
|
+
export { Navbar } from "../Navbar";
|
|
9
|
+
export { Sidebar } from "../Sidebar";
|
|
10
|
+
export { OnThisPage } from "../OnThisPage";
|
|
11
|
+
export { Head } from "../Head";
|
|
12
|
+
export { Breadcrumbs } from "../Breadcrumbs";
|
|
13
|
+
export { BackgroundGradient } from "../BackgroundGradient";
|
|
14
|
+
|
|
15
|
+
import { Navbar } from "../Navbar";
|
|
16
|
+
import { Sidebar } from "../Sidebar";
|
|
17
|
+
import { OnThisPage } from "../OnThisPage";
|
|
18
|
+
import { Head } from "../Head";
|
|
19
|
+
import { Breadcrumbs } from "../Breadcrumbs";
|
|
20
|
+
import { BackgroundGradient } from "../BackgroundGradient";
|
|
21
|
+
import "../../styles.css";
|
|
22
|
+
|
|
23
|
+
export interface ThemeLayoutProps {
|
|
24
|
+
config: BoltdocsConfig;
|
|
25
|
+
routes: ComponentRoute[];
|
|
26
|
+
children: React.ReactNode;
|
|
27
|
+
/** Custom navbar component (slots) */
|
|
28
|
+
navbar?: React.ReactNode;
|
|
29
|
+
/** Custom sidebar component (slots) */
|
|
30
|
+
sidebar?: React.ReactNode;
|
|
31
|
+
/** Custom table of contents (OnThisPage) component (slots) */
|
|
32
|
+
toc?: React.ReactNode;
|
|
33
|
+
/** Custom background component (slots) */
|
|
34
|
+
background?: React.ReactNode;
|
|
35
|
+
/** Custom head/metadata component (slots) */
|
|
36
|
+
head?: React.ReactNode;
|
|
37
|
+
/** Custom breadcrumbs component (slots) */
|
|
38
|
+
breadcrumbs?: React.ReactNode;
|
|
39
|
+
/** Custom class name for the root layout element */
|
|
40
|
+
className?: string;
|
|
41
|
+
/** Custom styles for the root layout element */
|
|
42
|
+
style?: React.CSSProperties;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The main structural layout for documentation pages.
|
|
47
|
+
* Integrates the Navbar, Sidebar, and OnThisPage components into a cohesive shell.
|
|
48
|
+
* It also manages mobile interaction states like the sidebar overlay toggle.
|
|
49
|
+
*
|
|
50
|
+
* @param config - The global Boltdocs configuration object
|
|
51
|
+
* @param routes - The array of available doc routes (used to render the sidebar)
|
|
52
|
+
*/
|
|
53
|
+
export function ThemeLayout({
|
|
54
|
+
config,
|
|
55
|
+
routes,
|
|
56
|
+
children,
|
|
57
|
+
navbar,
|
|
58
|
+
sidebar,
|
|
59
|
+
toc,
|
|
60
|
+
background,
|
|
61
|
+
head,
|
|
62
|
+
breadcrumbs,
|
|
63
|
+
className = "",
|
|
64
|
+
style,
|
|
65
|
+
}: ThemeLayoutProps) {
|
|
66
|
+
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
|
67
|
+
const siteTitle = config.themeConfig?.title || "Boltdocs";
|
|
68
|
+
const siteDescription = config.themeConfig?.description || "";
|
|
69
|
+
const location = useLocation();
|
|
70
|
+
|
|
71
|
+
// Compute prev/next pages and locale
|
|
72
|
+
const currentIndex = routes.findIndex((r) => r.path === location.pathname);
|
|
73
|
+
const currentRoute = routes[currentIndex];
|
|
74
|
+
// Determine current locale (fallback to default)
|
|
75
|
+
const currentLocale = config.i18n
|
|
76
|
+
? currentRoute?.locale || config.i18n.defaultLocale
|
|
77
|
+
: undefined;
|
|
78
|
+
|
|
79
|
+
// Determine current version (fallback to default)
|
|
80
|
+
const currentVersion = config.versions
|
|
81
|
+
? currentRoute?.version || config.versions.defaultVersion
|
|
82
|
+
: undefined;
|
|
83
|
+
|
|
84
|
+
// Filter routes for sidebar, search, and navigation to only ones in the current locale and version
|
|
85
|
+
const filteredRoutes = routes.filter((r) => {
|
|
86
|
+
const localeMatch = config.i18n
|
|
87
|
+
? (r.locale || config.i18n.defaultLocale) === currentLocale
|
|
88
|
+
: true;
|
|
89
|
+
const versionMatch = config.versions
|
|
90
|
+
? (r.version || config.versions.defaultVersion) === currentVersion
|
|
91
|
+
: true;
|
|
92
|
+
return localeMatch && versionMatch;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const localIndex = filteredRoutes.findIndex(
|
|
96
|
+
(r) => r.path === location.pathname,
|
|
97
|
+
);
|
|
98
|
+
const prevPage = localIndex > 0 ? filteredRoutes[localIndex - 1] : null;
|
|
99
|
+
const nextPage =
|
|
100
|
+
localIndex >= 0 && localIndex < filteredRoutes.length - 1
|
|
101
|
+
? filteredRoutes[localIndex + 1]
|
|
102
|
+
: null;
|
|
103
|
+
|
|
104
|
+
const { preload } = usePreload();
|
|
105
|
+
React.useEffect(() => {
|
|
106
|
+
if (prevPage?.path) preload(prevPage.path);
|
|
107
|
+
if (nextPage?.path) preload(nextPage.path);
|
|
108
|
+
}, [prevPage, nextPage, preload]);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div className={`boltdocs-layout ${className}`} style={style}>
|
|
112
|
+
{background !== undefined ? background : <BackgroundGradient />}
|
|
113
|
+
{head !== undefined ? (
|
|
114
|
+
head
|
|
115
|
+
) : (
|
|
116
|
+
<Head
|
|
117
|
+
siteTitle={siteTitle}
|
|
118
|
+
siteDescription={siteDescription}
|
|
119
|
+
routes={routes}
|
|
120
|
+
/>
|
|
121
|
+
)}
|
|
122
|
+
{navbar !== undefined ? (
|
|
123
|
+
navbar
|
|
124
|
+
) : (
|
|
125
|
+
<Navbar
|
|
126
|
+
config={config}
|
|
127
|
+
routes={filteredRoutes}
|
|
128
|
+
allRoutes={routes}
|
|
129
|
+
currentLocale={currentLocale}
|
|
130
|
+
currentVersion={currentVersion}
|
|
131
|
+
/>
|
|
132
|
+
)}
|
|
133
|
+
<div
|
|
134
|
+
className={`boltdocs-main-container ${!isSidebarOpen ? "sidebar-collapsed" : ""}`}
|
|
135
|
+
>
|
|
136
|
+
{sidebar !== undefined ? (
|
|
137
|
+
sidebar
|
|
138
|
+
) : (
|
|
139
|
+
<Sidebar
|
|
140
|
+
routes={filteredRoutes}
|
|
141
|
+
config={config}
|
|
142
|
+
onCollapse={() => setIsSidebarOpen(false)}
|
|
143
|
+
/>
|
|
144
|
+
)}
|
|
145
|
+
|
|
146
|
+
{/* Floating Expand Button when Sidebar is Collapsed */}
|
|
147
|
+
{sidebar === undefined && (
|
|
148
|
+
<button
|
|
149
|
+
className="sidebar-toggle-floating"
|
|
150
|
+
onClick={() => setIsSidebarOpen(true)}
|
|
151
|
+
aria-label="Expand Sidebar"
|
|
152
|
+
title="Expand Sidebar"
|
|
153
|
+
>
|
|
154
|
+
<Menu size={20} />
|
|
155
|
+
</button>
|
|
156
|
+
)}
|
|
157
|
+
|
|
158
|
+
<main className="boltdocs-content">
|
|
159
|
+
{breadcrumbs !== undefined ? (
|
|
160
|
+
breadcrumbs
|
|
161
|
+
) : (
|
|
162
|
+
<Breadcrumbs routes={filteredRoutes} config={config} />
|
|
163
|
+
)}
|
|
164
|
+
<div className="boltdocs-page">{children}</div>
|
|
165
|
+
|
|
166
|
+
{/* Prev / Next Navigation */}
|
|
167
|
+
{(prevPage || nextPage) && (
|
|
168
|
+
<nav className="page-nav" aria-label="Pagination">
|
|
169
|
+
{prevPage ? (
|
|
170
|
+
<Link
|
|
171
|
+
to={prevPage.path || "/"}
|
|
172
|
+
className="page-nav-link page-nav-link--prev"
|
|
173
|
+
>
|
|
174
|
+
<div className="page-nav-info">
|
|
175
|
+
<span className="page-nav-label">Previous</span>
|
|
176
|
+
<span className="page-nav-title">{prevPage.title}</span>
|
|
177
|
+
</div>
|
|
178
|
+
<ChevronLeft className="page-nav-arrow" size={16} />
|
|
179
|
+
</Link>
|
|
180
|
+
) : (
|
|
181
|
+
<span />
|
|
182
|
+
)}
|
|
183
|
+
{nextPage ? (
|
|
184
|
+
<Link
|
|
185
|
+
to={nextPage.path || "/"}
|
|
186
|
+
className="page-nav-link page-nav-link--next"
|
|
187
|
+
>
|
|
188
|
+
<div className="page-nav-info">
|
|
189
|
+
<span className="page-nav-label">Next</span>
|
|
190
|
+
<span className="page-nav-title">{nextPage.title}</span>
|
|
191
|
+
</div>
|
|
192
|
+
<ChevronRight className="page-nav-arrow" size={16} />
|
|
193
|
+
</Link>
|
|
194
|
+
) : (
|
|
195
|
+
<span />
|
|
196
|
+
)}
|
|
197
|
+
</nav>
|
|
198
|
+
)}
|
|
199
|
+
</main>
|
|
200
|
+
{toc !== undefined ? (
|
|
201
|
+
toc
|
|
202
|
+
) : (
|
|
203
|
+
<OnThisPage
|
|
204
|
+
headings={routes[currentIndex]?.headings}
|
|
205
|
+
editLink={config.themeConfig?.editLink}
|
|
206
|
+
communityHelp={config.themeConfig?.communityHelp}
|
|
207
|
+
filePath={routes[currentIndex]?.filePath}
|
|
208
|
+
/>
|
|
209
|
+
)}
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/* ─── Reset ──────────────────────────────────────────────── */
|
|
2
|
+
*,
|
|
3
|
+
*::before,
|
|
4
|
+
*::after {
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
body {
|
|
9
|
+
margin: 0;
|
|
10
|
+
font-family: var(--ld-font-sans);
|
|
11
|
+
background-color: var(--ld-bg-main);
|
|
12
|
+
color: var(--ld-text-main);
|
|
13
|
+
line-height: 1.7;
|
|
14
|
+
-webkit-font-smoothing: antialiased;
|
|
15
|
+
-moz-osx-font-smoothing: grayscale;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
a {
|
|
19
|
+
text-decoration: none;
|
|
20
|
+
|
|
21
|
+
&:hover {
|
|
22
|
+
text-decoration: none;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* ─── Layout Shell ───────────────────────────────────────── */
|
|
27
|
+
.boltdocs-layout {
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
min-height: 100vh;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.boltdocs-main-container {
|
|
34
|
+
display: flex;
|
|
35
|
+
flex: 1;
|
|
36
|
+
width: 100%;
|
|
37
|
+
max-width: 1440px;
|
|
38
|
+
margin: 0 auto;
|
|
39
|
+
padding: 0 1.5rem;
|
|
40
|
+
/* aligns with navbar */
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* ─── Background Glow ────────────────────────────────────── */
|
|
44
|
+
.boltdocs-background-glow {
|
|
45
|
+
position: fixed;
|
|
46
|
+
top: 0;
|
|
47
|
+
left: 0;
|
|
48
|
+
right: 0;
|
|
49
|
+
height: 100vh;
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
z-index: -1;
|
|
52
|
+
pointer-events: none;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.glow-shape {
|
|
56
|
+
position: absolute;
|
|
57
|
+
border-radius: 50%;
|
|
58
|
+
filter: blur(100px);
|
|
59
|
+
opacity: 0.15;
|
|
60
|
+
top: -100px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.glow-1 {
|
|
64
|
+
width: 600px;
|
|
65
|
+
height: 600px;
|
|
66
|
+
background: var(--ld-glow-1-bg);
|
|
67
|
+
left: -200px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.glow-2 {
|
|
71
|
+
width: 800px;
|
|
72
|
+
height: 800px;
|
|
73
|
+
background: var(--ld-glow-2-bg);
|
|
74
|
+
right: -300px;
|
|
75
|
+
top: -200px;
|
|
76
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════════════════
|
|
2
|
+
PREV / NEXT NAVIGATION
|
|
3
|
+
═══════════════════════════════════════════════════════════ */
|
|
4
|
+
.page-nav {
|
|
5
|
+
display: grid;
|
|
6
|
+
grid-template-columns: 1fr 1fr;
|
|
7
|
+
gap: 0.75rem;
|
|
8
|
+
margin-top: 2.5rem;
|
|
9
|
+
padding-top: 1.5rem;
|
|
10
|
+
border-top: 1px solid var(--ld-border-subtle);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.page-nav-link {
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: space-between;
|
|
17
|
+
gap: 0.75rem;
|
|
18
|
+
padding: 0.875rem 1rem;
|
|
19
|
+
border-radius: var(--ld-radius-md);
|
|
20
|
+
border: 1px solid var(--ld-border-subtle);
|
|
21
|
+
text-decoration: none;
|
|
22
|
+
transition: all 0.2s;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.page-nav-link:hover {
|
|
26
|
+
border-color: var(--ld-color-primary);
|
|
27
|
+
background-color: var(--ld-color-primary-muted);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.page-nav-link--prev {
|
|
31
|
+
flex-direction: row-reverse;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.page-nav-link--next {
|
|
35
|
+
text-align: left;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.page-nav-info {
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
gap: 0.15rem;
|
|
42
|
+
min-width: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.page-nav-label {
|
|
46
|
+
font-size: 0.6875rem;
|
|
47
|
+
font-weight: 600;
|
|
48
|
+
text-transform: uppercase;
|
|
49
|
+
letter-spacing: 0.05em;
|
|
50
|
+
color: var(--ld-text-dim);
|
|
51
|
+
display: block;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.page-nav-title {
|
|
55
|
+
font-size: 0.8125rem;
|
|
56
|
+
font-weight: 600;
|
|
57
|
+
color: var(--ld-text-main);
|
|
58
|
+
display: block;
|
|
59
|
+
white-space: nowrap;
|
|
60
|
+
overflow: hidden;
|
|
61
|
+
text-overflow: ellipsis;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.page-nav-arrow {
|
|
65
|
+
color: var(--ld-text-dim);
|
|
66
|
+
flex-shrink: 0;
|
|
67
|
+
transition: color 0.2s;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.page-nav-link:hover .page-nav-arrow {
|
|
71
|
+
color: var(--ld-color-primary);
|
|
72
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════════════════
|
|
2
|
+
RESPONSIVE
|
|
3
|
+
═══════════════════════════════════════════════════════════ */
|
|
4
|
+
@media (max-width: 1100px) {
|
|
5
|
+
.boltdocs-on-this-page {
|
|
6
|
+
display: none;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@media (max-width: 768px) {
|
|
11
|
+
.boltdocs-sidebar {
|
|
12
|
+
display: none;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.sidebar-toggle-floating {
|
|
16
|
+
display: none !important;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.boltdocs-content {
|
|
20
|
+
padding: 1.5rem 1rem 3rem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.hero-title {
|
|
24
|
+
font-size: 2.25rem;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.ld-cards--2,
|
|
28
|
+
.ld-cards--3,
|
|
29
|
+
.ld-cards--4 {
|
|
30
|
+
grid-template-columns: 1fr;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.navbar-search {
|
|
34
|
+
display: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.page-nav {
|
|
38
|
+
flex-direction: column;
|
|
39
|
+
}
|
|
40
|
+
}
|