Skip to content

Commit 51fd873

Browse files
committed
✨ Scroll bar for featured jargons
1 parent 06a6046 commit 51fd873

1 file changed

Lines changed: 75 additions & 2 deletions

File tree

components/home/featured-jargon-carousel.tsx

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useRef, useState, useEffect } from "react";
3+
import { useRef, useState, useEffect, useCallback } from "react";
44
import Autoplay from "embla-carousel-autoplay";
55
import { Sparkles } from "lucide-react";
66
import {
@@ -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

Comments
 (0)