import React, { useState, useEffect } from 'react'; import { Menu, Button, ScrollArea, ActionIcon, Tooltip } from '@mantine/core'; import { useTranslation } from 'react-i18next'; import { supportedLanguages } from '../../i18n'; import LocalIcon from './LocalIcon'; import styles from './LanguageSelector.module.css'; // Types interface LanguageSelectorProps { position?: React.ComponentProps['position']; offset?: number; compact?: boolean; // icon-only trigger } interface LanguageOption { value: string; label: string; } interface RippleEffect { x: number; y: number; key: number; } // Sub-components interface LanguageItemProps { option: LanguageOption; index: number; animationTriggered: boolean; isSelected: boolean; onClick: (event: React.MouseEvent) => void; rippleEffect?: RippleEffect | null; pendingLanguage: string | null; compact: boolean; disabled?: boolean; } const LanguageItem: React.FC = ({ option, index, animationTriggered, isSelected, onClick, rippleEffect, pendingLanguage, compact, disabled = false }) => { const { t } = useTranslation(); const label = disabled ? (

{option.label}

) : (

{option.label}

); return (
); }; const RippleStyles: React.FC = () => ( ); // Main component const LanguageSelector: React.FC = ({ position = 'bottom-start', offset = 8, compact = false }) => { const { i18n } = useTranslation(); const [opened, setOpened] = useState(false); const [animationTriggered, setAnimationTriggered] = useState(false); const [pendingLanguage, setPendingLanguage] = useState(null); const [rippleEffect, setRippleEffect] = useState(null); const languageOptions: LanguageOption[] = Object.entries(supportedLanguages) .sort(([, nameA], [, nameB]) => nameA.localeCompare(nameB)) .map(([code, name]) => ({ value: code, label: name, })); const handleLanguageChange = (value: string, event: React.MouseEvent) => { // Create ripple effect at click position (only for button mode) if (!compact) { const rect = (event.currentTarget as HTMLElement).getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; setRippleEffect({ x, y, key: Date.now() }); } // Start transition animation setPendingLanguage(value); // Simulate processing time for smooth transition setTimeout(() => { i18n.changeLanguage(value); setTimeout(() => { setPendingLanguage(null); setOpened(false); // Clear ripple effect setTimeout(() => setRippleEffect(null), 100); }, 300); }, 200); }; const currentLanguage = supportedLanguages[i18n.language as keyof typeof supportedLanguages] || supportedLanguages['en-GB']; // Trigger animation when dropdown opens useEffect(() => { if (opened) { setAnimationTriggered(false); // Small delay to ensure DOM is ready setTimeout(() => setAnimationTriggered(true), 50); } }, [opened]); return ( <> {compact ? ( ) : ( )}
{languageOptions.map((option, index) => { const isEnglishGB = option.value === 'en-GB'; // Currently only English GB has enough translations to use const isDisabled = !isEnglishGB; return ( handleLanguageChange(option.value, event)} rippleEffect={rippleEffect} pendingLanguage={pendingLanguage} compact={compact} disabled={isDisabled} /> ); })}
); }; export default LanguageSelector; export type { LanguageSelectorProps, LanguageOption, RippleEffect };