Player2 provides a visual interface for viewing and managing your Spotify playback queue. The queue drawer displays upcoming tracks with album artwork, artist information, and interactive controls.
Architecture
The queue management system is built with:
Framer Motion Reorder - Drag-and-drop functionality
Context Menu - Right-click options for track actions
Real-time Updates - Automatic queue synchronization
Animated Transitions - Smooth enter/exit animations
Queue Fetching
The queue is fetched from Spotify’s API and stored in the SpotifyContext:
src/contexts/SpotifyContext.tsx
const refreshQueue = useCallback ( async () => {
if ( ! sdk ) return ;
try {
const queueData = await sdk . player . getUsersQueue ();
setQueue ( queueData . queue . filter ( t => t . type === 'track' ) as SpotifyTrack []);
} catch ( err ) {
console . error ( 'Queue error:' , err );
}
}, [ sdk ]);
Only tracks are included in the queue. Other content types (like podcasts) are filtered out.
Queue Drawer Component
The QueueDrawer displays all tracks in the current queue:
src/components/QueueDrawer.tsx
export function QueueDrawer () {
const { sdk , queue } = useSpotify ();
const [ items , setItems ] = useState ( queue );
useEffect (() => {
setItems ( queue );
}, [ queue ]);
if ( ! sdk ) return null ;
return (
< Reorder.Group
axis = "y"
values = { items }
onReorder = { setItems }
className = { "flex flex-col gap-3 p-3 bg-black/40 text-white flex-1" }
>
< AnimatePresence >
{ items . map (( item , i ) => (
< Item key = { item . id } track = { item } index = { i } />
)) }
</ AnimatePresence >
</ Reorder.Group >
);
}
The component:
Syncs local state with the global queue
Uses Reorder.Group for drag-and-drop
Wraps items in AnimatePresence for exit animations
Individual Queue Items
Each track in the queue is rendered as an interactive item:
src/components/QueueDrawer.tsx
function Item ({ track , index } : { track : SpotifyTrack ; index : number }) {
const { sdk } = useSpotify ();
const { openDrawer } = useDrawer ();
if ( ! sdk ) return null ;
return (
< Reorder.Item
value = { track }
initial = { { opacity: 0 , translateY: 10 , transition: { delay: index * 0.5 } } }
animate = { { opacity: 1 , translateY: 0 , transition: { delay: 0 } } }
exit = { { opacity: 0 , height: 0 } }
>
< ContextMenuDemo
track = { track }
trigger = {
< div className = 'flex gap-3' >
< img
src = { track . album . images [ 0 ]. url }
alt = { track . name }
className = 'size-10 rounded pointer-events-none'
/>
< div className = 'flex flex-col flex-1' >
< span className = 'line-clamp-1' > { track . name } </ span >
< span className = 'text-xs line-clamp-1' >
{ parseArtists ( track . artists , sdk , openDrawer ) }
</ span >
</ div >
< button type = 'button' className = "p-2" >
{ /* Drag handle icon */ }
</ button >
</ div >
}
/>
</ Reorder.Item >
);
}
Animation System
Staggered Entry
Queue items animate in with a staggered delay:
src/components/QueueDrawer.tsx
initial = {{
opacity : 0 ,
translateY : 10 ,
transition : { delay : index * 0.5 }
}}
animate = {{
opacity : 1 ,
translateY : 0 ,
transition : { delay : 0 }
}}
Animation Sequence
Item starts 10px below final position with 0 opacity
Each subsequent item has 0.5s additional delay
Items fade in and slide up to final position
Animation takes 0s once started (instant after delay)
Exit Animation
When items are removed, they smoothly collapse:
src/components/QueueDrawer.tsx
exit = {{ opacity : 0 , height : 0 }}
This creates a seamless experience when tracks finish playing and leave the queue.
Artist Parsing & Navigation
Artist names are clickable and open the artist detail view:
src/components/QueueDrawer.tsx
function parseArtists ( artists : SpotifyArtist [], sdk : SpotifyApi , openDrawer : Function ) {
return artists . map (( artist , index ) => (
< span
key = { artist . id }
onClick = { () => openArtist ( artist . id , sdk , openDrawer ) }
data-id = { artist . id }
className = { ` ${ ( index < artists . length - 1 ) ? `after:content-[",_"]` : `` } ` }
>
< span className = 'hover:underline' > { artist . name } </ span >
</ span >
));
}
Features:
Multiple artists separated by commas
Hover underline for visual feedback
Click opens artist drawer
Each artist retains their Spotify ID for API calls
Right-clicking (or long-pressing) a queue item opens a context menu with track actions:
src/components/QueueDrawer.tsx
< ContextMenuDemo
track = { track }
trigger = {
< div className = 'flex gap-3' >
{ /* Track display */ }
</ div >
}
/>
The ContextMenuDemo component wraps the track display and provides actions like “Add to playlist”, “Go to album”, “Go to artist”, etc.
Drag and Drop Reordering
Tracks can be reordered by dragging (currently visual only):
src/components/QueueDrawer.tsx
< Reorder.Group
axis = "y"
values = { items }
onReorder = { setItems }
>
{ /* Items */ }
</ Reorder.Group >
The reordering updates local state immediately for responsive feedback:
src/components/QueueDrawer.tsx
const [ items , setItems ] = useState ( queue );
useEffect (() => {
setItems ( queue );
}, [ queue ]);
Current implementation updates local state only. To persist reordering, you would need to call the Spotify API’s queue management endpoints.
Queue Refresh
The queue automatically refreshes when:
The current track changes
The user manually triggers a refresh
import { useSpotify } from './contexts/SpotifyContext' ;
function MyComponent () {
const { queue , refreshQueue } = useSpotify ();
return (
< div >
< button onClick = { refreshQueue } > Refresh Queue </ button >
< p > { queue . length } tracks in queue </ p >
</ div >
);
}
Usage
import { QueueDrawer } from './components/QueueDrawer' ;
import { useDrawer } from './components/DrawerManager' ;
function App () {
const { openDrawer } = useDrawer ();
return (
< div >
< button onClick = { () => openDrawer ( < QueueDrawer /> ) } >
View Queue
</ button >
</ div >
);
}
The component only re-renders when the queue changes, not on every playback update.
Album artwork uses the smallest image size (10x10 rem) for faster loading.
Framer Motion’s AnimatePresence only animates items that are being added or removed.
Album artwork has pointer-events-none to prevent accidental interaction during dragging.
Component Integration
The queue drawer is designed to work with the Drawer Manager system:
const { openDrawer , closeDrawer } = useDrawer ();
// Open queue drawer
openDrawer ( < QueueDrawer /> );
// Close drawer
closeDrawer ();
This allows the queue to be displayed as a side panel or modal overlay anywhere in the app.