@@ -8,6 +8,15 @@ const Playlists = ({ playlists, onSelect, onPlay }) => {
88 const [ arrowVisible , setArrowVisible ] = useState ( true ) ;
99 const [ arrowOffset , setArrowOffset ] = useState ( 0 ) ;
1010 const wrapperRef = useRef ( null ) ;
11+ const scrollState = useRef ( {
12+ isDown : false ,
13+ startX : 0 ,
14+ scrollLeft : 0 ,
15+ lastX : 0 ,
16+ lastTime : 0 ,
17+ velocity : 0 ,
18+ animationFrame : null
19+ } ) ;
1120
1221 const handleSelect = ( playlist ) => {
1322 setActivePlaylist ( playlist . id ) ;
@@ -22,14 +31,103 @@ const Playlists = ({ playlists, onSelect, onPlay }) => {
2231 setArrowOffset ( scrollLeft ) ;
2332 } ;
2433
34+ const animateScroll = ( ) => {
35+ const wrapper = wrapperRef . current ;
36+ if ( ! wrapper ) return ;
37+
38+ // Apply friction
39+ scrollState . current . velocity *= 0.95 ;
40+
41+ // Stop if velocity is very small
42+ if ( Math . abs ( scrollState . current . velocity ) < 0.1 ) {
43+ scrollState . current . velocity = 0 ;
44+ return ;
45+ }
46+
47+ // Update scroll position
48+ wrapper . scrollLeft -= scrollState . current . velocity ;
49+
50+ // Bounce at boundaries
51+ if ( wrapper . scrollLeft <= 0 ) {
52+ wrapper . scrollLeft = 0 ;
53+ scrollState . current . velocity = 0 ;
54+ } else if ( wrapper . scrollLeft >= wrapper . scrollWidth - wrapper . clientWidth ) {
55+ wrapper . scrollLeft = wrapper . scrollWidth - wrapper . clientWidth ;
56+ scrollState . current . velocity = 0 ;
57+ }
58+
59+ scrollState . current . animationFrame = requestAnimationFrame ( animateScroll ) ;
60+ } ;
61+
2562 useEffect ( ( ) => {
2663 const wrapper = wrapperRef . current ;
2764 if ( ! wrapper ) return ;
2865
66+ const handleMouseDown = ( e ) => {
67+ scrollState . current . isDown = true ;
68+ scrollState . current . startX = e . pageX - wrapper . offsetLeft ;
69+ scrollState . current . scrollLeft = wrapper . scrollLeft ;
70+ scrollState . current . lastX = e . pageX ;
71+ scrollState . current . lastTime = performance . now ( ) ;
72+ scrollState . current . velocity = 0 ;
73+
74+ if ( scrollState . current . animationFrame ) {
75+ cancelAnimationFrame ( scrollState . current . animationFrame ) ;
76+ }
77+
78+ wrapper . style . cursor = 'grabbing' ;
79+ } ;
80+
81+ const handleMouseLeave = ( ) => {
82+ scrollState . current . isDown = false ;
83+ wrapper . style . cursor = 'grab' ;
84+ animateScroll ( ) ;
85+ } ;
86+
87+ const handleMouseUp = ( ) => {
88+ scrollState . current . isDown = false ;
89+ wrapper . style . cursor = 'grab' ;
90+ animateScroll ( ) ;
91+ } ;
92+
93+ const handleMouseMove = ( e ) => {
94+ if ( ! scrollState . current . isDown ) return ;
95+ e . preventDefault ( ) ;
96+
97+ const now = performance . now ( ) ;
98+ const deltaTime = now - scrollState . current . lastTime ;
99+ const deltaX = e . pageX - scrollState . current . lastX ;
100+
101+ // Calculate velocity (pixels per frame)
102+ scrollState . current . velocity = deltaX ;
103+
104+ const x = e . pageX - wrapper . offsetLeft ;
105+ const walkX = x - scrollState . current . startX ;
106+ wrapper . scrollLeft = scrollState . current . scrollLeft - walkX ;
107+
108+ scrollState . current . lastX = e . pageX ;
109+ scrollState . current . lastTime = now ;
110+ } ;
111+
112+ wrapper . addEventListener ( 'mousedown' , handleMouseDown ) ;
113+ wrapper . addEventListener ( 'mouseleave' , handleMouseLeave ) ;
114+ wrapper . addEventListener ( 'mouseup' , handleMouseUp ) ;
115+ document . addEventListener ( 'mousemove' , handleMouseMove ) ;
29116 wrapper . addEventListener ( "scroll" , handleScroll ) ;
117+
118+ wrapper . style . cursor = 'grab' ;
30119 handleScroll ( ) ; // Initialize visibility on mount
31120
32- return ( ) => wrapper . removeEventListener ( "scroll" , handleScroll ) ;
121+ return ( ) => {
122+ wrapper . removeEventListener ( 'mousedown' , handleMouseDown ) ;
123+ wrapper . removeEventListener ( 'mouseleave' , handleMouseLeave ) ;
124+ wrapper . removeEventListener ( 'mouseup' , handleMouseUp ) ;
125+ document . removeEventListener ( 'mousemove' , handleMouseMove ) ;
126+ wrapper . removeEventListener ( "scroll" , handleScroll ) ;
127+ if ( scrollState . current . animationFrame ) {
128+ cancelAnimationFrame ( scrollState . current . animationFrame ) ;
129+ }
130+ } ;
33131 } , [ ] ) ;
34132
35133 return (
0 commit comments