Skip to content

Commit 604301d

Browse files
authored
Merge pull request #19 from barocss/feat/language-switcher-and-mobile
feat: 문서 언어 스위처 노출 및 모바일 반응형 개선
2 parents 9b98a25 + 736d43b commit 604301d

4 files changed

Lines changed: 113 additions & 26 deletions

File tree

packages/theme-docs/src/components/DocHeader.tsx

Lines changed: 100 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
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";
33
import { Button } from "./ui/button";
44
import { Separator } from "./ui/separator";
55
import {
@@ -17,17 +17,56 @@ interface DocHeaderProps {
1717
hasMultipleLocales?: boolean;
1818
currentLocale?: string;
1919
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);
2044
}
2145

2246
export function DocHeader({
2347
siteName,
2448
logo,
2549
githubUrl,
2650
hasMultipleLocales,
27-
currentLocale,
28-
localeLabels,
51+
currentLocale = "en",
52+
localeLabels = {},
53+
currentPath = "",
54+
locales = [],
55+
defaultLocale = "en",
2956
}: DocHeaderProps) {
3057
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+
}, []);
3170

3271
React.useEffect(() => {
3372
const isDark = document.documentElement.classList.contains("dark");
@@ -51,21 +90,21 @@ export function DocHeader({
5190

5291
return (
5392
<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">
5695
{/* Logo */}
57-
<div className="flex items-center gap-6">
96+
<div className="flex items-center gap-6 min-w-0 shrink">
5897
<a
5998
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"
61100
>
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>
64103
</a>
65104
</div>
66105

67106
{/* Right side actions */}
68-
<div className="flex items-center gap-1">
107+
<div className="flex items-center gap-1 shrink-0">
69108
{/* Search button */}
70109
<Button
71110
variant="outline"
@@ -124,6 +163,57 @@ export function DocHeader({
124163
</Tooltip>
125164
)}
126165

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+
127217
{/* Theme toggle */}
128218
<Tooltip>
129219
<TooltipTrigger asChild>
Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
---
22
import { DocHeader } from "./DocHeader";
3-
import LanguageSwitcher from "./LanguageSwitcher.astro";
43
import config from "virtual:barodoc/config";
54
import { locales, defaultLocale } from "virtual:barodoc/i18n";
65
76
interface Props {
87
currentLocale?: string;
8+
currentPath?: string;
99
}
1010
11-
const { currentLocale = defaultLocale } = Astro.props;
11+
const { currentLocale = defaultLocale, currentPath = "" } = Astro.props;
1212
const hasMultipleLocales = locales.length > 1;
1313
14-
// Build locale labels from config
1514
const localeLabels: Record<string, string> = config.i18n?.labels || {};
1615
---
1716

@@ -23,10 +22,7 @@ const localeLabels: Record<string, string> = config.i18n?.labels || {};
2322
hasMultipleLocales={hasMultipleLocales}
2423
currentLocale={currentLocale}
2524
localeLabels={localeLabels}
25+
currentPath={currentPath}
26+
locales={locales}
27+
defaultLocale={defaultLocale}
2628
/>
27-
28-
{hasMultipleLocales && (
29-
<div class="hidden">
30-
<LanguageSwitcher currentLocale={currentLocale} />
31-
</div>
32-
)}

packages/theme-docs/src/layouts/DocsLayout.astro

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,22 @@ const currentLocale = getLocaleFromPath(currentPath, i18nConfig);
3030
---
3131

3232
<BaseLayout title={title} description={description}>
33-
<Header currentLocale={currentLocale} />
33+
<Header currentLocale={currentLocale} currentPath={currentPath} />
3434

3535
<!-- 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">
3838
<!-- Desktop Sidebar -->
3939
<aside class="hidden lg:block w-[220px] shrink-0">
4040
<div class="sticky top-14 h-[calc(100vh-3.5rem)] overflow-y-auto py-6 pr-4">
4141
<Sidebar currentPath={currentPath} currentLocale={currentLocale} />
4242
</div>
4343
</aside>
4444

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">
4949
<slot />
5050
</article>
5151

packages/theme-docs/src/styles/global.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
background-color: var(--color-bg);
6464
color: var(--color-text);
6565
font-feature-settings: "rlig" 1, "calt" 1;
66+
overflow-x: hidden;
6667
}
6768

6869
/* Smooth transitions for all interactive elements */

0 commit comments

Comments
 (0)