11"use client" ;
22
3- import { useRef , useState , useEffect } from "react" ;
3+ import { useRef , useState , useEffect , useCallback } from "react" ;
44import Autoplay from "embla-carousel-autoplay" ;
55import { Sparkles } from "lucide-react" ;
66import {
@@ -36,6 +36,60 @@ export default function FeaturedJargonCarousel({
3636 const [ api , setApi ] = useState < CarouselApi > ( ) ;
3737 const [ canScrollPrev , setCanScrollPrev ] = useState ( false ) ;
3838 const [ canScrollNext , setCanScrollNext ] = useState ( false ) ;
39+ const [ scrollProgress , setScrollProgress ] = useState ( 0 ) ;
40+ const scrollbarRef = useRef < HTMLDivElement > ( null ) ;
41+ const isDraggingRef = useRef ( false ) ;
42+
43+ const onScroll = useCallback ( ( ) => {
44+ if ( ! api ) return ;
45+ const progress = Math . max ( 0 , Math . min ( 1 , api . scrollProgress ( ) ) ) ;
46+ setScrollProgress ( progress ) ;
47+ } , [ api ] ) ;
48+
49+ // Calculate scroll position from mouse X and scroll to that slide
50+ const scrollToPosition = useCallback (
51+ ( clientX : number ) => {
52+ if ( ! api || ! scrollbarRef . current ) return ;
53+ const rect = scrollbarRef . current . getBoundingClientRect ( ) ;
54+ const percentage = Math . max (
55+ 0 ,
56+ Math . min ( 1 , ( clientX - rect . left ) / rect . width ) ,
57+ ) ;
58+ const scrollSnapList = api . scrollSnapList ( ) ;
59+ const targetIndex = Math . round ( percentage * ( scrollSnapList . length - 1 ) ) ;
60+ api . scrollTo ( targetIndex ) ;
61+ } ,
62+ [ api ] ,
63+ ) ;
64+
65+ // Handle mouse down on scrollbar (start drag)
66+ const handleScrollbarMouseDown = useCallback (
67+ ( e : React . MouseEvent < HTMLDivElement > ) => {
68+ e . preventDefault ( ) ;
69+ isDraggingRef . current = true ;
70+ scrollToPosition ( e . clientX ) ;
71+ } ,
72+ [ scrollToPosition ] ,
73+ ) ;
74+
75+ // Handle mouse move (drag) and mouse up (end drag)
76+ useEffect ( ( ) => {
77+ const handleMouseMove = ( e : MouseEvent ) => {
78+ if ( ! isDraggingRef . current ) return ;
79+ scrollToPosition ( e . clientX ) ;
80+ } ;
81+
82+ const handleMouseUp = ( ) => {
83+ isDraggingRef . current = false ;
84+ } ;
85+
86+ document . addEventListener ( "mousemove" , handleMouseMove ) ;
87+ document . addEventListener ( "mouseup" , handleMouseUp ) ;
88+ return ( ) => {
89+ document . removeEventListener ( "mousemove" , handleMouseMove ) ;
90+ document . removeEventListener ( "mouseup" , handleMouseUp ) ;
91+ } ;
92+ } , [ scrollToPosition ] ) ;
3993
4094 useEffect ( ( ) => {
4195 if ( ! api ) {
@@ -44,16 +98,21 @@ export default function FeaturedJargonCarousel({
4498
4599 setCanScrollPrev ( api . canScrollPrev ( ) ) ;
46100 setCanScrollNext ( api . canScrollNext ( ) ) ;
101+ onScroll ( ) ;
47102
48103 const onSelect = ( ) => {
49104 setCanScrollPrev ( api . canScrollPrev ( ) ) ;
50105 setCanScrollNext ( api . canScrollNext ( ) ) ;
51106 } ;
52107 api . on ( "select" , onSelect ) ;
108+ api . on ( "scroll" , onScroll ) ;
109+ api . on ( "reInit" , onScroll ) ;
53110 return ( ) => {
54111 api . off ( "select" , onSelect ) ;
112+ api . off ( "scroll" , onScroll ) ;
113+ api . off ( "reInit" , onScroll ) ;
55114 } ;
56- } , [ api ] ) ;
115+ } , [ api , onScroll ] ) ;
57116
58117 return (
59118 < div
@@ -135,6 +194,20 @@ export default function FeaturedJargonCarousel({
135194 < CarouselNext className = "right-2 z-20 border-stone-200 bg-white hover:bg-stone-50 dark:border-stone-700 dark:bg-stone-900 dark:hover:bg-stone-800" />
136195 ) }
137196 </ Carousel >
197+ { /* Horizontal scroll progress bar - visible on desktop only */ }
198+ < div
199+ ref = { scrollbarRef }
200+ onMouseDown = { handleScrollbarMouseDown }
201+ className = "mt-3 hidden h-2 w-full cursor-pointer rounded-full bg-amber-200/50 md:block dark:bg-amber-900/30"
202+ >
203+ < div
204+ className = "pointer-events-none h-full rounded-full bg-amber-900 transition-transform duration-75 ease-out dark:bg-amber-50"
205+ style = { {
206+ width : "20%" ,
207+ transform : `translateX(${ scrollProgress * 400 } %)` ,
208+ } }
209+ />
210+ </ div >
138211 </ div >
139212 </ div >
140213 ) ;
0 commit comments