@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Barocss
3
+ Copyright (c) 2026 Barocss
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barodoc/theme-docs",
3
- "version": "0.0.1",
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.1"
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.5 font-semibold text-[var(--color-text)] hover:opacity-80 transition-opacity"
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
- // Remove current locale from path if present
13
+ // URLs: /docs/... (en), /docs/ko/... (ko) no /en/ or /ko/ prefix
14
14
  let path = currentPath;
15
- for (const loc of locales) {
16
- if (path.startsWith(`/${loc}/`) || path === `/${loc}`) {
17
- path = path.slice(loc.length + 1) || "/";
18
- break;
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 `/${locale}${path}`;
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
- return normalized === pagePath ||
23
- normalized === `docs/${pagePath}` ||
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
- if (currentLocale === defaultLocale) {
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
- // Build the path
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
- if (locale === defaultLocale) {
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
@@ -63,6 +63,7 @@
63
63
  background-color: var(--color-bg);
64
64
  color: var(--color-text);
65
65
  font-feature-settings: "rlig" 1, "calt" 1;
66
+ overflow-x: hidden;
66
67
  }
67
68
 
68
69
  /* Smooth transitions for all interactive elements */