diff --git a/dashboard/src/components/Layout.css b/dashboard/src/components/Layout.css index 0db1288f..b54d046d 100644 --- a/dashboard/src/components/Layout.css +++ b/dashboard/src/components/Layout.css @@ -195,6 +195,64 @@ color: var(--text-primary); } +.language-menu { + position: relative; +} + +.language-menu .theme-toggle-btn { + width: 100%; +} + +.language-menu-list { + position: absolute; + left: 0; + right: 0; + bottom: calc(100% + 0.35rem); + display: flex; + flex-direction: column; + gap: 0.15rem; + min-width: 160px; + padding: 0.3rem; + background: var(--bg-white); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: 0 10px 30px rgba(15, 23, 42, 0.16); + z-index: 120; +} + +.language-menu-item { + display: flex; + align-items: center; + width: 100%; + min-height: 34px; + padding: 0.45rem 0.6rem; + color: var(--text-secondary); + background: transparent; + border: 0; + border-radius: calc(var(--radius) - 2px); + font-size: 0.875rem; + font-weight: 500; + text-align: left; + cursor: pointer; + transition: all 0.2s; +} + +.language-menu-item:hover { + background: var(--bg-light); + color: var(--text-primary); +} + +.language-menu-item.active { + background: rgba(37, 211, 102, 0.1); + color: var(--primary); +} + +.sidebar.collapsed .language-menu-list { + right: auto; + left: calc(100% + 0.5rem); + bottom: 0; +} + .logout-btn { display: flex; align-items: center; @@ -352,6 +410,15 @@ transform: scaleX(-1); } +[dir="rtl"] .language-menu-item { + text-align: right; +} + +[dir="rtl"] .sidebar.collapsed .language-menu-list { + right: calc(100% + 0.5rem); + left: auto; +} + [dir="rtl"] .main-content { margin-left: 0; margin-right: 260px; diff --git a/dashboard/src/components/Layout.tsx b/dashboard/src/components/Layout.tsx index f2815bb8..dc32cd56 100644 --- a/dashboard/src/components/Layout.tsx +++ b/dashboard/src/components/Layout.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { NavLink, Outlet } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { @@ -22,7 +22,7 @@ import { } from 'lucide-react'; import { useTheme } from '../hooks/useTheme'; import { type UserRole } from '../hooks/useRole'; -import { supportedLanguages, type SupportedLanguage } from '../i18n'; +import { languageOptions, resolveSupportedLanguage, type SupportedLanguage } from '../i18n'; import './Layout.css'; interface LayoutProps { @@ -54,6 +54,8 @@ export function Layout({ onLogout, userRole }: LayoutProps) { const [isCollapsed, setIsCollapsed] = useState(false); const [isMobileOpen, setIsMobileOpen] = useState(false); const [isMobile, setIsMobile] = useState(window.innerWidth < 768); + const [isLanguageMenuOpen, setIsLanguageMenuOpen] = useState(false); + const languageMenuRef = useRef(null); useEffect(() => { const handleResize = () => { @@ -76,16 +78,35 @@ export function Layout({ onLogout, userRole }: LayoutProps) { }; }, [isMobileOpen]); + useEffect(() => { + if (!isLanguageMenuOpen) return; + + const closeOnOutsideClick = (event: MouseEvent) => { + if (!languageMenuRef.current?.contains(event.target as Node)) { + setIsLanguageMenuOpen(false); + } + }; + const closeOnEscape = (event: KeyboardEvent) => { + if (event.key === 'Escape') setIsLanguageMenuOpen(false); + }; + + document.addEventListener('mousedown', closeOnOutsideClick); + document.addEventListener('keydown', closeOnEscape); + return () => { + document.removeEventListener('mousedown', closeOnOutsideClick); + document.removeEventListener('keydown', closeOnEscape); + }; + }, [isLanguageMenuOpen]); + const toggleCollapse = () => setIsCollapsed(!isCollapsed); const toggleMobile = () => setIsMobileOpen(!isMobileOpen); - const currentLang = (i18n.resolvedLanguage || i18n.language || 'en').split('-')[0] as SupportedLanguage; - const cycleLanguage = () => { - const idx = supportedLanguages.indexOf(currentLang); - const next = supportedLanguages[(idx + 1) % supportedLanguages.length]; - void i18n.changeLanguage(next); + const currentLang = resolveSupportedLanguage(i18n.resolvedLanguage || i18n.language); + const languageLabel = languageOptions.find(option => option.value === currentLang)?.compactLabel ?? 'EN'; + const changeLanguage = (language: SupportedLanguage) => { + setIsLanguageMenuOpen(false); + void i18n.changeLanguage(language); }; - const languageLabel = currentLang === 'he' ? 'עברית' : 'EN'; const isRtl = currentLang === 'he'; return ( @@ -151,15 +172,34 @@ export function Layout({ onLogout, userRole }: LayoutProps) {
- +
+ + {isLanguageMenuOpen && ( +
+ {languageOptions.map(option => ( + + ))} +
+ )} +
+ +
+ + +
+