From aa89f0c9327cfecba4e537b38d26d512a2d2f296 Mon Sep 17 00:00:00 2001 From: ranokay Date: Sun, 23 Aug 2026 05:53:39 +0300 Subject: [PATCH] fix(ui): close source popover with lyrics surface Signed-off-by: ranokay --- ui/src/audioplayer/LyricsLayerControls.jsx | 7 ++-- ui/src/audioplayer/LyricsSidebar.jsx | 1 + ui/src/audioplayer/LyricsSidebar.test.jsx | 21 ++++++++++++ .../MobileKaraokeLyricsPortal.test.jsx | 33 +++++++++++++++++++ ui/src/audioplayer/usePlayerLyrics.jsx | 1 + 5 files changed, 60 insertions(+), 3 deletions(-) diff --git a/ui/src/audioplayer/LyricsLayerControls.jsx b/ui/src/audioplayer/LyricsLayerControls.jsx index bcb396e44..449a704d9 100644 --- a/ui/src/audioplayer/LyricsLayerControls.jsx +++ b/ui/src/audioplayer/LyricsLayerControls.jsx @@ -176,13 +176,14 @@ const LyricsLayerControls = ({ onToggleTranslation, onTogglePronunciation, source, + active = true, labels = {}, testId = 'lyrics-layer-controls', }) => { const classes = useStyles() const [sourceAnchor, setSourceAnchor] = useState(null) const sourcePopoverId = `${testId}-source-popover` - const sourceOpen = Boolean(source && sourceAnchor) + const sourceOpen = Boolean(active && source && sourceAnchor) const sourceDetails = [ { label: labels.provider || 'Provider', @@ -195,8 +196,8 @@ const LyricsLayerControls = ({ ].filter((detail) => detail.value) useEffect(() => { - if (!source) setSourceAnchor(null) - }, [source]) + if (!active || !source) setSourceAnchor(null) + }, [active, source]) return (
diff --git a/ui/src/audioplayer/LyricsSidebar.test.jsx b/ui/src/audioplayer/LyricsSidebar.test.jsx index b5023ca4a..0bafd3bcc 100644 --- a/ui/src/audioplayer/LyricsSidebar.test.jsx +++ b/ui/src/audioplayer/LyricsSidebar.test.jsx @@ -31,6 +31,12 @@ const lyric = { synced: true, line: [{ start: 0, end: 1000, value: 'Main line' }], } +const source = { + type: 'plugin', + name: 'Better Lyrics', + provider: 'ttml', + format: 'ttml', +} const originalPointerEvent = window.PointerEvent const ControlledLyricsSidebar = ({ @@ -184,6 +190,21 @@ describe('', () => { expect(sidebar).not.toHaveAttribute('inert') }) + it('closes the source popover when the queue obscures the sidebar', async () => { + const { rerender } = renderSidebar({ source }) + + fireEvent.click(screen.getByRole('button', { name: 'View lyrics source' })) + expect(screen.getByRole('dialog', { name: 'Lyrics source' })).toBeVisible() + + rerender(sidebarView({ source, obscuredByQueue: true })) + + await waitFor(() => + expect( + screen.queryByRole('dialog', { name: 'Lyrics source' }), + ).not.toBeInTheDocument(), + ) + }) + it('clamps controlled keyboard resizing to the available width', () => { const onWidthChange = vi.fn() renderSidebar({ initialWidth: 999, maxWidth: 435, onWidthChange }) diff --git a/ui/src/audioplayer/MobileKaraokeLyricsPortal.test.jsx b/ui/src/audioplayer/MobileKaraokeLyricsPortal.test.jsx index 2676db7f5..eb2748684 100644 --- a/ui/src/audioplayer/MobileKaraokeLyricsPortal.test.jsx +++ b/ui/src/audioplayer/MobileKaraokeLyricsPortal.test.jsx @@ -38,6 +38,22 @@ const { defaultLyricsResponse, useEnhancedLyricsMock } = vi.hoisted(() => { } }) +const sourcedLyricsResponse = { + ...defaultLyricsResponse, + layers: { + ...defaultLyricsResponse.layers, + main: { + ...defaultLyricsResponse.layers.main, + source: { + type: 'plugin', + name: 'Better Lyrics', + provider: 'ttml', + format: 'ttml', + }, + }, + }, +} + vi.mock('./useEnhancedLyrics', () => ({ default: useEnhancedLyricsMock, })) @@ -198,6 +214,23 @@ describe('', () => { expect(layer).toHaveStyle({ pointerEvents: 'auto' }) }) + it('closes the source popover when the queue obscures mobile lyrics', async () => { + useEnhancedLyricsMock.mockImplementation(() => sourcedLyricsResponse) + const host = createHost() + const { rerender } = render() + + fireEvent.click(screen.getByRole('button', { name: 'Toggle lyrics' })) + const sourceButton = await within(host).findByTestId('lyrics-source-button') + fireEvent.click(sourceButton) + expect(screen.getByRole('dialog')).toBeVisible() + + rerender() + + await waitFor(() => + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(), + ) + }) + it('attaches when the mobile cover host appears after activation', async () => { vi.useFakeTimers() diff --git a/ui/src/audioplayer/usePlayerLyrics.jsx b/ui/src/audioplayer/usePlayerLyrics.jsx index 32669facf..de8f2114c 100644 --- a/ui/src/audioplayer/usePlayerLyrics.jsx +++ b/ui/src/audioplayer/usePlayerLyrics.jsx @@ -195,6 +195,7 @@ const usePlayerLyrics = ({ onToggleTranslation={toggleTranslation} onTogglePronunciation={togglePronunciation} source={lyricsSource} + active={useInlineMobileLyrics && !obscuredByQueue} labels={labels} testId="lyrics-mobile-layer-controls" />