11"use client" ;
22
3+ import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui" ;
4+ import { Check , Globe } from "lucide-react" ;
35import { LOCALES , LOCALE_LABELS } from "@/lib/i18n/i18n.types" ;
46import { useI18n } from "@/lib/i18n/use-i18n" ;
57import { cn } from "@/lib/cn" ;
@@ -9,49 +11,54 @@ interface LanguageSwitcherProps {
911 className ?: string ;
1012}
1113
12- const SHORT_LABELS = {
13- ko : "KO" ,
14- en : "EN" ,
15- ja : "JA" ,
16- } as const ;
17-
1814export function LanguageSwitcher ( { size = "md" , className } : LanguageSwitcherProps ) {
1915 const { locale, setLocale, t } = useI18n ( ) ;
2016
2117 return (
22- < div
23- role = "group"
24- aria-label = { t . languageSwitcher . groupAria }
25- className = { cn (
26- "inline-flex items-center rounded-full bg-white ring-1 ring-[var(--color-border)]" ,
27- size === "md" ? "gap-0.5 p-1" : "gap-px p-0.5" ,
28- className ,
29- ) }
30- >
31- { LOCALES . map ( ( code ) => {
32- const active = code === locale ;
33- return (
34- < button
35- key = { code }
36- type = "button"
37- lang = { code }
38- aria-label = { LOCALE_LABELS [ code ] }
39- aria-pressed = { active }
40- onClick = { ( ) => setLocale ( code ) }
41- className = { cn (
42- "cursor-pointer rounded-full font-semibold tracking-wide transition-colors" ,
43- size === "md"
44- ? "px-2.5 py-1 text-[11px]"
45- : "px-2 py-0.5 text-[10.5px]" ,
46- active
47- ? "bg-[var(--color-primary)] text-white"
48- : "text-[var(--color-fg-muted)] hover:text-[var(--color-primary)]" ,
49- ) }
50- >
51- { SHORT_LABELS [ code ] }
52- </ button >
53- ) ;
54- } ) }
55- </ div >
18+ < DropdownMenuPrimitive . Root >
19+ < DropdownMenuPrimitive . Trigger asChild >
20+ < button
21+ type = "button"
22+ aria-label = { t . languageSwitcher . groupAria }
23+ className = { cn (
24+ "inline-flex cursor-pointer items-center justify-center rounded-full bg-white text-[var(--color-fg-muted)] ring-1 ring-[var(--color-border)] transition-colors hover:text-[var(--color-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-primary)] data-[state=open]:text-[var(--color-primary)]" ,
25+ size === "md" ? "h-9 w-9" : "h-8 w-8" ,
26+ className ,
27+ ) }
28+ >
29+ < Globe
30+ aria-hidden
31+ className = { size === "md" ? "h-[18px] w-[18px]" : "h-4 w-4" }
32+ />
33+ </ button >
34+ </ DropdownMenuPrimitive . Trigger >
35+ < DropdownMenuPrimitive . Portal >
36+ < DropdownMenuPrimitive . Content
37+ align = "end"
38+ sideOffset = { 8 }
39+ className = "z-50 min-w-[9rem] overflow-hidden rounded-2xl border border-[var(--color-border)] bg-white p-1 shadow-[0_8px_30px_rgba(15,76,58,0.12)]"
40+ >
41+ { LOCALES . map ( ( code ) => {
42+ const active = code === locale ;
43+ return (
44+ < DropdownMenuPrimitive . Item
45+ key = { code }
46+ lang = { code }
47+ onSelect = { ( ) => setLocale ( code ) }
48+ className = { cn (
49+ "flex cursor-pointer select-none items-center justify-between gap-3 rounded-xl px-3 py-2 text-sm font-medium outline-none transition-colors focus:bg-[var(--color-primary)]/8 data-[highlighted]:bg-[var(--color-primary)]/8" ,
50+ active
51+ ? "text-[var(--color-primary)]"
52+ : "text-[var(--color-fg-muted)]" ,
53+ ) }
54+ >
55+ { LOCALE_LABELS [ code ] }
56+ { active && < Check aria-hidden className = "h-4 w-4" /> }
57+ </ DropdownMenuPrimitive . Item >
58+ ) ;
59+ } ) }
60+ </ DropdownMenuPrimitive . Content >
61+ </ DropdownMenuPrimitive . Portal >
62+ </ DropdownMenuPrimitive . Root >
5663 ) ;
5764}
0 commit comments