fix(ui): close source popover with lyrics surface

Signed-off-by: ranokay <github@ranokay.com>
This commit is contained in:
ranokay 2026-08-23 05:53:39 +03:00
commit aa89f0c932
No known key found for this signature in database
5 changed files with 60 additions and 3 deletions

View file

@ -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 (
<div

View file

@ -280,6 +280,7 @@ const LyricsSidebar = ({
onTogglePronunciation={onTogglePronunciation}
onToggleTranslation={onToggleTranslation}
source={source}
active={interactive}
labels={labels}
testId="lyrics-sidebar-floating-controls"
/>

View file

@ -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('<LyricsSidebar />', () => {
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 })

View file

@ -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('<MobileKaraokeLyricsPortal />', () => {
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(<MobileLyricsHarness />)
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(<MobileLyricsHarness obscuredByQueue />)
await waitFor(() =>
expect(screen.queryByRole('dialog')).not.toBeInTheDocument(),
)
})
it('attaches when the mobile cover host appears after activation', async () => {
vi.useFakeTimers()

View file

@ -195,6 +195,7 @@ const usePlayerLyrics = ({
onToggleTranslation={toggleTranslation}
onTogglePronunciation={togglePronunciation}
source={lyricsSource}
active={useInlineMobileLyrics && !obscuredByQueue}
labels={labels}
testId="lyrics-mobile-layer-controls"
/>