1+ import { useCallback , useEffect , useRef , useState } from "react" ;
12import { CarouselBlock } from "../../types" ;
23import { 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