diff --git a/apps/web/src/components/SpotifyPlayer.tsx b/apps/web/src/components/SpotifyPlayer.tsx index a51ef874b..bd1a76055 100644 --- a/apps/web/src/components/SpotifyPlayer.tsx +++ b/apps/web/src/components/SpotifyPlayer.tsx @@ -1,4 +1,15 @@ -import { ChevronDownIcon, ChevronUpIcon, ListMusicIcon, Music2Icon, XIcon } from "lucide-react"; +import { + ChevronDownIcon, + ChevronUpIcon, + ListMusicIcon, + MaximizeIcon, + MinimizeIcon, + Music2Icon, + Volume1Icon, + Volume2Icon, + VolumeXIcon, + XIcon, +} from "lucide-react"; import { useCallback, useMemo, useRef, useState } from "react"; import { buildEmbedUrl, @@ -40,17 +51,72 @@ export function SpotifyToggleButton() { // --------------------------------------------------------------------------- const CATEGORIES = [...new Set(DEFAULT_PLAYLISTS.map((p) => p.category))]; +// --------------------------------------------------------------------------- +// Volume slider — always accessible in the header bar +// --------------------------------------------------------------------------- +function VolumeControl() { + const { volume, setVolume } = useSpotifyPlayerStore(); + const [premuteVolume, setPremuteVolume] = useState(80); + + const toggleMute = useCallback(() => { + if (volume > 0) { + setPremuteVolume(volume); + setVolume(0); + } else { + setVolume(premuteVolume || 80); + } + }, [volume, premuteVolume, setVolume]); + + const VolumeIcon = volume === 0 ? VolumeXIcon : volume < 50 ? Volume1Icon : Volume2Icon; + + return ( +
+ + setVolume(Number(e.target.value))} + className="spotify-volume-slider h-1 w-16 cursor-pointer appearance-none rounded-full bg-muted-foreground/20 accent-emerald-400 [&::-webkit-slider-thumb]:size-2.5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-emerald-400 [&::-webkit-slider-thumb]:transition-transform [&::-webkit-slider-thumb]:hover:scale-125 [&::-moz-range-thumb]:size-2.5 [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-0 [&::-moz-range-thumb]:bg-emerald-400" + aria-label="Volume" + /> + + {volume} + +
+ ); +} + // --------------------------------------------------------------------------- // Main Spotify Player Drawer — rendered at the bottom of ChatView // --------------------------------------------------------------------------- export function SpotifyPlayerDrawer() { - const { isOpen, selectedPlaylistUri, customUri, setOpen, selectPlaylist, setCustomUri } = - useSpotifyPlayerStore(); + const { + isOpen, + minimized, + selectedPlaylistUri, + customUri, + setOpen, + setMinimized, + selectPlaylist, + setCustomUri, + } = useSpotifyPlayerStore(); const [expanded, setExpanded] = useState(false); const [customInput, setCustomInput] = useState(""); const [activeCategory, setActiveCategory] = useState(CATEGORIES[0] ?? "Focus"); const inputRef = useRef(null); + const activePlaylist = DEFAULT_PLAYLISTS.find((p) => p.uri === selectedPlaylistUri); + const embedUrl = useMemo(() => { // Custom URI takes priority if (customUri) { @@ -81,24 +147,55 @@ export function SpotifyPlayerDrawer() { return (
- {/* Header bar */} + {/* Header bar — always visible */}
- - Spotify + + + {/* Now-playing label */} + + {activePlaylist ? activePlaylist.name : "Spotify"} + + + {/* Spacer */} +
+ + {/* Volume — always accessible */} + + {/* Playlist picker toggle (only when not minimized) */} + {!minimized && ( + + )} + + {/* Minimize / Restore */} + {/* Close */}
- {/* Expanded playlist picker */} - {expanded && ( + {/* Expanded playlist picker (hidden when minimized) */} + {expanded && !minimized && (
{/* Category tabs */}
@@ -182,14 +279,22 @@ export function SpotifyPlayerDrawer() {
)} - {/* Spotify embed iframe */} + {/* Spotify embed iframe — kept in DOM when minimized so audio continues */} {embedUrl ? ( -
+