272 lines
12 KiB
TypeScript
272 lines
12 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect, useRef } from 'react';
|
|
import { useRouter, usePathname } from 'next/navigation';
|
|
import { Locale, locales, localeNames, localeFlags } from '../lib/i18n';
|
|
|
|
interface FloatingLanguageSwitcherProps {
|
|
locale: Locale;
|
|
}
|
|
|
|
export default function FloatingLanguageSwitcher({ locale }: FloatingLanguageSwitcherProps) {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [isVisible, setIsVisible] = useState(true);
|
|
const [isMounted, setIsMounted] = useState(false);
|
|
const router = useRouter();
|
|
const pathname = usePathname();
|
|
const switcherRef = useRef<HTMLDivElement>(null);
|
|
const lastScrollY = useRef(0);
|
|
|
|
// Ensure component is mounted before rendering to avoid hydration issues
|
|
useEffect(() => {
|
|
setIsMounted(true);
|
|
}, []);
|
|
|
|
// Handle scroll to show/hide the switcher (temporarily disabled for visibility)
|
|
useEffect(() => {
|
|
// Keep the switcher always visible for now
|
|
setIsVisible(true);
|
|
|
|
// Uncomment below to re-enable scroll hiding
|
|
/*
|
|
const handleScroll = () => {
|
|
const currentScrollY = window.scrollY;
|
|
if (currentScrollY > lastScrollY.current && currentScrollY > 100) {
|
|
// Scrolling down
|
|
setIsVisible(false);
|
|
} else {
|
|
// Scrolling up
|
|
setIsVisible(true);
|
|
}
|
|
lastScrollY.current = currentScrollY;
|
|
};
|
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
return () => window.removeEventListener('scroll', handleScroll);
|
|
*/
|
|
}, []);
|
|
|
|
// Close menu when clicking outside
|
|
useEffect(() => {
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
// Add a small delay to ensure button click is processed first
|
|
setTimeout(() => {
|
|
if (switcherRef.current && !switcherRef.current.contains(event.target as Node)) {
|
|
setIsOpen(false);
|
|
}
|
|
}, 10);
|
|
};
|
|
|
|
if (isOpen) {
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
}
|
|
|
|
return () => {
|
|
document.removeEventListener('mousedown', handleClickOutside);
|
|
};
|
|
}, [isOpen]);
|
|
|
|
const switchLanguage = (newLocale: Locale) => {
|
|
console.log('Switching language from', locale, 'to', newLocale);
|
|
console.log('Current pathname:', pathname);
|
|
|
|
try {
|
|
// Get the current path segments
|
|
const pathSegments = pathname.split('/').filter(Boolean);
|
|
console.log('Path segments:', pathSegments);
|
|
|
|
// Remove the current locale from the beginning if it exists
|
|
if (pathSegments[0] && locales.includes(pathSegments[0] as Locale)) {
|
|
pathSegments.shift();
|
|
}
|
|
|
|
// Construct the new path with the new locale
|
|
const newPath = `/${newLocale}${pathSegments.length > 0 ? '/' + pathSegments.join('/') : ''}`;
|
|
console.log('New path:', newPath);
|
|
|
|
// Use replace instead of push to avoid history issues
|
|
router.replace(newPath);
|
|
setIsOpen(false);
|
|
} catch (error) {
|
|
console.error('Error switching language:', error);
|
|
// Fallback to simple navigation
|
|
router.replace(`/${newLocale}`);
|
|
setIsOpen(false);
|
|
}
|
|
};
|
|
|
|
// Don't render until mounted to avoid hydration issues
|
|
if (!isMounted) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div
|
|
ref={switcherRef}
|
|
className={`fixed right-4 md:right-6 top-1/2 -translate-y-1/2 transition-all duration-300 ${
|
|
isVisible ? 'translate-x-0 opacity-100' : 'translate-x-full opacity-0'
|
|
}`}
|
|
style={{
|
|
pointerEvents: 'auto',
|
|
isolation: 'isolate',
|
|
zIndex: 999999,
|
|
position: 'fixed',
|
|
}}
|
|
data-oid="taerglg"
|
|
>
|
|
{/* Main Switcher Button */}
|
|
<button
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
console.log('Button clicked, current isOpen:', isOpen);
|
|
const newState = !isOpen;
|
|
setIsOpen(newState);
|
|
console.log('Setting isOpen to:', newState);
|
|
}}
|
|
onMouseDown={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}}
|
|
className="group relative bg-slate-800/90 border-2 border-green-400/50 rounded-full p-3 shadow-2xl hover:bg-slate-700/90 hover:border-green-400 transition-all duration-200 cursor-pointer"
|
|
title={`Current: ${localeNames[locale]}`}
|
|
type="button"
|
|
style={{
|
|
pointerEvents: 'auto',
|
|
touchAction: 'manipulation',
|
|
zIndex: 1000000,
|
|
position: 'relative',
|
|
}}
|
|
data-oid="_:fx8.e"
|
|
>
|
|
<div className="flex items-center justify-center w-8 h-8" data-oid="di.5x8v">
|
|
<span className="text-2xl" data-oid="ll7ebjo">
|
|
{localeFlags[locale]}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Tooltip */}
|
|
<div
|
|
className="absolute right-full mr-3 top-1/2 -translate-y-1/2 bg-slate-800/95 backdrop-blur-md text-white px-3 py-2 rounded-lg text-sm font-medium opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none whitespace-nowrap"
|
|
data-oid=":kya.x4"
|
|
>
|
|
{localeNames[locale]}
|
|
<div
|
|
className="absolute left-full top-1/2 -translate-y-1/2 border-4 border-transparent border-l-slate-800/95"
|
|
data-oid="01-w6mc"
|
|
></div>
|
|
</div>
|
|
</button>
|
|
|
|
{/* Language Options */}
|
|
{isOpen && (
|
|
<div
|
|
className="absolute right-full mr-3 md:mr-4 top-1/2 -translate-y-1/2 bg-slate-800/98 rounded-2xl shadow-2xl border-2 border-green-400/30 overflow-hidden min-w-[180px] md:min-w-[200px]"
|
|
style={{
|
|
zIndex: 1000001,
|
|
pointerEvents: 'auto',
|
|
}}
|
|
data-oid="e1v:81b"
|
|
>
|
|
<div className="p-2" data-oid="xcuqg9t">
|
|
<div
|
|
className="px-3 py-2 text-xs font-medium text-white/60 uppercase tracking-wider border-b border-white/10 mb-2"
|
|
data-oid="_jrtjyl"
|
|
>
|
|
Select Language
|
|
</div>
|
|
|
|
<div className="space-y-1" data-oid="tg0y3c4">
|
|
{locales.map((loc) => (
|
|
<button
|
|
key={loc}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
console.log('Language option clicked:', loc);
|
|
// Add immediate feedback
|
|
e.currentTarget.style.backgroundColor =
|
|
'rgba(34, 197, 94, 0.3)';
|
|
setTimeout(() => {
|
|
switchLanguage(loc);
|
|
}, 100);
|
|
}}
|
|
onMouseDown={(e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}}
|
|
className={`w-full text-left px-3 py-3 rounded-xl flex items-center space-x-3 transition-all duration-200 cursor-pointer ${
|
|
locale === loc
|
|
? 'bg-green-500/30 text-green-400 border border-green-500/50'
|
|
: 'text-white hover:bg-green-500/20 hover:text-green-400'
|
|
}`}
|
|
type="button"
|
|
style={{
|
|
pointerEvents: 'auto',
|
|
touchAction: 'manipulation',
|
|
zIndex: 1000002,
|
|
}}
|
|
data-oid="i3cq3gh"
|
|
>
|
|
<span className="text-lg" data-oid="kl5sqzg">
|
|
{localeFlags[loc]}
|
|
</span>
|
|
<div className="flex-1" data-oid="ump7m:6">
|
|
<span className="font-medium" data-oid="lsnf7ck">
|
|
{localeNames[loc]}
|
|
</span>
|
|
{locale === loc && (
|
|
<div
|
|
className="text-xs text-green-400/70 mt-0.5"
|
|
data-oid="6e9d:gj"
|
|
>
|
|
Current
|
|
</div>
|
|
)}
|
|
</div>
|
|
{locale === loc && (
|
|
<svg
|
|
className="w-4 h-4 text-green-400"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
data-oid="v2bjs6b"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M5 13l4 4L19 7"
|
|
data-oid="mmqle:5"
|
|
/>
|
|
</svg>
|
|
)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Arrow pointing to the button */}
|
|
<div
|
|
className="absolute left-full top-1/2 -translate-y-1/2 border-8 border-transparent border-l-slate-800/95"
|
|
data-oid="0gukon1"
|
|
></div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Subtle glow effect */}
|
|
<div
|
|
className="absolute inset-0 rounded-full bg-green-400/20 animate-pulse pointer-events-none"
|
|
style={{ zIndex: -1 }}
|
|
data-oid="g55eh56"
|
|
></div>
|
|
|
|
{/* Floating indicator */}
|
|
<div
|
|
className="absolute -top-1 -right-1 w-3 h-3 bg-green-400 rounded-full animate-bounce pointer-events-none"
|
|
style={{ zIndex: -1 }}
|
|
data-oid="1t6b6v9"
|
|
></div>
|
|
</div>
|
|
);
|
|
}
|