22
33import { useEffect , useState } from 'react'
44import { Check , ChevronDown } from 'lucide-react'
5- import { useParams , usePathname } from 'next/navigation'
5+ import { useParams , usePathname , useRouter } from 'next/navigation'
66
77const languages = {
88 en : { name : 'English' , flag : '🇺🇸' } ,
99 es : { name : 'Español' , flag : '🇪🇸' } ,
1010 fr : { name : 'Français' , flag : '🇫🇷' } ,
11+ de : { name : 'Deutsch' , flag : '🇩🇪' } ,
12+ ja : { name : '日本語' , flag : '🇯🇵' } ,
1113 zh : { name : '简体中文' , flag : '🇨🇳' } ,
1214}
1315
1416export function LanguageDropdown ( ) {
1517 const [ isOpen , setIsOpen ] = useState ( false )
1618 const pathname = usePathname ( )
1719 const params = useParams ( )
20+ const router = useRouter ( )
1821
1922 const [ currentLang , setCurrentLang ] = useState ( ( ) => {
2023 const langFromParams = params ?. lang as string
@@ -56,9 +59,18 @@ export function LanguageDropdown() {
5659 newPath = `/${ locale } ${ segments . length > 0 ? `/${ segments . join ( '/' ) } ` : '/introduction' } `
5760 }
5861
59- window . location . href = newPath
62+ router . push ( newPath )
6063 }
6164
65+ useEffect ( ( ) => {
66+ if ( ! isOpen ) return
67+ const onKey = ( e : KeyboardEvent ) => {
68+ if ( e . key === 'Escape' ) setIsOpen ( false )
69+ }
70+ window . addEventListener ( 'keydown' , onKey )
71+ return ( ) => window . removeEventListener ( 'keydown' , onKey )
72+ } , [ isOpen ] )
73+
6274 return (
6375 < div className = 'relative' >
6476 < button
@@ -67,9 +79,12 @@ export function LanguageDropdown() {
6779 e . stopPropagation ( )
6880 setIsOpen ( ! isOpen )
6981 } }
70- className = 'flex items-center gap-2 rounded-xl border border-border/20 bg-muted/50 px-3 py-2 text-sm backdrop-blur-sm transition-colors hover:bg-muted'
82+ aria-haspopup = 'listbox'
83+ aria-expanded = { isOpen }
84+ aria-controls = 'language-menu'
85+ className = 'flex items-center gap-1.5 rounded-lg border border-border/30 bg-muted/40 px-2.5 py-1.5 text-sm shadow-sm backdrop-blur-sm transition-colors hover:bg-muted focus:outline-none focus-visible:ring-2 focus-visible:ring-ring'
7186 >
72- < span className = 'text-base ' > { languages [ currentLang as keyof typeof languages ] ?. flag } </ span >
87+ < span className = 'text-sm ' > { languages [ currentLang as keyof typeof languages ] ?. flag } </ span >
7388 < span className = 'font-medium text-foreground' >
7489 { languages [ currentLang as keyof typeof languages ] ?. name }
7590 </ span >
@@ -80,8 +95,12 @@ export function LanguageDropdown() {
8095
8196 { isOpen && (
8297 < >
83- < div className = 'fixed inset-0 z-10' onClick = { ( ) => setIsOpen ( false ) } />
84- < div className = 'absolute top-full left-0 z-20 mt-1 w-48 rounded-lg border border-border/50 bg-background/95 shadow-xl backdrop-blur-md' >
98+ < div className = 'fixed inset-0 z-[1000]' aria-hidden onClick = { ( ) => setIsOpen ( false ) } />
99+ < div
100+ id = 'language-menu'
101+ role = 'listbox'
102+ className = 'absolute top-full left-0 z-[1001] mt-1 max-h-[75vh] w-56 overflow-auto rounded-xl border border-border/50 bg-white shadow-2xl md:w-44 md:bg-background/95 md:backdrop-blur-md dark:bg-neutral-950 md:dark:bg-background/95'
103+ >
85104 { Object . entries ( languages ) . map ( ( [ code , lang ] ) => (
86105 < button
87106 key = { code }
@@ -90,13 +109,17 @@ export function LanguageDropdown() {
90109 e . stopPropagation ( )
91110 handleLanguageChange ( code )
92111 } }
93- className = { `flex w-full items-center gap-3 px-3 py-2.5 text-sm transition-colors first:rounded-t-lg last:rounded-b-lg hover:bg-muted/80 ${
112+ role = 'option'
113+ aria-selected = { currentLang === code }
114+ className = { `flex w-full items-center gap-3 px-3 py-3 text-base transition-colors first:rounded-t-xl last:rounded-b-xl hover:bg-muted/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring md:gap-2 md:px-2.5 md:py-2 md:text-sm ${
94115 currentLang === code ? 'bg-muted/60 font-medium text-primary' : 'text-foreground'
95116 } `}
96117 >
97- < span className = 'text-base' > { lang . flag } </ span >
98- < span > { lang . name } </ span >
99- { currentLang === code && < Check className = 'ml-auto h-4 w-4 text-primary' /> }
118+ < span className = 'text-base md:text-sm' > { lang . flag } </ span >
119+ < span className = 'leading-none' > { lang . name } </ span >
120+ { currentLang === code && (
121+ < Check className = 'ml-auto h-4 w-4 text-primary md:h-3.5 md:w-3.5' />
122+ ) }
100123 </ button >
101124 ) ) }
102125 </ div >
0 commit comments