121 lines
4.0 KiB
TypeScript
121 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useTransition } from "react";
|
|
import { useRouter, usePathname } from "next/navigation";
|
|
import { Check, ChevronDown, Globe } from "lucide-react";
|
|
import { locales, localeFlags, localeNames, type Locale } from "@/i18n/config";
|
|
import { useLocale } from "next-intl";
|
|
|
|
export default function LanguageSelectRadix() {
|
|
const router = useRouter();
|
|
const pathname = usePathname();
|
|
const currentLocale = useLocale() as Locale;
|
|
const [isPending, startTransition] = useTransition();
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
|
|
const changeLanguage = (newLocale: Locale) => {
|
|
if (newLocale === currentLocale) {
|
|
setIsOpen(false);
|
|
return;
|
|
}
|
|
|
|
startTransition(() => {
|
|
// ✅ 1. Set cookie (middleware will sync this)
|
|
document.cookie = `NEXT_LOCALE=${newLocale}; path=/; max-age=31536000; SameSite=Lax`;
|
|
|
|
// ✅ 2. Build new path
|
|
const segments = pathname.split("/").filter(Boolean);
|
|
|
|
// Remove current locale if exists
|
|
const pathWithoutLocale =
|
|
segments[0] && locales.includes(segments[0] as Locale)
|
|
? segments.slice(1)
|
|
: segments;
|
|
|
|
// Add new locale
|
|
const newPath = `/${newLocale}${
|
|
pathWithoutLocale.length ? "/" + pathWithoutLocale.join("/") : ""
|
|
}`;
|
|
|
|
// ✅ 3. Navigate (middleware will handle the rest)
|
|
router.push(newPath);
|
|
|
|
// ✅ 4. Force refresh after navigation completes
|
|
setTimeout(() => {
|
|
router.refresh();
|
|
}, 100); // Small delay ensures navigation completes
|
|
});
|
|
|
|
setIsOpen(false);
|
|
};
|
|
|
|
return (
|
|
<div className="relative">
|
|
{/* Trigger Button */}
|
|
<button
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
disabled={isPending}
|
|
className="inline-flex items-center justify-between gap-2 px-2 py-1 border border-gray-300 hover:border-red-600
|
|
rounded-lg text-white text-sm font-medium shadow-sm hover:bg-red-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
|
|
aria-label="Tilni tanlash"
|
|
aria-expanded={isOpen}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
{isPending ? (
|
|
<Globe className="w-4 h-4 animate-spin" />
|
|
) : (
|
|
<span className="text-lg">{localeFlags[currentLocale]}</span>
|
|
)}
|
|
<span className="hidden md:inline font-medium">
|
|
{localeNames[currentLocale]}
|
|
</span>
|
|
</div>
|
|
<ChevronDown
|
|
className={`w-4 h-4 transition-transform duration-200 ${
|
|
isOpen ? "rotate-180" : ""
|
|
}`}
|
|
/>
|
|
</button>
|
|
|
|
{/* Dropdown */}
|
|
{isOpen && (
|
|
<>
|
|
{/* Backdrop */}
|
|
<div
|
|
className="fixed inset-0 z-40"
|
|
onClick={() => setIsOpen(false)}
|
|
aria-hidden="true"
|
|
/>
|
|
|
|
{/* Menu */}
|
|
<div className="absolute right-0 mt-2 w-48 rounded-lg border border-gray-200 bg-white shadow-lg z-50 animate-in fade-in slide-in-from-top-2 duration-200">
|
|
<div className="p-1" role="menu">
|
|
{locales.map((lang) => (
|
|
<button
|
|
key={lang}
|
|
onClick={() => changeLanguage(lang)}
|
|
disabled={isPending}
|
|
role="menuitem"
|
|
className={`w-full flex items-center justify-between px-3 py-2 text-sm rounded-md transition-colors ${
|
|
currentLocale === lang
|
|
? "bg-blue-50 text-blue-700 font-medium"
|
|
: "hover:bg-gray-100 text-gray-700"
|
|
} disabled:opacity-50 disabled:cursor-not-allowed`}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-lg">{localeFlags[lang]}</span>
|
|
<span>{localeNames[lang]}</span>
|
|
</div>
|
|
{currentLocale === lang && (
|
|
<Check className="w-4 h-4 text-blue-700" />
|
|
)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|