fix(queue): marquee long track names + dedupe now-playing bars
Queue sidebar no longer scrolls horizontally on long titles/artists: text now ping-pong scrolls (news-ticker style) only when it overflows, via a new Marquee component; .qd-scroll also clips overflow-x. The current track previously showed the playing-bars indicator both in place of the drag grip and over the cover. Keep only the cover overlay and restore the drag grip on the current row. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
import { useLayoutEffect, useRef, useState, type CSSProperties } from 'react';
|
||||||
|
|
||||||
|
/** Single-line text that ping-pong scrolls (like a news ticker) only when it
|
||||||
|
* overflows its container, otherwise renders as static clipped text. Keeps the
|
||||||
|
* queue panel from ever growing a horizontal scrollbar on long titles. */
|
||||||
|
export function Marquee({
|
||||||
|
text,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
text: string;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
const ref = useRef<HTMLSpanElement>(null);
|
||||||
|
const [shift, setShift] = useState(0);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const el = ref.current;
|
||||||
|
if (!el) return;
|
||||||
|
const measure = () => {
|
||||||
|
const inner = el.firstElementChild as HTMLElement | null;
|
||||||
|
const overflow = (inner?.scrollWidth ?? 0) - el.clientWidth;
|
||||||
|
setShift(overflow > 1 ? overflow : 0);
|
||||||
|
};
|
||||||
|
measure();
|
||||||
|
const ro = new ResizeObserver(measure);
|
||||||
|
ro.observe(el);
|
||||||
|
return () => ro.disconnect();
|
||||||
|
}, [text]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
ref={ref}
|
||||||
|
className={`marquee${shift ? ' on' : ''}${className ? ` ${className}` : ''}`}
|
||||||
|
style={shift ? ({ '--mq-shift': `-${shift}px` } as CSSProperties) : undefined}
|
||||||
|
>
|
||||||
|
<span className="marquee-inner">{text}</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
import { Icon } from '../common/Icon';
|
import { Icon } from '../common/Icon';
|
||||||
import { ArtTile } from '../common/ArtTile';
|
import { ArtTile } from '../common/ArtTile';
|
||||||
|
import { Marquee } from '../common/Marquee';
|
||||||
import { PlayingIndicator } from '../common/PlayingIndicator';
|
import { PlayingIndicator } from '../common/PlayingIndicator';
|
||||||
import { useAppDispatch, useAppSelector } from '../../hooks/useAppDispatch';
|
import { useAppDispatch, useAppSelector } from '../../hooks/useAppDispatch';
|
||||||
import {
|
import {
|
||||||
@@ -259,13 +260,9 @@ function QueueRow({
|
|||||||
onDoubleClick={onPlay}
|
onDoubleClick={onPlay}
|
||||||
title={t('queue.doubleClickPlay')}
|
title={t('queue.doubleClickPlay')}
|
||||||
>
|
>
|
||||||
{isCurrent ? (
|
<span className="grip" {...attributes} {...listeners}>
|
||||||
<PlayingIndicator animate={isPlaying} />
|
<Icon name="dots-six-vertical" />
|
||||||
) : (
|
</span>
|
||||||
<span className="grip" {...attributes} {...listeners}>
|
|
||||||
<Icon name="dots-six-vertical" />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<div className="qart">
|
<div className="qart">
|
||||||
<ArtTile seed={albumTitle} size={36} label={albumTitle} src={artUrl} />
|
<ArtTile seed={albumTitle} size={36} label={albumTitle} src={artUrl} />
|
||||||
{isCurrent && (
|
{isCurrent && (
|
||||||
@@ -275,8 +272,8 @@ function QueueRow({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="qt">
|
<div className="qt">
|
||||||
<div className="t">{resolved?.title ?? entry.title}</div>
|
<Marquee className="t" text={resolved?.title ?? entry.title} />
|
||||||
<div className="r">{resolved?.artistName ?? entry.artistName}</div>
|
<Marquee className="r" text={resolved?.artistName ?? entry.artistName} />
|
||||||
</div>
|
</div>
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuTrigger asChild>
|
<MenuTrigger asChild>
|
||||||
|
|||||||
+36
-12
@@ -651,6 +651,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
padding: 12px 12px 18px;
|
padding: 12px 12px 18px;
|
||||||
}
|
}
|
||||||
.qrow {
|
.qrow {
|
||||||
@@ -700,23 +701,46 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--fg-1);
|
color: var(--fg-1);
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
.qrow .qt .r {
|
.qrow .qt .r {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--fg-3);
|
color: var(--fg-3);
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
}
|
||||||
.qrow .qt .r .ph {
|
|
||||||
color: var(--lime);
|
/* News-ticker text: clips by default, ping-pong scrolls only when it overflows
|
||||||
font-size: 11px;
|
(the .on class is set by the Marquee component after measuring). */
|
||||||
|
.marquee {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.marquee-inner {
|
||||||
|
display: inline-block;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
.marquee.on .marquee-inner {
|
||||||
|
max-width: none;
|
||||||
|
animation: marquee-pingpong 9s ease-in-out infinite alternate;
|
||||||
|
}
|
||||||
|
@keyframes marquee-pingpong {
|
||||||
|
0%,
|
||||||
|
12% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
88%,
|
||||||
|
100% {
|
||||||
|
transform: translateX(var(--mq-shift, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.marquee.on .marquee-inner {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.qd-radio {
|
.qd-radio {
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
|
|||||||
Reference in New Issue
Block a user