mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 06:25:41 +00:00
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:
@@ -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')}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user