boltdocs 1.3.0 → 1.3.1
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/node/index.js +21 -6
- package/dist/node/index.mjs +21 -6
- package/package.json +1 -1
- package/src/client/app/index.tsx +344 -344
- package/src/client/app/preload.tsx +56 -56
- package/src/client/index.ts +40 -40
- package/src/client/ssr.tsx +51 -51
- package/src/client/theme/components/CodeBlock/CodeBlock.tsx +76 -76
- package/src/client/theme/components/CodeBlock/index.ts +1 -1
- package/src/client/theme/components/PackageManagerTabs/PackageManagerTabs.tsx +154 -154
- package/src/client/theme/components/PackageManagerTabs/index.ts +1 -1
- package/src/client/theme/components/PackageManagerTabs/pkg-tabs.css +64 -64
- package/src/client/theme/components/Playground/Playground.tsx +124 -124
- package/src/client/theme/components/Playground/index.ts +1 -1
- package/src/client/theme/components/Playground/playground.css +168 -168
- package/src/client/theme/components/Video/Video.tsx +84 -84
- package/src/client/theme/components/Video/index.ts +1 -1
- package/src/client/theme/components/Video/video.css +41 -41
- package/src/client/theme/components/mdx/Admonition.tsx +80 -80
- package/src/client/theme/components/mdx/Badge.tsx +31 -31
- package/src/client/theme/components/mdx/Button.tsx +50 -50
- package/src/client/theme/components/mdx/Card.tsx +80 -80
- package/src/client/theme/components/mdx/List.tsx +57 -57
- package/src/client/theme/components/mdx/Tabs.tsx +94 -94
- package/src/client/theme/components/mdx/index.ts +18 -18
- package/src/client/theme/components/mdx/mdx-components.css +424 -424
- package/src/client/theme/icons/bun.tsx +62 -62
- package/src/client/theme/icons/deno.tsx +20 -20
- package/src/client/theme/icons/discord.tsx +12 -12
- package/src/client/theme/icons/github.tsx +15 -15
- package/src/client/theme/icons/npm.tsx +13 -13
- package/src/client/theme/icons/pnpm.tsx +72 -72
- package/src/client/theme/icons/twitter.tsx +12 -12
- package/src/client/theme/styles/markdown.css +343 -343
- package/src/client/theme/styles/variables.css +162 -162
- package/src/client/theme/styles.css +37 -37
- package/src/client/theme/ui/BackgroundGradient/BackgroundGradient.tsx +10 -10
- package/src/client/theme/ui/BackgroundGradient/index.ts +1 -1
- package/src/client/theme/ui/Breadcrumbs/Breadcrumbs.tsx +68 -68
- package/src/client/theme/ui/Breadcrumbs/index.ts +1 -1
- package/src/client/theme/ui/Footer/footer.css +32 -32
- package/src/client/theme/ui/Head/Head.tsx +69 -69
- package/src/client/theme/ui/Head/index.ts +1 -1
- package/src/client/theme/ui/LanguageSwitcher/LanguageSwitcher.tsx +125 -125
- package/src/client/theme/ui/LanguageSwitcher/index.ts +1 -1
- package/src/client/theme/ui/LanguageSwitcher/language-switcher.css +98 -98
- package/src/client/theme/ui/Layout/Layout.tsx +202 -202
- package/src/client/theme/ui/Layout/base.css +76 -76
- package/src/client/theme/ui/Layout/index.ts +2 -2
- package/src/client/theme/ui/Layout/pagination.css +72 -72
- package/src/client/theme/ui/Layout/responsive.css +36 -36
- package/src/client/theme/ui/Link/Link.tsx +254 -254
- package/src/client/theme/ui/Link/index.ts +2 -2
- package/src/client/theme/ui/Loading/Loading.tsx +10 -10
- package/src/client/theme/ui/Loading/index.ts +1 -1
- package/src/client/theme/ui/Loading/loading.css +30 -30
- package/src/client/theme/ui/Navbar/GithubStars.tsx +27 -27
- package/src/client/theme/ui/Navbar/Navbar.tsx +145 -145
- package/src/client/theme/ui/Navbar/index.ts +2 -2
- package/src/client/theme/ui/Navbar/navbar.css +233 -233
- package/src/client/theme/ui/NotFound/NotFound.tsx +19 -19
- package/src/client/theme/ui/NotFound/index.ts +1 -1
- package/src/client/theme/ui/NotFound/not-found.css +64 -64
- package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +235 -235
- package/src/client/theme/ui/OnThisPage/index.ts +1 -1
- package/src/client/theme/ui/OnThisPage/toc.css +132 -132
- package/src/client/theme/ui/PoweredBy/PoweredBy.tsx +18 -18
- package/src/client/theme/ui/PoweredBy/index.ts +1 -1
- package/src/client/theme/ui/PoweredBy/powered-by.css +76 -76
- package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +199 -199
- package/src/client/theme/ui/SearchDialog/index.ts +1 -1
- package/src/client/theme/ui/SearchDialog/search.css +152 -152
- package/src/client/theme/ui/Sidebar/Sidebar.tsx +204 -204
- package/src/client/theme/ui/Sidebar/index.ts +1 -1
- package/src/client/theme/ui/Sidebar/sidebar.css +236 -236
- package/src/client/theme/ui/ThemeToggle/ThemeToggle.tsx +69 -69
- package/src/client/theme/ui/ThemeToggle/index.ts +1 -1
- package/src/client/theme/ui/VersionSwitcher/VersionSwitcher.tsx +136 -136
- package/src/client/theme/ui/VersionSwitcher/index.ts +1 -1
- package/src/client/types.ts +50 -50
- package/src/client/utils.ts +26 -26
- package/src/node/cache.ts +408 -408
- package/src/node/config.ts +192 -192
- package/src/node/index.ts +21 -21
- package/src/node/mdx.ts +120 -120
- package/src/node/plugin/entry.ts +58 -58
- package/src/node/plugin/html.ts +55 -55
- package/src/node/plugin/index.ts +193 -193
- package/src/node/plugin/types.ts +11 -11
- package/src/node/routes/cache.ts +28 -28
- package/src/node/routes/index.ts +167 -167
- package/src/node/routes/parser.ts +153 -127
- package/src/node/routes/sorter.ts +42 -42
- package/src/node/routes/types.ts +49 -49
- package/src/node/ssg/index.ts +114 -114
- package/src/node/ssg/meta.ts +34 -34
- package/src/node/ssg/options.ts +13 -13
- package/src/node/ssg/sitemap.ts +54 -54
- package/src/node/utils.ts +134 -134
- package/tsconfig.json +20 -20
- package/tsup.config.ts +22 -22
|
@@ -1,69 +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
|
-
}
|
|
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
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { Head } from "./Head";
|
|
1
|
+
export { Head } from "./Head";
|
|
@@ -1,125 +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 "../../../types";
|
|
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
|
-
}
|
|
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 "../../../types";
|
|
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
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { LanguageSwitcher } from "./LanguageSwitcher";
|
|
1
|
+
export { LanguageSwitcher } from "./LanguageSwitcher";
|
|
@@ -1,98 +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
|
-
}
|
|
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
|
+
}
|