fix(fullscreen): lyrics menu toggle + readable panel

Clicking the mic button now toggles the lyrics settings panel instead
of outside-handler closing it and click re-opening it — trigger ref is
excluded from the outside-click check.

Panel is now a solid surface (no backdrop-blur, near-opaque background,
higher-contrast button text) so settings stay readable over the busy
fullscreen background.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-04-18 22:48:21 +02:00
parent 8b7bce5b85
commit 25537f2743
2 changed files with 21 additions and 17 deletions
+12 -6
View File
@@ -502,8 +502,9 @@ interface FsLyricsMenuProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
accentColor: string | null; accentColor: string | null;
triggerRef?: React.RefObject<HTMLElement | null>;
} }
const FsLyricsMenu = memo(function FsLyricsMenu({ open, onClose, accentColor }: FsLyricsMenuProps) { const FsLyricsMenu = memo(function FsLyricsMenu({ open, onClose, accentColor, triggerRef }: FsLyricsMenuProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const showLyrics = useAuthStore(s => s.showFullscreenLyrics); const showLyrics = useAuthStore(s => s.showFullscreenLyrics);
const lyricsStyle = useAuthStore(s => s.fsLyricsStyle); const lyricsStyle = useAuthStore(s => s.fsLyricsStyle);
@@ -512,13 +513,16 @@ const FsLyricsMenu = memo(function FsLyricsMenu({ open, onClose, accentColor }:
const panelRef = useRef<HTMLDivElement>(null); const panelRef = useRef<HTMLDivElement>(null);
// Close on click outside the panel or on Escape. // Close on click outside the panel or on Escape.
// setTimeout(0) defers listener registration past the current click cycle // Ignore clicks on the trigger button so re-clicking it toggles normally
// so the button click that opens the panel doesn't immediately close it. // instead of outside-handler closing + click re-opening.
useEffect(() => { useEffect(() => {
if (!open) return; if (!open) return;
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
const onMouse = (e: MouseEvent) => { const onMouse = (e: MouseEvent) => {
if (panelRef.current && !panelRef.current.contains(e.target as Node)) onClose(); const target = e.target as Node;
if (panelRef.current?.contains(target)) return;
if (triggerRef?.current?.contains(target)) return;
onClose();
}; };
window.addEventListener('keydown', onKey); window.addEventListener('keydown', onKey);
const t = setTimeout(() => window.addEventListener('mousedown', onMouse), 0); const t = setTimeout(() => window.addEventListener('mousedown', onMouse), 0);
@@ -527,7 +531,7 @@ const FsLyricsMenu = memo(function FsLyricsMenu({ open, onClose, accentColor }:
window.removeEventListener('keydown', onKey); window.removeEventListener('keydown', onKey);
window.removeEventListener('mousedown', onMouse); window.removeEventListener('mousedown', onMouse);
}; };
}, [open, onClose]); }, [open, onClose, triggerRef]);
if (!open) return null; if (!open) return null;
@@ -702,6 +706,7 @@ export default function FullscreenPlayer({ onClose }: FullscreenPlayerProps) {
// Lyrics settings popover state // Lyrics settings popover state
const [lyricsMenuOpen, setLyricsMenuOpen] = useState(false); const [lyricsMenuOpen, setLyricsMenuOpen] = useState(false);
const closeLyricsMenu = useCallback(() => setLyricsMenuOpen(false), []); const closeLyricsMenu = useCallback(() => setLyricsMenuOpen(false), []);
const lyricsMenuTriggerRef = useRef<HTMLButtonElement>(null);
// Idle-fade system — hides controls after 3 s of inactivity // Idle-fade system — hides controls after 3 s of inactivity
const [isIdle, setIsIdle] = useState(false); const [isIdle, setIsIdle] = useState(false);
@@ -838,8 +843,9 @@ export default function FullscreenPlayer({ onClose }: FullscreenPlayerProps) {
</button> </button>
)} )}
<div style={{ position: 'relative', zIndex: 9 }}> <div style={{ position: 'relative', zIndex: 9 }}>
<FsLyricsMenu open={lyricsMenuOpen} onClose={closeLyricsMenu} accentColor={dynamicAccent} /> <FsLyricsMenu open={lyricsMenuOpen} onClose={closeLyricsMenu} accentColor={dynamicAccent} triggerRef={lyricsMenuTriggerRef} />
<button <button
ref={lyricsMenuTriggerRef}
className={`fs-btn fs-btn-sm${lyricsMenuOpen ? ' active' : ''}`} className={`fs-btn fs-btn-sm${lyricsMenuOpen ? ' active' : ''}`}
onClick={() => setLyricsMenuOpen(v => !v)} onClick={() => setLyricsMenuOpen(v => !v)}
aria-label={t('player.fsLyricsToggle')} aria-label={t('player.fsLyricsToggle')}
+9 -11
View File
@@ -3690,13 +3690,11 @@
right: 0; right: 0;
width: 230px; width: 230px;
z-index: 9; z-index: 9;
background: rgba(18, 18, 28, 0.88); background: #14141d;
backdrop-filter: blur(20px) saturate(1.4); border: 1px solid rgba(255, 255, 255, 0.14);
-webkit-backdrop-filter: blur(20px) saturate(1.4);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 14px; border-radius: 14px;
padding: 14px; padding: 14px;
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.65), 0 0 0 1px rgba(255, 255, 255, 0.04); box-shadow: 0 12px 44px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(255, 255, 255, 0.04);
animation: fslmIn 160ms cubic-bezier(0.22, 1, 0.36, 1) both; animation: fslmIn 160ms cubic-bezier(0.22, 1, 0.36, 1) both;
transform-origin: bottom right; transform-origin: bottom right;
} }
@@ -3737,18 +3735,18 @@
flex: 1; flex: 1;
padding: 8px 10px; padding: 8px 10px;
border-radius: 9px; border-radius: 9px;
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.14);
background: rgba(255, 255, 255, 0.05); background: rgba(255, 255, 255, 0.06);
color: rgba(255, 255, 255, 0.5); color: rgba(255, 255, 255, 0.78);
cursor: pointer; cursor: pointer;
text-align: left; text-align: left;
transition: border-color 150ms ease, background 150ms ease, color 150ms ease; transition: border-color 150ms ease, background 150ms ease, color 150ms ease;
} }
.fslm-style-btn:hover { .fslm-style-btn:hover {
border-color: rgba(255, 255, 255, 0.22); border-color: rgba(255, 255, 255, 0.3);
color: rgba(255, 255, 255, 0.75); color: #fff;
background: rgba(255, 255, 255, 0.08); background: rgba(255, 255, 255, 0.1);
} }
.fslm-style-active { .fslm-style-active {