1- import React , { ReactNode , useState } from 'react' ;
1+ import {
2+ CSSProperties ,
3+ forwardRef ,
4+ HTMLAttributes ,
5+ ReactNode ,
6+ useEffect ,
7+ useRef ,
8+ useState ,
9+ } from 'react' ;
210import { cva , VariantProps } from 'class-variance-authority' ;
3- import { ChevronLeft , ChevronRight } from 'lucide-react' ;
11+ import { ChevronLeft , ChevronRight , PauseIcon , PlayIcon } from 'lucide-react' ;
412
513import { P } from './typography' ;
614
@@ -34,40 +42,58 @@ const vizContainerVariants = cva('flex box-border grow shrink-0', {
3442 defaultVariants : { vizWidth : 'base' } ,
3543} ) ;
3644
37- interface CardProps
38- extends React . HTMLAttributes < HTMLDivElement > ,
39- VariantProps < typeof cardVariants > {
40- vizStyle ?: React . CSSProperties ;
45+ interface CardProps extends HTMLAttributes < HTMLDivElement > , VariantProps < typeof cardVariants > {
4146 slides : {
4247 content : ReactNode ;
4348 viz : ReactNode ;
4449 } [ ] ;
45- contentStyle ?: React . CSSProperties ;
46- contentClassName ?: string ;
47- vizClassName ?: string ;
4850 slideNo ?: boolean ;
51+ autoScroll ?: boolean | number ;
52+ classNames ?: {
53+ content ?: string ;
54+ viz ?: string ;
55+ arrowButton ?: string ;
56+ arrows ?: string ;
57+ playPauseButton ?: string ;
58+ playPauseIcon ?: string ;
59+ progressBar ?: string ;
60+ progressBarBg ?: string ;
61+ } ;
62+ styles ?: {
63+ content ?: CSSProperties ;
64+ viz ?: CSSProperties ;
65+ arrowButton ?: CSSProperties ;
66+ arrows ?: CSSProperties ;
67+ playPauseButton ?: CSSProperties ;
68+ playPauseIcon ?: CSSProperties ;
69+ progressBar ?: CSSProperties ;
70+ progressBarBg ?: CSSProperties ;
71+ } ;
4972}
5073
51- const VizCarousel = React . forwardRef < HTMLDivElement , CardProps > (
74+ const VizCarousel = forwardRef < HTMLDivElement , CardProps > (
5275 (
5376 {
5477 className,
5578 vizWidth,
5679 slides,
57- vizStyle,
58- contentStyle,
59- contentClassName,
60- vizClassName,
80+ styles,
81+ classNames,
6182 slideNo = true ,
83+ autoScroll = false ,
6284 ...props
6385 } ,
6486 ref ,
6587 ) => {
66- const WrapperRef = React . useRef < HTMLDivElement > ( null ) ;
67- const slideRefs = React . useRef < ( HTMLDivElement | null ) [ ] > ( [ ] ) ;
88+ const WrapperRef = useRef < HTMLDivElement > ( null ) ;
89+ const slideRefs = useRef < ( HTMLDivElement | null ) [ ] > ( [ ] ) ;
90+ const intervalRef = useRef < NodeJS . Timeout | null > ( null ) ;
91+ const [ paused , setPaused ] = useState ( false ) ;
92+ const [ mouseOver , setMouseOver ] = useState ( false ) ;
6893 const [ slide , setSlide ] = useState ( 1 ) ;
69-
70- React . useEffect ( ( ) => {
94+ const [ progress , setProgress ] = useState ( 0 ) ;
95+ const progressInterval = 50 ;
96+ useEffect ( ( ) => {
7197 const observer = new IntersectionObserver (
7298 entries => {
7399 const visible = entries
@@ -97,8 +123,73 @@ const VizCarousel = React.forwardRef<HTMLDivElement, CardProps>(
97123 } ;
98124 } , [ ] ) ;
99125
126+ useEffect ( ( ) => {
127+ if ( ! autoScroll ) return ;
128+ const interval = typeof autoScroll === 'number' ? autoScroll : 5000 ;
129+ let currentProgress = progress ;
130+ if ( intervalRef . current ) {
131+ clearInterval ( intervalRef . current ) ;
132+ }
133+ if ( ! paused && ! mouseOver ) {
134+ intervalRef . current = setInterval ( ( ) => {
135+ currentProgress += ( progressInterval / interval ) * 100 ;
136+
137+ if ( currentProgress >= 100 ) {
138+ currentProgress = 0 ;
139+ setProgress ( 0 ) ;
140+ if ( ! WrapperRef . current ) return ;
141+ const parentWithDir = WrapperRef . current . closest ( '[dir]' ) ;
142+ const isRTL = parentWithDir ?. getAttribute ( 'dir' ) === 'rtl' ;
143+ const scrollBy = isRTL ? - 280 : 280 ;
144+
145+ if ( slide === slides . length ) {
146+ WrapperRef . current . scrollTo ( {
147+ left : isRTL ? WrapperRef . current . scrollWidth : 0 ,
148+ behavior : 'smooth' ,
149+ } ) ;
150+ setSlide ( 1 ) ;
151+ } else {
152+ WrapperRef . current . scrollBy ( {
153+ left : scrollBy ,
154+ behavior : 'smooth' ,
155+ } ) ;
156+ setSlide ( prev => prev + 1 ) ;
157+ }
158+ } else {
159+ setProgress ( currentProgress ) ;
160+ }
161+ } , progressInterval ) ;
162+ }
163+ return ( ) => {
164+ if ( intervalRef . current ) {
165+ clearInterval ( intervalRef . current ) ;
166+ }
167+ } ;
168+ } , [ autoScroll , mouseOver , paused , slide , slides . length , progress ] ) ;
169+
100170 return (
101- < div ref = { ref } >
171+ < div
172+ ref = { ref }
173+ onMouseEnter = { ( ) => setMouseOver ( true ) }
174+ onMouseLeave = { ( ) => setMouseOver ( false ) }
175+ >
176+ { autoScroll ? (
177+ < div
178+ className = { cn (
179+ 'w-full h-4 bg-primary-gray-300 dark:bg-primary-gray-600 rounded-full overflow-hidden mb-4' ,
180+ classNames ?. progressBarBg ,
181+ ) }
182+ style = { styles ?. progressBarBg }
183+ >
184+ < div
185+ className = { cn (
186+ 'h-full bg-accent-yellow transition-all duration-100 ease-linear' ,
187+ classNames ?. progressBar ,
188+ ) }
189+ style = { { ...styles ?. progressBar , width : `${ progress } %` } }
190+ />
191+ </ div >
192+ ) : null }
102193 < div
103194 ref = { WrapperRef }
104195 className = { cn (
@@ -116,13 +207,20 @@ const VizCarousel = React.forwardRef<HTMLDivElement, CardProps>(
116207 className = { `flex box-border flex-wrap w-full shrink-0 snap-start ${ vizWidth === 'full' ? 'flex-col items-start' : 'flex-row items-stretch' } ` }
117208 >
118209 < div
119- style = { contentStyle }
120- className = { cn ( cardVariants ( { vizWidth } ) , contentClassName ) }
210+ style = { styles ?. content }
211+ className = { cn ( cardVariants ( { vizWidth } ) , classNames ?. content ) }
121212 >
122213 < div className = 'min-w-80 grow sm:grow-0' > { d . content } </ div >
123214 < div className = { `flex ${ slideNo ? 'gap-2' : 'gap-3' } items-center shrink-0` } >
124215 < div
125- className = { `rounded-full pr-1 w-9 h-9 md:w-12 md:h-12 border-0 flex items-center justify-center rtl:rotate-180 ${ slide === 1 ? 'bg-primary-gray-400 dark:bg-primary-gray-550 cursor-not-allowed' : 'cursor-pointer bg-primary-gray-700 dark:bg-primary-gray-100 hover:bg-primary-gray-600 dark:hover:bg-primary-gray-200' } ` }
216+ style = { styles ?. arrowButton }
217+ className = { cn (
218+ `rounded-full pr-1 w-9 h-9 md:w-12 md:h-12 border-0 flex items-center justify-center rtl:rotate-180` ,
219+ slide === 1
220+ ? 'bg-primary-gray-400 dark:bg-primary-gray-550 cursor-not-allowed'
221+ : 'cursor-pointer bg-primary-gray-700 dark:bg-primary-gray-100 hover:bg-primary-gray-600 dark:hover:bg-primary-gray-200' ,
222+ classNames ?. arrowButton ,
223+ ) }
126224 onClick = { ( ) => {
127225 if ( WrapperRef . current && slide !== 1 ) {
128226 const parentWithDir = WrapperRef . current . closest ( '[dir]' ) ;
@@ -132,15 +230,28 @@ const VizCarousel = React.forwardRef<HTMLDivElement, CardProps>(
132230 }
133231 } }
134232 >
135- < ChevronLeft className = 'w-6 h-6 text-primary-white dark:text-primary-gray-700' />
233+ < ChevronLeft
234+ style = { styles ?. arrows }
235+ className = { cn (
236+ 'w-6 h-6 text-primary-white dark:text-primary-gray-700' ,
237+ classNames ?. arrows ,
238+ ) }
239+ />
136240 </ div >
137241 { slideNo ? (
138242 < P marginBottom = 'none' className = 'px-2! shrink-0' >
139243 { slide } / { slides . length }
140244 </ P >
141245 ) : null }
142246 < div
143- className = { `rounded-full pl-1 w-9 h-9 md:w-12 md:h-12 border-0 flex items-center justify-center rtl:rotate-180 ${ slide === slides . length ? 'bg-primary-gray-400 dark:bg-primary-gray-550 cursor-not-allowed' : 'cursor-pointer bg-primary-gray-700 dark:bg-primary-gray-100 hover:bg-primary-gray-600 dark:hover:bg-primary-gray-200' } ` }
247+ className = { cn (
248+ `rounded-full pl-1 w-9 h-9 md:w-12 md:h-12 border-0 flex items-center justify-center rtl:rotate-180` ,
249+ slide === slides . length
250+ ? 'bg-primary-gray-400 dark:bg-primary-gray-550 cursor-not-allowed'
251+ : 'cursor-pointer bg-primary-gray-700 dark:bg-primary-gray-100 hover:bg-primary-gray-600 dark:hover:bg-primary-gray-200' ,
252+ classNames ?. arrowButton ,
253+ ) }
254+ style = { styles ?. arrowButton }
144255 onClick = { ( ) => {
145256 if ( WrapperRef . current && slide !== slides . length ) {
146257 const parentWithDir = WrapperRef . current . closest ( '[dir]' ) ;
@@ -150,13 +261,51 @@ const VizCarousel = React.forwardRef<HTMLDivElement, CardProps>(
150261 }
151262 } }
152263 >
153- < ChevronRight className = 'w-6 h-6 text-primary-white dark:text-primary-gray-700' />
264+ < ChevronRight
265+ style = { styles ?. arrows }
266+ className = { cn (
267+ 'w-6 h-6 text-primary-white dark:text-primary-gray-700' ,
268+ classNames ?. arrows ,
269+ ) }
270+ />
154271 </ div >
272+ { autoScroll ? (
273+ < div
274+ style = { styles ?. playPauseButton }
275+ className = { cn (
276+ 'rounded-full pl-1 w-9 h-9 md:w-12 md:h-12 border-2 border-primary-gray-600 dark:border-primary-white flex items-center justify-center rtl:rotate-180 cursor-pointer bg-transparent hover:bg-primary-gray-100 dark:hover:bg-primary-gray-600' ,
277+ classNames ?. playPauseButton ,
278+ ) }
279+ onClick = { ( ) => {
280+ setPaused ( ! paused ) ;
281+ } }
282+ >
283+ { paused ? (
284+ < PlayIcon
285+ style = { styles ?. playPauseIcon }
286+ strokeWidth = { 2 }
287+ className = { cn (
288+ 'w-6 h-6 text-primary-gray-700 dark:text-primary-white' ,
289+ classNames ?. playPauseIcon ,
290+ ) }
291+ />
292+ ) : (
293+ < PauseIcon
294+ strokeWidth = { 2 }
295+ style = { styles ?. playPauseIcon }
296+ className = { cn (
297+ 'w-6 h-6 text-primary-gray-700 dark:text-primary-white' ,
298+ classNames ?. playPauseIcon ,
299+ ) }
300+ />
301+ ) }
302+ </ div >
303+ ) : null }
155304 </ div >
156305 </ div >
157306 < div
158- style = { vizStyle }
159- className = { cn ( vizContainerVariants ( { vizWidth } ) , vizClassName ) }
307+ style = { styles ?. viz }
308+ className = { cn ( vizContainerVariants ( { vizWidth } ) , classNames ?. viz ) }
160309 >
161310 { d . viz }
162311 </ div >
0 commit comments