Skip to content

Commit 4a9b207

Browse files
authored
Merge pull request #72 from themashcodee/claude/hopeful-pare-31b73f
fix: add left/right navigation buttons to carousel block
2 parents a07e11d + 73c6145 commit 4a9b207

1 file changed

Lines changed: 85 additions & 1 deletion

File tree

src/components/blocks/carousel.tsx

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useCallback, useEffect, useRef, useState } from "react";
12
import { CarouselBlock } from "../../types";
23
import { Card } from "./card";
34

@@ -9,23 +10,106 @@ export const Carousel = (props: CarouselProps) => {
910
const { elements, block_id } = props.data;
1011
const cards = elements.slice(0, 10);
1112

13+
const trackRef = useRef<HTMLDivElement>(null);
14+
const [canScrollLeft, setCanScrollLeft] = useState(false);
15+
const [canScrollRight, setCanScrollRight] = useState(false);
16+
17+
const updateScrollButtons = useCallback(() => {
18+
const el = trackRef.current;
19+
if (!el) return;
20+
const { scrollLeft, scrollWidth, clientWidth } = el;
21+
const tolerance = 8;
22+
setCanScrollLeft(scrollLeft > tolerance);
23+
setCanScrollRight(scrollLeft + clientWidth < scrollWidth - tolerance);
24+
}, []);
25+
26+
useEffect(() => {
27+
const el = trackRef.current;
28+
if (!el) return;
29+
updateScrollButtons();
30+
el.addEventListener("scroll", updateScrollButtons, { passive: true });
31+
const resizeObserver =
32+
typeof ResizeObserver !== "undefined" ? new ResizeObserver(updateScrollButtons) : null;
33+
resizeObserver?.observe(el);
34+
window.addEventListener("resize", updateScrollButtons);
35+
return () => {
36+
el.removeEventListener("scroll", updateScrollButtons);
37+
resizeObserver?.disconnect();
38+
window.removeEventListener("resize", updateScrollButtons);
39+
};
40+
}, [updateScrollButtons, cards.length]);
41+
42+
const scrollBy = (direction: "left" | "right") => {
43+
const el = trackRef.current;
44+
if (!el) return;
45+
const delta = el.clientWidth * 0.8 * (direction === "left" ? -1 : 1);
46+
el.scrollBy({ left: delta, behavior: "smooth" });
47+
};
48+
1249
if (cards.length === 0) return null;
1350

1451
return (
1552
<div
1653
id={block_id}
17-
className="my-2 slack_blocks_to_jsx__carousel"
54+
className="my-2 relative slack_blocks_to_jsx__carousel"
1855
role="region"
1956
aria-label="Carousel"
2057
>
2158
<div
59+
ref={trackRef}
2260
className="flex gap-3 overflow-x-auto snap-x snap-mandatory pb-2 -mx-1 px-1 slack_blocks_to_jsx__carousel_track"
2361
style={{ scrollbarWidth: "thin" }}
2462
>
2563
{cards.map((card, i) => (
2664
<Card key={card.block_id || i} data={card} inCarousel />
2765
))}
2866
</div>
67+
68+
{canScrollLeft && (
69+
<button
70+
type="button"
71+
aria-label="Scroll left"
72+
onClick={() => scrollBy("left")}
73+
className="absolute left-1 top-1/2 -translate-y-1/2 w-8 h-8 rounded-full flex items-center justify-center bg-white-primary dark:bg-dark-bg-secondary text-black-primary dark:text-dark-text-primary border border-black-primary/[0.13] dark:border-dark-border shadow-md hover:bg-gray-secondary dark:hover:bg-dark-bg-primary slack_blocks_to_jsx__carousel_arrow slack_blocks_to_jsx__carousel_arrow--left"
74+
>
75+
<svg
76+
xmlns="http://www.w3.org/2000/svg"
77+
viewBox="0 0 20 20"
78+
aria-hidden="true"
79+
className="w-5 h-5"
80+
>
81+
<path
82+
fill="currentColor"
83+
fillRule="evenodd"
84+
d="M12.03 5.72a.75.75 0 0 1 0 1.06L8.81 10l3.22 3.22a.75.75 0 1 1-1.06 1.06l-3.75-3.75a.75.75 0 0 1 0-1.06l3.75-3.75a.75.75 0 0 1 1.06 0"
85+
clipRule="evenodd"
86+
/>
87+
</svg>
88+
</button>
89+
)}
90+
91+
{canScrollRight && (
92+
<button
93+
type="button"
94+
aria-label="Scroll right"
95+
onClick={() => scrollBy("right")}
96+
className="absolute right-1 top-1/2 -translate-y-1/2 w-8 h-8 rounded-full flex items-center justify-center bg-white-primary dark:bg-dark-bg-secondary text-black-primary dark:text-dark-text-primary border border-black-primary/[0.13] dark:border-dark-border shadow-md hover:bg-gray-secondary dark:hover:bg-dark-bg-primary slack_blocks_to_jsx__carousel_arrow slack_blocks_to_jsx__carousel_arrow--right"
97+
>
98+
<svg
99+
xmlns="http://www.w3.org/2000/svg"
100+
viewBox="0 0 20 20"
101+
aria-hidden="true"
102+
className="w-5 h-5"
103+
>
104+
<path
105+
fill="currentColor"
106+
fillRule="evenodd"
107+
d="M7.72 5.72a.75.75 0 0 1 1.06 0l3.75 3.75a.75.75 0 0 1 0 1.06l-3.75 3.75a.75.75 0 0 1-1.06-1.06L10.94 10 7.72 6.78a.75.75 0 0 1 0-1.06"
108+
clipRule="evenodd"
109+
/>
110+
</svg>
111+
</button>
112+
)}
29113
</div>
30114
);
31115
};

0 commit comments

Comments
 (0)