Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | 2x 2x 27x 27x 27x 27x 27x 1x 1x 1x 27x 27x 1x 1x 27x 27x 27x 12x 1x | import React, {useEffect, useRef, useState} from "react"; import {useTranslation} from "react-i18next"; const flags = { en: "https://flagcdn.com/w20/gb.png", it: "https://flagcdn.com/w20/it.png", }; const languageLabels = { en: "English", it: "Italiano", }; /** * LanguageSwitcher component provides a dropdown button to switch between supported languages. * * Shows current language with corresponding flag and allows user to select a different language * from a dropdown menu. On selection, the language changes via `i18n.changeLanguage`. * * Supported languages: English ("en") and Italian ("it"). * * @component * @module components/ui/LanguageSwitcher * @returns {JSX.Element} The language switcher UI. */ export default function LanguageSwitcher() { const {i18n} = useTranslation(); const [open, setOpen] = useState(false); const dropdownRef = useRef(null); const toggleDropdown = () => setOpen((prev) => !prev); const handleChange = (lang) => { Eif (lang !== i18n.language) { i18n.changeLanguage(lang).catch(console.error); } setOpen(false); }; useEffect(() => { const handleClickOutside = (event) => { Eif (dropdownRef.current && !dropdownRef.current.contains(event.target)) { setOpen(false); } }; if (open) document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, [open]); return ( <div ref={dropdownRef} className="relative inline-block text-left z-20"> <button onClick={toggleDropdown} aria-haspopup="true" aria-expanded={open} aria-label="Seleziona lingua" className="flex items-center gap-2 rounded bg-gray-200 px-3 py-1 text-sm font-medium text-gray-900 hover:ring-2 hover:ring-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white dark:hover:ring-blue-300 transition min-w-[5.5rem] sm:min-w-[6.5rem]" type="button" > <img src={flags[i18n.language]} alt={`${i18n.language} flag`} className="w-5 h-auto rounded-sm shadow" loading="lazy" /> <span className="uppercase select-none">{i18n.language}</span> <svg className={`w-4 h-4 transition-transform ${open ? "rotate-180" : "rotate-0"}`} fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" > <path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7"></path> </svg> </button> {open && ( <div className="absolute right-0 mt-2 w-full max-w-[12rem] sm:max-w-[16rem] origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-gray-800 animate-fade-in" role="menu" aria-label="Selezione lingua" style={{zIndex: 9999}} > {Object.entries(flags).map(([lang, flagUrl]) => ( <button key={lang} onClick={() => handleChange(lang)} className={` flex w-full items-center gap-3 px-4 py-3 text-base text-gray-900 hover:bg-gray-100 focus:bg-gray-100 dark:text-white dark:hover:bg-gray-700 dark:focus:bg-gray-700 transition-colors truncate ${lang === i18n.language ? "font-semibold bg-gray-200 dark:bg-gray-700 cursor-default" : "cursor-pointer"} `} role="menuitem" type="button" disabled={lang === i18n.language} aria-current={lang === i18n.language ? "true" : undefined} style={{minHeight: 44}} // Touch target min 44px height > <img src={flagUrl} alt={`${lang} flag`} className="w-6 h-auto rounded-sm shadow" loading="lazy" /> <div className="flex flex-col text-left"> <span className="text-sm font-semibold uppercase">{lang}</span> <span className="text-xs text-gray-500 dark:text-gray-400">{languageLabels[lang]}</span> </div> </button> ))} </div> )} </div> ); } |