From 9512c6ceda2565d0e451e9502c6339af1b51a172 Mon Sep 17 00:00:00 2001 From: mARTin Date: Tue, 10 Mar 2026 15:35:39 +0100 Subject: [PATCH] fix: UserMenu dropdown invisible in dark mode and clipped by overflow Rewrote UserMenu to use CSS variables and portal rendering instead of hardcoded Tailwind classes. Added localized labels for all 5 languages. v1.22.2 --- package.json | 2 +- src/components/UserMenu.tsx | 207 +++++++++++++++++++++++++--------- src/components/WeeklyView.tsx | 1 + 3 files changed, 154 insertions(+), 56 deletions(-) diff --git a/package.json b/package.json index 7f389f9..21f7658 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.22.1", + "version": "1.22.2", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "main": "index.js", "scripts": { diff --git a/src/components/UserMenu.tsx b/src/components/UserMenu.tsx index 4c76597..29e4505 100644 --- a/src/components/UserMenu.tsx +++ b/src/components/UserMenu.tsx @@ -1,84 +1,181 @@ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef, useEffect, useCallback } from 'react'; +import { createPortal } from 'react-dom'; import { signOut } from 'next-auth/react'; interface UserMenuProps { userEmail?: string | null; onOpenSettings: () => void; trigger?: React.ReactNode; + language?: string; } -export default function UserMenu({ userEmail, onOpenSettings, trigger }: UserMenuProps) { +export default function UserMenu({ userEmail, onOpenSettings, trigger, language = 'en' }: UserMenuProps) { const [isOpen, setIsOpen] = useState(false); const menuRef = useRef(null); + const triggerRef = useRef(null); + const [position, setPosition] = useState<{ top: number; right: number } | null>(null); + + const labels = { + en: { signedIn: 'Signed in as', settings: 'Settings', signOut: 'Sign Out' }, + de: { signedIn: 'Angemeldet als', settings: 'Einstellungen', signOut: 'Abmelden' }, + fr: { signedIn: 'Connecté en tant que', settings: 'Paramètres', signOut: 'Se déconnecter' }, + es: { signedIn: 'Conectado como', settings: 'Ajustes', signOut: 'Cerrar sesión' }, + it: { signedIn: 'Connesso come', settings: 'Impostazioni', signOut: 'Esci' }, + }[language] || { signedIn: 'Signed in as', settings: 'Settings', signOut: 'Sign Out' }; + + const updatePosition = useCallback(() => { + if (triggerRef.current) { + const rect = triggerRef.current.getBoundingClientRect(); + setPosition({ + top: rect.bottom + 8, + right: window.innerWidth - rect.right, + }); + } + }, []); useEffect(() => { + if (!isOpen) return; + updatePosition(); + window.addEventListener('resize', updatePosition); + window.addEventListener('scroll', updatePosition, true); + return () => { + window.removeEventListener('resize', updatePosition); + window.removeEventListener('scroll', updatePosition, true); + }; + }, [isOpen, updatePosition]); + + useEffect(() => { + if (!isOpen) return; const handleClickOutside = (event: MouseEvent) => { - if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + if ( + menuRef.current && !menuRef.current.contains(event.target as Node) && + triggerRef.current && !triggerRef.current.contains(event.target as Node) + ) { setIsOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); - }, []); + }, [isOpen]); - return ( -
- {trigger ? ( -
setIsOpen(!isOpen)}>{trigger}
- ) : ( - - )} + const menuContent = isOpen && position ? createPortal( +
+
+

{labels.signedIn}

+

{userEmail || 'User'}

+
- {isOpen && ( -
-
-

Signed in as

-

{userEmail || 'User'}

-
+ - +
+ -
- - -
- )} - -
+
, + document.body + ) : null; + + return ( + <> +
+ {trigger ? ( +
setIsOpen(!isOpen)}>{trigger}
+ ) : ( + + )} +
+ {menuContent} + ); } diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index c559c2b..51f7118 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -5140,6 +5140,7 @@ export default function WeeklyView() { setShowSettings(true)} + language={profile.language} trigger={