feat(ui): show playing status in search and artist tracks

cx
This commit is contained in:
vicrodh committed 2026-01-18 05:46:57 -06:00
1 parent c6f86a9bcc
commit d39180bd6c
3 files changed
+314 -15

No files matched your search

@@ -1,10 +1,11 @@
<script lang="ts">
import { invoke } from '@tauri-apps/api/core';
import { ArrowLeft, User, ChevronDown, ChevronUp, Play, Music, Heart, Search, X, ChevronLeft, ChevronRight } from 'lucide-svelte';
import { ArrowLeft, User, ChevronDown, ChevronUp, Play, Pause, Music, Heart, Search, X, ChevronLeft, ChevronRight } from 'lucide-svelte';
import type { ArtistDetail, QobuzArtist } from '$lib/types';
import AlbumCard from '../AlbumCard.svelte';
import TrackMenu from '../TrackMenu.svelte';
import { setPlaybackContext } from '$lib/stores/playbackContextStore';
import { togglePlay } from '$lib/stores/playerStore';
interface Track {
id: number;
@@ -69,6 +70,8 @@
onTrackShareSonglink?: (track: Track) => void;
onTrackGoToAlbum?: (albumId: string) => void;
onTrackGoToArtist?: (artistId: number) => void;
activeTrackId?: number | null;
isPlaybackActive?: boolean;
}
let {
@@ -96,7 +99,9 @@
onTrackShareQobuz,
onTrackShareSonglink,
onTrackGoToAlbum,
onTrackGoToArtist
onTrackGoToArtist,
activeTrackId = null,
isPlaybackActive = false
}: Props = $props();
let bioExpanded = $state(false);
@@ -298,6 +303,11 @@
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
function handlePausePlayback(event: MouseEvent) {
event.stopPropagation();
void togglePlay();
}
function handleTrackPlay(track: Track, trackIndex?: number) {
// Create artist top tracks context
if (topTracks.length > 0) {
@@ -763,8 +773,10 @@
{:else}
<div class="tracks-list">
{#each topTracks as track, index}
{@const isActiveTrack = isPlaybackActive && activeTrackId === track.id}
<div
class="track-row"
class:playing={isActiveTrack}
role="button"
tabindex="0"
onclick={() => handleTrackPlay(track, index)}
@@ -779,6 +791,29 @@
<Music size={16} />
</div>
{/if}
<button
class="track-play-overlay"
class:is-playing={isActiveTrack}
onclick={(event) => {
if (isActiveTrack) {
handlePausePlayback(event);
} else {
event.stopPropagation();
handleTrackPlay(track, index);
}
}}
aria-label={isActiveTrack ? 'Pause track' : 'Play track'}
>
<span class="play-icon" aria-hidden="true">
<Play size={18} />
</span>
<div class="playing-indicator" aria-hidden="true">
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
</div>
<Pause size={18} class="pause-icon" aria-hidden="true" />
</button>
</div>
<div class="track-info">
<div class="track-title">{track.title}</div>
@@ -1671,6 +1706,7 @@
border-radius: 4px;
overflow: hidden;
flex-shrink: 0;
position: relative;
}
.track-artwork img {
@@ -1689,6 +1725,90 @@
color: var(--text-muted);
}
.track-play-overlay {
position: absolute;
inset: 0;
display: none;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.6);
border: none;
cursor: pointer;
transition: background 150ms ease;
}
.track-row:hover .track-play-overlay {
display: flex;
}
.track-row.playing .track-play-overlay {
display: flex;
}
.track-play-overlay:hover {
background: rgba(0, 0, 0, 0.75);
}
.track-play-overlay .playing-indicator,
.track-play-overlay .pause-icon {
display: none;
}
.track-row.playing .track-play-overlay .play-icon {
display: none;
}
.track-row.playing .track-play-overlay .playing-indicator {
display: flex;
}
.track-row.playing:hover .track-play-overlay .playing-indicator {
display: none;
}
.track-row.playing:hover .track-play-overlay .pause-icon {
display: inline-flex;
}
.playing-indicator {
display: flex;
align-items: center;
gap: 2px;
}
.playing-indicator .bar {
width: 3px;
background-color: var(--accent-primary);
border-radius: 9999px;
transform-origin: bottom;
animation: artist-equalize 1s ease-in-out infinite;
}
.playing-indicator .bar:nth-child(1) {
height: 10px;
}
.playing-indicator .bar:nth-child(2) {
height: 14px;
animation-delay: 0.15s;
}
.playing-indicator .bar:nth-child(3) {
height: 8px;
animation-delay: 0.3s;
}
@keyframes artist-equalize {
0%, 100% {
transform: scaleY(0.5);
opacity: 0.7;
}
50% {
transform: scaleY(1);
opacity: 1;
}
}
.track-info {
flex: 1;
min-width: 0;