@barodoc/theme-docs 0.0.1 → 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/LICENSE +1 -1
- package/package.json +2 -2
- package/src/components/DocHeader.tsx +100 -10
- package/src/components/Header.astro +5 -9
- package/src/components/LanguageSwitcher.astro +12 -10
- package/src/components/Sidebar.astro +3 -7
- package/src/index.ts +0 -11
- package/src/layouts/DocsLayout.astro +7 -7
- package/src/pages/404.astro +32 -0
- package/src/pages/docs/[...slug].astro +3 -6
- package/src/styles/global.css +1 -0
package/LICENSE
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barodoc/theme-docs",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Documentation theme for Barodoc",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"lucide-react": "^0.563.0",
|
|
31
31
|
"mermaid": "^11.12.2",
|
|
32
32
|
"tailwind-merge": "^3.4.0",
|
|
33
|
-
"@barodoc/core": "0.0
|
|
33
|
+
"@barodoc/core": "1.0.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"astro": "^5.0.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { Search, Menu, Moon, Sun, Github } from "lucide-react";
|
|
2
|
+
import { Search, Menu, Moon, Sun, Github, Globe, ChevronDown } from "lucide-react";
|
|
3
3
|
import { Button } from "./ui/button";
|
|
4
4
|
import { Separator } from "./ui/separator";
|
|
5
5
|
import {
|
|
@@ -17,6 +17,30 @@ interface DocHeaderProps {
|
|
|
17
17
|
hasMultipleLocales?: boolean;
|
|
18
18
|
currentLocale?: string;
|
|
19
19
|
localeLabels?: Record<string, string>;
|
|
20
|
+
currentPath?: string;
|
|
21
|
+
locales?: string[];
|
|
22
|
+
defaultLocale?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getLocalizedUrl(
|
|
26
|
+
path: string,
|
|
27
|
+
locale: string,
|
|
28
|
+
defaultLocale: string,
|
|
29
|
+
): string {
|
|
30
|
+
const docsPrefix = "/docs/";
|
|
31
|
+
const koPrefix = "/docs/ko/";
|
|
32
|
+
|
|
33
|
+
if (path.startsWith(koPrefix)) {
|
|
34
|
+
path = path === "/docs/ko" ? "/docs" : docsPrefix + path.slice(koPrefix.length);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (locale === defaultLocale) {
|
|
38
|
+
return path || "/";
|
|
39
|
+
}
|
|
40
|
+
if (path === "/" || !path.startsWith(docsPrefix)) {
|
|
41
|
+
return path === "/" ? "/docs/ko/introduction" : path;
|
|
42
|
+
}
|
|
43
|
+
return docsPrefix + "ko/" + path.slice(docsPrefix.length);
|
|
20
44
|
}
|
|
21
45
|
|
|
22
46
|
export function DocHeader({
|
|
@@ -24,10 +48,25 @@ export function DocHeader({
|
|
|
24
48
|
logo,
|
|
25
49
|
githubUrl,
|
|
26
50
|
hasMultipleLocales,
|
|
27
|
-
currentLocale,
|
|
28
|
-
localeLabels,
|
|
51
|
+
currentLocale = "en",
|
|
52
|
+
localeLabels = {},
|
|
53
|
+
currentPath = "",
|
|
54
|
+
locales = [],
|
|
55
|
+
defaultLocale = "en",
|
|
29
56
|
}: DocHeaderProps) {
|
|
30
57
|
const [theme, setTheme] = React.useState<"light" | "dark">("light");
|
|
58
|
+
const [langOpen, setLangOpen] = React.useState(false);
|
|
59
|
+
const langRef = React.useRef<HTMLDivElement>(null);
|
|
60
|
+
|
|
61
|
+
React.useEffect(() => {
|
|
62
|
+
const close = (e: MouseEvent) => {
|
|
63
|
+
if (langRef.current && !langRef.current.contains(e.target as Node)) {
|
|
64
|
+
setLangOpen(false);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
document.addEventListener("click", close);
|
|
68
|
+
return () => document.removeEventListener("click", close);
|
|
69
|
+
}, []);
|
|
31
70
|
|
|
32
71
|
React.useEffect(() => {
|
|
33
72
|
const isDark = document.documentElement.classList.contains("dark");
|
|
@@ -51,21 +90,21 @@ export function DocHeader({
|
|
|
51
90
|
|
|
52
91
|
return (
|
|
53
92
|
<TooltipProvider>
|
|
54
|
-
<header className="sticky top-0 z-50 w-full border-b border-[var(--color-border)] bg-[var(--color-bg)]/95 backdrop-blur-md supports-[backdrop-filter]:bg-[var(--color-bg)]/80">
|
|
55
|
-
<div className="flex h-14 items-center justify-between px-4 max-w-[1120px] mx-auto">
|
|
93
|
+
<header className="sticky top-0 z-50 w-full min-w-0 border-b border-[var(--color-border)] bg-[var(--color-bg)]/95 backdrop-blur-md supports-[backdrop-filter]:bg-[var(--color-bg)]/80">
|
|
94
|
+
<div className="flex h-14 items-center justify-between gap-2 px-3 sm:px-4 max-w-[1120px] mx-auto min-w-0">
|
|
56
95
|
{/* Logo */}
|
|
57
|
-
<div className="flex items-center gap-6">
|
|
96
|
+
<div className="flex items-center gap-6 min-w-0 shrink">
|
|
58
97
|
<a
|
|
59
98
|
href="/"
|
|
60
|
-
className="flex items-center gap-2
|
|
99
|
+
className="flex items-center gap-2 min-w-0 shrink overflow-hidden font-semibold text-[var(--color-text)] hover:opacity-80 transition-opacity"
|
|
61
100
|
>
|
|
62
|
-
{logo && <img src={logo} alt={siteName} className="h-7 w-7" />}
|
|
63
|
-
<span className="text-lg">{siteName}</span>
|
|
101
|
+
{logo && <img src={logo} alt={siteName} className="h-7 w-7 shrink-0" />}
|
|
102
|
+
<span className="text-lg truncate">{siteName}</span>
|
|
64
103
|
</a>
|
|
65
104
|
</div>
|
|
66
105
|
|
|
67
106
|
{/* Right side actions */}
|
|
68
|
-
<div className="flex items-center gap-1">
|
|
107
|
+
<div className="flex items-center gap-1 shrink-0">
|
|
69
108
|
{/* Search button */}
|
|
70
109
|
<Button
|
|
71
110
|
variant="outline"
|
|
@@ -124,6 +163,57 @@ export function DocHeader({
|
|
|
124
163
|
</Tooltip>
|
|
125
164
|
)}
|
|
126
165
|
|
|
166
|
+
{/* Language switcher */}
|
|
167
|
+
{hasMultipleLocales && locales.length > 0 && (
|
|
168
|
+
<>
|
|
169
|
+
<Separator orientation="vertical" className="hidden md:block h-6 mx-2" />
|
|
170
|
+
<div className="relative" ref={langRef}>
|
|
171
|
+
<Tooltip>
|
|
172
|
+
<TooltipTrigger asChild>
|
|
173
|
+
<Button
|
|
174
|
+
variant="ghost"
|
|
175
|
+
className="rounded-xl gap-1 px-2"
|
|
176
|
+
onClick={(e) => {
|
|
177
|
+
e.stopPropagation();
|
|
178
|
+
setLangOpen((o) => !o);
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
<Globe className="h-4 w-4" />
|
|
182
|
+
<span className="text-sm hidden sm:inline">
|
|
183
|
+
{localeLabels[currentLocale] ?? currentLocale}
|
|
184
|
+
</span>
|
|
185
|
+
<ChevronDown className="h-3 w-3" />
|
|
186
|
+
<span className="sr-only">Language</span>
|
|
187
|
+
</Button>
|
|
188
|
+
</TooltipTrigger>
|
|
189
|
+
<TooltipContent>Language</TooltipContent>
|
|
190
|
+
</Tooltip>
|
|
191
|
+
{langOpen && (
|
|
192
|
+
<div
|
|
193
|
+
className="absolute right-0 mt-1 py-1 min-w-[8rem] rounded-lg border border-[var(--color-border)] bg-[var(--color-bg)] shadow-lg z-50"
|
|
194
|
+
role="menu"
|
|
195
|
+
>
|
|
196
|
+
{locales.map((locale) => (
|
|
197
|
+
<a
|
|
198
|
+
key={locale}
|
|
199
|
+
href={getLocalizedUrl(currentPath, locale, defaultLocale)}
|
|
200
|
+
className={cn(
|
|
201
|
+
"block px-3 py-2 text-sm transition-colors",
|
|
202
|
+
locale === currentLocale
|
|
203
|
+
? "bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300"
|
|
204
|
+
: "text-[var(--color-text)] hover:bg-[var(--color-bg-secondary)]"
|
|
205
|
+
)}
|
|
206
|
+
role="menuitem"
|
|
207
|
+
>
|
|
208
|
+
{localeLabels[locale] ?? locale}
|
|
209
|
+
</a>
|
|
210
|
+
))}
|
|
211
|
+
</div>
|
|
212
|
+
)}
|
|
213
|
+
</div>
|
|
214
|
+
</>
|
|
215
|
+
)}
|
|
216
|
+
|
|
127
217
|
{/* Theme toggle */}
|
|
128
218
|
<Tooltip>
|
|
129
219
|
<TooltipTrigger asChild>
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { DocHeader } from "./DocHeader";
|
|
3
|
-
import LanguageSwitcher from "./LanguageSwitcher.astro";
|
|
4
3
|
import config from "virtual:barodoc/config";
|
|
5
4
|
import { locales, defaultLocale } from "virtual:barodoc/i18n";
|
|
6
5
|
|
|
7
6
|
interface Props {
|
|
8
7
|
currentLocale?: string;
|
|
8
|
+
currentPath?: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
const { currentLocale = defaultLocale } = Astro.props;
|
|
11
|
+
const { currentLocale = defaultLocale, currentPath = "" } = Astro.props;
|
|
12
12
|
const hasMultipleLocales = locales.length > 1;
|
|
13
13
|
|
|
14
|
-
// Build locale labels from config
|
|
15
14
|
const localeLabels: Record<string, string> = config.i18n?.labels || {};
|
|
16
15
|
---
|
|
17
16
|
|
|
@@ -23,10 +22,7 @@ const localeLabels: Record<string, string> = config.i18n?.labels || {};
|
|
|
23
22
|
hasMultipleLocales={hasMultipleLocales}
|
|
24
23
|
currentLocale={currentLocale}
|
|
25
24
|
localeLabels={localeLabels}
|
|
25
|
+
currentPath={currentPath}
|
|
26
|
+
locales={locales}
|
|
27
|
+
defaultLocale={defaultLocale}
|
|
26
28
|
/>
|
|
27
|
-
|
|
28
|
-
{hasMultipleLocales && (
|
|
29
|
-
<div class="hidden">
|
|
30
|
-
<LanguageSwitcher currentLocale={currentLocale} />
|
|
31
|
-
</div>
|
|
32
|
-
)}
|
|
@@ -10,20 +10,22 @@ const { currentLocale } = Astro.props;
|
|
|
10
10
|
const currentPath = Astro.url.pathname;
|
|
11
11
|
|
|
12
12
|
function getLocalizedUrl(locale: string): string {
|
|
13
|
-
//
|
|
13
|
+
// URLs: /docs/... (en), /docs/ko/... (ko) — no /en/ or /ko/ prefix
|
|
14
14
|
let path = currentPath;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
const docsPrefix = "/docs/";
|
|
16
|
+
const koPrefix = "/docs/ko/";
|
|
17
|
+
|
|
18
|
+
if (path.startsWith(koPrefix)) {
|
|
19
|
+
path = path === koPrefix.slice(0, -1) ? "/docs" : docsPrefix + path.slice(koPrefix.length);
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
// Add new locale
|
|
21
|
+
|
|
23
22
|
if (locale === defaultLocale) {
|
|
24
|
-
return path;
|
|
23
|
+
return path || "/";
|
|
24
|
+
}
|
|
25
|
+
if (path === "/" || !path.startsWith(docsPrefix)) {
|
|
26
|
+
return path === "/" ? "/docs/ko/introduction" : path;
|
|
25
27
|
}
|
|
26
|
-
return
|
|
28
|
+
return docsPrefix + "ko/" + path.slice(docsPrefix.length);
|
|
27
29
|
}
|
|
28
30
|
---
|
|
29
31
|
|
|
@@ -19,16 +19,12 @@ function normalizePath(path: string): string {
|
|
|
19
19
|
function isActive(page: string): boolean {
|
|
20
20
|
const normalized = normalizePath(currentPath);
|
|
21
21
|
const pagePath = normalizePath(page);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
normalized === `${currentLocale}/docs/${pagePath}`;
|
|
22
|
+
const docsSlug = currentLocale === defaultLocale ? pagePath : `ko/${pagePath}`;
|
|
23
|
+
return normalized === `docs/${pagePath}` || normalized === `docs/${docsSlug}`;
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
function getPageHref(page: string): string {
|
|
28
|
-
|
|
29
|
-
return `/docs/${page}`;
|
|
30
|
-
}
|
|
31
|
-
return `/${currentLocale}/docs/${page}`;
|
|
27
|
+
return currentLocale === defaultLocale ? `/docs/${page}` : `/docs/ko/${page}`;
|
|
32
28
|
}
|
|
33
29
|
|
|
34
30
|
// Get page title from the page slug
|
package/src/index.ts
CHANGED
|
@@ -26,17 +26,6 @@ function createThemeIntegration(options?: DocsThemeOptions): AstroIntegration {
|
|
|
26
26
|
entrypoint: "@barodoc/theme-docs/pages/docs/[...slug].astro",
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
// Inject localized routes for non-default locales
|
|
30
|
-
injectRoute({
|
|
31
|
-
pattern: "/[locale]",
|
|
32
|
-
entrypoint: "@barodoc/theme-docs/pages/index.astro",
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
injectRoute({
|
|
36
|
-
pattern: "/[locale]/docs/[...slug]",
|
|
37
|
-
entrypoint: "@barodoc/theme-docs/pages/docs/[...slug].astro",
|
|
38
|
-
});
|
|
39
|
-
|
|
40
29
|
// Update Astro config with integrations and Vite plugins
|
|
41
30
|
updateConfig({
|
|
42
31
|
integrations: [mdx(), react()],
|
|
@@ -30,11 +30,11 @@ const currentLocale = getLocaleFromPath(currentPath, i18nConfig);
|
|
|
30
30
|
---
|
|
31
31
|
|
|
32
32
|
<BaseLayout title={title} description={description}>
|
|
33
|
-
<Header currentLocale={currentLocale} />
|
|
33
|
+
<Header currentLocale={currentLocale} currentPath={currentPath} />
|
|
34
34
|
|
|
35
35
|
<!-- Centered container for docs layout -->
|
|
36
|
-
<div class="w-full min-h-[calc(100vh-3.5rem)] flex justify-center">
|
|
37
|
-
<div class="flex w-full max-w-[1120px]">
|
|
36
|
+
<div class="w-full min-w-0 min-h-[calc(100vh-3.5rem)] flex justify-center overflow-x-hidden">
|
|
37
|
+
<div class="flex w-full max-w-[1120px] min-w-0">
|
|
38
38
|
<!-- Desktop Sidebar -->
|
|
39
39
|
<aside class="hidden lg:block w-[220px] shrink-0">
|
|
40
40
|
<div class="sticky top-14 h-[calc(100vh-3.5rem)] overflow-y-auto py-6 pr-4">
|
|
@@ -42,10 +42,10 @@ const currentLocale = getLocaleFromPath(currentPath, i18nConfig);
|
|
|
42
42
|
</div>
|
|
43
43
|
</aside>
|
|
44
44
|
|
|
45
|
-
<!-- Main Content -->
|
|
46
|
-
<main class="flex-1 min-w-0 min-w-[650px] max-w-[720px]">
|
|
47
|
-
<div class="px-8 py-8">
|
|
48
|
-
<article class="prose prose-gray dark:prose-invert max-w-none">
|
|
45
|
+
<!-- Main Content: no min-width on mobile to avoid horizontal scroll -->
|
|
46
|
+
<main class="flex-1 min-w-0 lg:min-w-[650px] max-w-[720px]">
|
|
47
|
+
<div class="px-4 py-6 sm:px-6 sm:py-8 lg:px-8 lg:py-8">
|
|
48
|
+
<article class="prose prose-gray dark:prose-invert max-w-none min-w-0 overflow-x-auto">
|
|
49
49
|
<slot />
|
|
50
50
|
</article>
|
|
51
51
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
import BaseLayout from "../layouts/BaseLayout.astro";
|
|
3
|
+
import config from "virtual:barodoc/config";
|
|
4
|
+
|
|
5
|
+
const pathname = new URL(Astro.request.url).pathname;
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<BaseLayout title={`${config.name} · Page not found`} description="The page you are looking for does not exist.">
|
|
9
|
+
<div class="min-h-screen flex flex-col items-center justify-center px-4">
|
|
10
|
+
<div class="text-center max-w-md">
|
|
11
|
+
<p class="text-8xl font-bold text-[var(--color-text-muted)] select-none" aria-hidden="true">404</p>
|
|
12
|
+
<h1 class="text-2xl font-semibold text-[var(--color-text)] mt-4">Page not found</h1>
|
|
13
|
+
<p class="text-[var(--color-text-secondary)] mt-2">
|
|
14
|
+
The page at <code class="text-sm bg-[var(--color-bg-secondary)] px-2 py-1 rounded break-all">{pathname}</code> does not exist.
|
|
15
|
+
</p>
|
|
16
|
+
<div class="flex flex-col sm:flex-row items-center justify-center gap-3 mt-8">
|
|
17
|
+
<a
|
|
18
|
+
href="/"
|
|
19
|
+
class="px-5 py-2.5 bg-primary-600 text-white rounded-xl hover:bg-primary-700 transition-colors font-medium"
|
|
20
|
+
>
|
|
21
|
+
Home
|
|
22
|
+
</a>
|
|
23
|
+
<a
|
|
24
|
+
href="/docs/introduction"
|
|
25
|
+
class="px-5 py-2.5 border border-[var(--color-border)] text-[var(--color-text)] rounded-xl hover:bg-[var(--color-bg-secondary)] transition-colors font-medium"
|
|
26
|
+
>
|
|
27
|
+
Documentation
|
|
28
|
+
</a>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</BaseLayout>
|
|
@@ -21,7 +21,7 @@ export async function getStaticPaths() {
|
|
|
21
21
|
cleanSlug = slugParts.slice(1).join("/");
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
//
|
|
24
|
+
// URL: /docs/introduction (en), /docs/ko/introduction (ko) — locale only inside slug
|
|
25
25
|
const path = locale === defaultLocale
|
|
26
26
|
? cleanSlug
|
|
27
27
|
: `${locale}/${cleanSlug}`;
|
|
@@ -71,12 +71,9 @@ function getPageTitle(slug: string): string {
|
|
|
71
71
|
.join(' ');
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
// Get page href
|
|
74
|
+
// Get page href (always /docs/...; non-default locale is inside slug)
|
|
75
75
|
function getPageHref(slug: string): string {
|
|
76
|
-
|
|
77
|
-
return `/docs/${slug}`;
|
|
78
|
-
}
|
|
79
|
-
return `/${locale}/docs/${slug}`;
|
|
76
|
+
return `/docs/${locale === defaultLocale ? slug : `${locale}/${slug}`}`;
|
|
80
77
|
}
|
|
81
78
|
|
|
82
79
|
// Find prev/next pages
|