67 lines
3.2 KiB
TypeScript
67 lines
3.2 KiB
TypeScript
'use client';
|
|
|
|
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react';
|
|
import { ChevronDownIcon } from '@heroicons/react/20/solid';
|
|
import { useTranslation } from '../i18n/useTranslation';
|
|
|
|
// Built-in language info (code → name + flag emoji)
|
|
const BUILTIN_LANG_INFO: Record<string, { name: string; flag: string }> = {
|
|
en: { name: 'English', flag: '🇬🇧' },
|
|
de: { name: 'Deutsch', flag: '🇩🇪' },
|
|
};
|
|
|
|
interface LangEntry { code: string; name: string; flag: string }
|
|
|
|
interface LanguageSwitcherProps {
|
|
variant?: 'light' | 'dark';
|
|
}
|
|
|
|
export default function LanguageSwitcher({ variant = 'light' }: LanguageSwitcherProps) {
|
|
const { language, setLanguage, customI18n } = useTranslation();
|
|
|
|
// Combine built-in + custom languages (deduplicated by code)
|
|
const allLangs: LangEntry[] = [
|
|
...Object.entries(BUILTIN_LANG_INFO).map(([code, info]) => ({ code, ...info })),
|
|
...customI18n.languages
|
|
.filter((l) => !BUILTIN_LANG_INFO[l.code])
|
|
.map((l) => ({ code: l.code, name: l.name, flag: l.flag ?? '🏳️' })),
|
|
];
|
|
|
|
const activeLang: LangEntry =
|
|
allLangs.find((l) => l.code === language) ?? { code: language, name: language, flag: '🏳️' };
|
|
|
|
const buttonCls =
|
|
variant === 'dark'
|
|
? 'inline-flex items-center gap-x-1.5 rounded-md bg-white/10 px-3 py-2 text-sm font-semibold text-white hover:bg-white/20 transition-colors'
|
|
: 'inline-flex items-center gap-x-1.5 rounded-md bg-gray-100 px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-gray-300 hover:bg-gray-200 transition-colors';
|
|
|
|
const menuCls =
|
|
variant === 'dark'
|
|
? 'absolute right-0 z-50 mt-2 w-52 origin-top-right rounded-xl bg-gray-800/95 backdrop-blur-sm border border-white/10 shadow-2xl py-1 transition data-closed:scale-95 data-closed:opacity-0 data-enter:duration-100 data-enter:ease-out data-leave:duration-75 data-leave:ease-in'
|
|
: 'absolute right-0 z-50 mt-2 w-52 origin-top-right rounded-xl bg-white border border-gray-200 shadow-xl py-1 transition data-closed:scale-95 data-closed:opacity-0 data-enter:duration-100 data-enter:ease-out data-leave:duration-75 data-leave:ease-in';
|
|
|
|
const itemCls = (isActive: boolean) =>
|
|
variant === 'dark'
|
|
? `flex w-full items-center gap-3 px-4 py-2.5 text-sm transition-colors ${isActive ? 'bg-[#8D6B1D] text-white' : 'text-gray-200 hover:bg-white/10 hover:text-white'}`
|
|
: `flex w-full items-center gap-3 px-4 py-2.5 text-sm transition-colors ${isActive ? 'bg-[#8D6B1D] text-white' : 'text-gray-700 hover:bg-gray-50 hover:text-gray-900'}`;
|
|
|
|
return (
|
|
<Menu as="div" className="relative inline-block">
|
|
<MenuButton className={buttonCls}>
|
|
<span>{activeLang.name}</span>
|
|
<ChevronDownIcon aria-hidden="true" className="size-4 opacity-60" />
|
|
</MenuButton>
|
|
|
|
<MenuItems transition className={menuCls}>
|
|
{allLangs.map((lang) => (
|
|
<MenuItem key={lang.code}>
|
|
<button onClick={() => setLanguage(lang.code)} className={itemCls(language === lang.code)}>
|
|
<span className="flex-1 text-left">{lang.name}</span>
|
|
{language === lang.code && <span className="text-xs font-bold">✓</span>}
|
|
</button>
|
|
</MenuItem>
|
|
))}
|
|
</MenuItems>
|
|
</Menu>
|
|
);
|
|
} |