Skip to content

Commit c041858

Browse files
committed
feat: Implement a dedicated desktop queue panel and introduce a variant-specific song item UI for the queue.
1 parent 8f4667f commit c041858

4 files changed

Lines changed: 268 additions & 25 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { ListMusic } from "lucide-react"
2+
import { memo } from "react"
3+
import { Badge } from "@/components/ui/badge"
4+
import { usePlayerStore } from "@/stores/playerStore"
5+
import QueueTab from "./QueueTab"
6+
7+
const DesktopQueuePanel = memo(({ ready }) => {
8+
const playlistLength = usePlayerStore((s) => s.playlist.length)
9+
10+
return (
11+
<>
12+
<div className="dqp-panel">
13+
<div className="dqp-header">
14+
<div className="flex items-center gap-2.5">
15+
<ListMusic className="w-4 h-4 text-white/50" />
16+
<span className="text-sm font-semibold text-white/85 tracking-wide">Queue</span>
17+
<Badge
18+
variant="secondary"
19+
className="h-[18px] text-[10px] px-1.5 bg-white/8 text-white/50 border-0 font-medium"
20+
>
21+
{playlistLength}
22+
</Badge>
23+
</div>
24+
</div>
25+
26+
<div className="dqp-list">
27+
{ready ? (
28+
<QueueTab variant="desktop" />
29+
) : (
30+
<div className="flex items-center justify-center h-32">
31+
<div className="w-5 h-5 border-2 border-white/10 border-t-white/40 rounded-full animate-spin" />
32+
</div>
33+
)}
34+
</div>
35+
</div>
36+
37+
<style>{`
38+
.dqp-panel {
39+
position: absolute;
40+
top: 0;
41+
right: 0;
42+
bottom: 0;
43+
width: 300px;
44+
background: transparent;
45+
// border-left: 1px solid rgba(255, 255, 255, 0.04);
46+
display: flex;
47+
flex-direction: column;
48+
z-index: 25;
49+
}
50+
.dqp-header {
51+
display: flex;
52+
align-items: center;
53+
justify-content: space-between;
54+
padding: 16px 16px 12px 18px;
55+
flex-shrink: 0;
56+
}
57+
.dqp-list {
58+
flex: 1;
59+
overflow-y: auto;
60+
overflow-x: hidden;
61+
scrollbar-width: thin;
62+
scrollbar-color: rgba(255,255,255,0.08) transparent;
63+
}
64+
.dqp-list::-webkit-scrollbar {
65+
width: 4px;
66+
}
67+
.dqp-list::-webkit-scrollbar-track {
68+
background: transparent;
69+
}
70+
.dqp-list::-webkit-scrollbar-thumb {
71+
background: rgba(255,255,255,0.1);
72+
border-radius: 4px;
73+
}
74+
.dqp-list::-webkit-scrollbar-thumb:hover {
75+
background: rgba(255,255,255,0.18);
76+
}
77+
.dqp-item {
78+
opacity: 0.75;
79+
transition: opacity 0.2s ease, background 0.2s ease;
80+
}
81+
.dqp-item:hover {
82+
opacity: 1;
83+
background: rgba(255,255,255,0.04);
84+
}
85+
.dqp-item-active {
86+
background: rgba(255,255,255,0.06);
87+
opacity: 1;
88+
}
89+
.dqp-eq-bar {
90+
width: 2px;
91+
border-radius: 1px;
92+
background: rgba(255,255,255,0.7);
93+
}
94+
.dqp-eq-1 { animation: dqpEq1 1s ease-in-out infinite; }
95+
.dqp-eq-2 { animation: dqpEq2 1s ease-in-out infinite 0.15s; }
96+
.dqp-eq-3 { animation: dqpEq3 1s ease-in-out infinite 0.3s; }
97+
@keyframes dqpEq1 { 0%,100%{height:10px} 50%{height:16px} }
98+
@keyframes dqpEq2 { 0%,100%{height:14px} 50%{height:8px} }
99+
@keyframes dqpEq3 { 0%,100%{height:8px} 50%{height:14px} }
100+
`}</style>
101+
</>
102+
)
103+
})
104+
105+
DesktopQueuePanel.displayName = "DesktopQueuePanel"
106+
export default DesktopQueuePanel

