11import * 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" ;
33import { Button } from "./ui/button" ;
44import { Separator } from "./ui/separator" ;
55import {
@@ -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
2246export 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 >
0 commit comments