language-switcher.tsx 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. 'use client';
  2. import { useState, useRef, useEffect } from 'react';
  3. import { useI18n } from '@/lib/hooks/use-i18n';
  4. import { supportedLocales } from '@/lib/i18n';
  5. import { cn } from '@/lib/utils';
  6. interface LanguageSwitcherProps {
  7. /** Called when the dropdown opens, so parent can close sibling dropdowns */
  8. onOpen?: () => void;
  9. }
  10. export function LanguageSwitcher({ onOpen }: LanguageSwitcherProps) {
  11. const { locale, setLocale } = useI18n();
  12. const [open, setOpen] = useState(false);
  13. const ref = useRef<HTMLDivElement>(null);
  14. // Close on click outside
  15. useEffect(() => {
  16. if (!open) return;
  17. const handler = (e: MouseEvent) => {
  18. if (ref.current && !ref.current.contains(e.target as Node)) {
  19. setOpen(false);
  20. }
  21. };
  22. document.addEventListener('mousedown', handler);
  23. return () => document.removeEventListener('mousedown', handler);
  24. }, [open]);
  25. return (
  26. <div className="relative" ref={ref}>
  27. <button
  28. onClick={() => {
  29. const next = !open;
  30. setOpen(next);
  31. if (next) onOpen?.();
  32. }}
  33. className="flex items-center gap-1 px-3 py-1.5 rounded-full text-xs font-bold text-gray-500 dark:text-gray-400 hover:bg-white dark:hover:bg-gray-700 hover:text-gray-800 dark:hover:text-gray-200 hover:shadow-sm transition-all"
  34. >
  35. {supportedLocales.find((l) => l.code === locale)?.shortLabel ?? locale}
  36. </button>
  37. {open && (
  38. <div className="absolute top-full mt-2 right-0 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg overflow-hidden z-50 min-w-[120px]">
  39. {supportedLocales.map((l) => (
  40. <button
  41. key={l.code}
  42. onClick={() => {
  43. setLocale(l.code);
  44. setOpen(false);
  45. }}
  46. className={cn(
  47. 'w-full px-4 py-2 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors',
  48. locale === l.code &&
  49. 'bg-purple-50 dark:bg-purple-900/20 text-purple-600 dark:text-purple-400',
  50. )}
  51. >
  52. {l.label}
  53. </button>
  54. ))}
  55. </div>
  56. )}
  57. </div>
  58. );
  59. }