client/src/Pages/Music/BottomPlayer/NowPlayingTab.jsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
2-
import { SheetTitle } from "@/components/ui/sheet"
31
import he from "he"
42
import { ChevronRight, Music } from "lucide-react"
53
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
4+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
5+
import { SheetTitle } from "@/components/ui/sheet"
66
import { usePlayerStore } from "@/stores/playerStore"
77
import { MusicControls, ProgressBarMusic } from "../Common"
88

@@ -233,7 +233,7 @@ const NowPlayingTab = memo(({ currentSong }) => {
233233
}), [])
234234

235235
return (
236-
<div className="w-full h-full relative overflow-hidden bg-[#050508]">
236+
<div className="w-full h-full relative overflow-hidden bg-[#050508] lg:pr-[300px]">
237237

238238
{/* === MOBILE BG: Pre-blurred canvas, zero CSS filter cost === */}
239239
{blurredBg.previous && (
@@ -253,12 +253,12 @@ const NowPlayingTab = memo(({ currentSong }) => {
253253
{images.previous && (
254254
<div
255255
className="hidden lg:block absolute inset-0 bg-cover bg-center np-bg-fade-out"
256-
style={{ backgroundImage: `url(${images.previous})`, filter: "blur(160px) saturate(1.4) brightness(0.65)", transform: "scale(2) translateZ(0)", willChange: "transform" }}
256+
style={{ backgroundImage: `url(${images.previous})`, filter: "blur(100px) saturate(1.3) brightness(0.6)", transform: "scale(1.5) translateZ(0)", willChange: "opacity" }}
257257
/>
258258
)}
259259
<div
260260
className={`hidden lg:block absolute inset-0 bg-cover bg-center ${images.transitioning ? "np-bg-fade-in" : ""}`}
261-
style={{ backgroundImage: `url(${images.current})`, filter: "blur(160px) saturate(1.4) brightness(0.65)", transform: "scale(2) translateZ(0)", willChange: "transform" }}
261+
style={{ backgroundImage: `url(${images.current})`, filter: "blur(100px) saturate(1.3) brightness(0.6)", transform: "scale(1.5) translateZ(0)" }}
262262
/>
263263

264264
{/* Desktop: glass effects */}
@@ -293,7 +293,7 @@ const NowPlayingTab = memo(({ currentSong }) => {
293293
</div>
294294

295295
<div
296-
className="flex-1 flex flex-col gap-6 min-w-0 transition-all duration-600 ease-out"
296+
className="flex-1 flex flex-col gap-6 min-w-0 overflow-hidden transition-all duration-600 ease-out"
297297
style={{
298298
opacity: showEntrance ? 1 : 0,
299299
transform: showEntrance ? "translateY(0)" : "translateY(20px)",
@@ -392,10 +392,11 @@ const NowPlayingTab = memo(({ currentSong }) => {
392392
mix-blend-mode: overlay;
393393
}
394394
.np-orb-layer {
395-
filter: blur(80px);
396-
transform: scale(1.3);
395+
filter: blur(50px);
396+
transform: scale(1.2);
397397
mix-blend-mode: soft-light;
398-
opacity: 0.6;
398+
opacity: 0.5;
399+
contain: layout style;
399400
}
400401
.np-orb {
401402
position: absolute;

client/src/Pages/Music/BottomPlayer/PlayerSheet.jsx

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/** biome-ignore-all lint/a11y/useButtonType: <explanation> */
22

33
import { ChevronDownIcon, Disc3, ListMusic, MoreHorizontal, User, X } from "lucide-react"
4-
import { memo, useCallback, useState } from "react"
4+
import { memo, useCallback, useEffect, useState } from "react"
55
import { useNavigate } from "react-router-dom"
66
import { Badge } from "@/components/ui/badge"
77
import { Button } from "@/components/ui/button"
@@ -13,14 +13,24 @@ import {
1313
} from "@/components/ui/dropdown-menu"
1414
import { Sheet, SheetContent } from "@/components/ui/sheet"
1515
import { usePlayerStore } from "@/stores/playerStore"
16+
import DesktopQueuePanel from "./DesktopQueuePanel"
1617
import NowPlayingTab from "./NowPlayingTab"
1718
import QueueTab from "./QueueTab"
1819

1920
const PlayerSheet = memo(({ isOpen, onClose, currentSong, onOpenModal }) => {
20-
const [queueOpen, setQueueOpen] = useState(false)
21+
const [mobileQueueOpen, setMobileQueueOpen] = useState(false)
22+
const [queueReady, setQueueReady] = useState(false)
2123
const playlistLength = usePlayerStore((s) => s.playlist.length)
2224
const navigate = useNavigate()
2325

26+
useEffect(() => {
27+
if (isOpen) {
28+
const timer = setTimeout(() => setQueueReady(true), 500)
29+
return () => clearTimeout(timer)
30+
}
31+
setQueueReady(false)
32+
}, [isOpen])
33+
2434
const handleGoToAlbum = useCallback(
2535
(e) => {
2636
e.stopPropagation()
@@ -51,11 +61,9 @@ const PlayerSheet = memo(({ isOpen, onClose, currentSong, onOpenModal }) => {
5161
side="bottom"
5262
className="h-full w-full p-0 overflow-hidden border-0 bg-black"
5363
>
54-
{/* Full-screen Now Playing - all screen sizes */}
5564
<div className="h-full w-full relative">
5665
<NowPlayingTab currentSong={currentSong} onOpenModal={onOpenModal} isDesktop />
5766

58-
{/* Floating controls overlay */}
5967
<div className="absolute top-4 left-4 right-4 flex items-center justify-between z-30">
6068
<Button
6169
variant="ghost"
@@ -70,8 +78,8 @@ const PlayerSheet = memo(({ isOpen, onClose, currentSong, onOpenModal }) => {
7078
<Button
7179
variant="ghost"
7280
size="icon"
73-
onClick={() => setQueueOpen(true)}
74-
className="h-10 w-10 bg-white/10 hover:bg-white/20 rounded-full transition-colors duration-300 relative"
81+
onClick={() => setMobileQueueOpen(true)}
82+
className="lg:hidden h-10 w-10 bg-white/10 hover:bg-white/20 rounded-full transition-colors duration-300 relative"
7583
>
7684
<ListMusic className="h-5 w-5 text-white" />
7785
<span className="absolute -top-1 -right-1 min-w-[18px] h-[18px] rounded-full bg-white/25 text-[10px] text-white font-semibold flex items-center justify-center px-1">
@@ -115,16 +123,21 @@ const PlayerSheet = memo(({ isOpen, onClose, currentSong, onOpenModal }) => {
115123
</div>
116124
</div>
117125

118-
{/* Queue slide-over panel */}
126+
{/* Desktop: always-visible queue panel, content delayed */}
127+
<div className="hidden lg:block">
128+
<DesktopQueuePanel ready={queueReady} />
129+
</div>
130+
131+
{/* Mobile: full-screen slide-over queue */}
119132
<div
120-
className={`absolute inset-0 z-40 transition-all duration-500 ease-out ${queueOpen ? "pointer-events-auto" : "pointer-events-none"}`}
133+
className={`lg:hidden absolute inset-0 z-40 transition-all duration-500 ease-out ${mobileQueueOpen ? "pointer-events-auto" : "pointer-events-none"}`}
121134
>
122135
<div
123-
className={`absolute inset-0 bg-black/70 transition-opacity duration-500 ${queueOpen ? "opacity-100" : "opacity-0"}`}
124-
onClick={() => setQueueOpen(false)}
136+
className={`absolute inset-0 bg-black/70 transition-opacity duration-500 ${mobileQueueOpen ? "opacity-100" : "opacity-0"}`}
137+
onClick={() => setMobileQueueOpen(false)}
125138
/>
126139
<div
127-
className={`absolute top-0 right-0 bottom-0 w-full sm:w-[420px] bg-[#0a0a0a]/95 transition-transform duration-500 ease-out flex flex-col ${queueOpen ? "translate-x-0" : "translate-x-full"}`}
140+
className={`absolute top-0 right-0 bottom-0 w-full sm:w-105 bg-black/10 backdrop-blur-xl transition-transform duration-500 ease-out flex flex-col border-l border-white/4 ${mobileQueueOpen ? "translate-x-0" : "translate-x-full"}`}
128141
>
129142
<div className="h-16 px-5 flex items-center justify-between shrink-0">
130143
<div className="flex items-center gap-3">
@@ -140,7 +153,7 @@ const PlayerSheet = memo(({ isOpen, onClose, currentSong, onOpenModal }) => {
140153
<Button
141154
variant="ghost"
142155
size="icon"
143-
onClick={() => setQueueOpen(false)}
156+
onClick={() => setMobileQueueOpen(false)}
144157
className="h-9 w-9 bg-white/10 hover:bg-white/20 rounded-full transition-colors duration-300"
145158
>
146159
<X className="h-4 w-4 text-white" />

0 commit comments

Comments
 (0)