1+ 'use client'
2+
3+ import React , { useState , useEffect , useCallback } from 'react'
4+ import Image from 'next/image'
5+ import { Carousel , CarouselContent , CarouselItem , CarouselNext , CarouselPrevious } from '@/components/ui/carousel'
6+ import { Button } from '@/components/ui/button'
7+ import { CarouselBlockProps , CarouselSlide } from './CarouselBlockProps'
8+ import { CarouselBlockDefaults } from './CarouselBlockDefaults'
9+
10+ export const CarouselBlock = ( {
11+ slides = CarouselBlockDefaults . slides ,
12+ autoPlay = CarouselBlockDefaults . autoPlay ,
13+ autoPlayInterval = CarouselBlockDefaults . autoPlayInterval ,
14+ showArrows = CarouselBlockDefaults . showArrows ,
15+ showDots = CarouselBlockDefaults . showDots ,
16+ loop = CarouselBlockDefaults . loop ,
17+ pauseOnHover = CarouselBlockDefaults . pauseOnHover ,
18+ height = CarouselBlockDefaults . height ,
19+ width = CarouselBlockDefaults . width ,
20+ maxWidth = CarouselBlockDefaults . maxWidth ,
21+ padding = CarouselBlockDefaults . padding ,
22+ borderRadius = CarouselBlockDefaults . borderRadius ,
23+ shadow = CarouselBlockDefaults . shadow ,
24+ arrowColor = CarouselBlockDefaults . arrowColor ,
25+ arrowBackgroundColor = CarouselBlockDefaults . arrowBackgroundColor ,
26+ dotColor = CarouselBlockDefaults . dotColor ,
27+ dotActiveColor = CarouselBlockDefaults . dotActiveColor ,
28+ responsive = CarouselBlockDefaults . responsive ,
29+ } : CarouselBlockProps ) => {
30+ const [ api , setApi ] = useState < any > ( null )
31+ const [ current , setCurrent ] = useState ( 0 )
32+ const [ isHovered , setIsHovered ] = useState ( false )
33+ const [ isMobile , setIsMobile ] = useState ( false )
34+ const [ isTablet , setIsTablet ] = useState ( false )
35+
36+ // Responsive detection
37+ useEffect ( ( ) => {
38+ const checkScreenSize = ( ) => {
39+ const width = window . innerWidth
40+ setIsMobile ( width < 768 )
41+ setIsTablet ( width >= 768 && width < 1024 )
42+ }
43+
44+ checkScreenSize ( )
45+ window . addEventListener ( 'resize' , checkScreenSize )
46+ return ( ) => window . removeEventListener ( 'resize' , checkScreenSize )
47+ } , [ ] )
48+
49+ // Auto-play functionality
50+ useEffect ( ( ) => {
51+ if ( ! api || ! autoPlay || isHovered ) return
52+
53+ const interval = setInterval ( ( ) => {
54+ api . scrollNext ( )
55+ } , autoPlayInterval )
56+
57+ return ( ) => clearInterval ( interval )
58+ } , [ api , autoPlay , autoPlayInterval , isHovered ] )
59+
60+ // Update current slide
61+ useEffect ( ( ) => {
62+ if ( ! api ) return
63+
64+ api . on ( 'select' , ( ) => {
65+ setCurrent ( api . selectedScrollSnap ( ) )
66+ } )
67+ } , [ api ] )
68+
69+ // Get responsive settings
70+ const getResponsiveSettings = ( ) => {
71+ if ( isMobile && responsive ?. mobile ) {
72+ return {
73+ height : responsive . mobile . height || height ,
74+ showArrows : responsive . mobile . showArrows ?? showArrows ,
75+ showDots : responsive . mobile . showDots ?? showDots ,
76+ }
77+ }
78+ if ( isTablet && responsive ?. tablet ) {
79+ return {
80+ height : responsive . tablet . height || height ,
81+ showArrows : responsive . tablet . showArrows ?? showArrows ,
82+ showDots : responsive . tablet . showDots ?? showDots ,
83+ }
84+ }
85+ if ( responsive ?. desktop ) {
86+ return {
87+ height : responsive . desktop . height || height ,
88+ showArrows : responsive . desktop . showArrows ?? showArrows ,
89+ showDots : responsive . desktop . showDots ?? showDots ,
90+ }
91+ }
92+ return { height, showArrows, showDots }
93+ }
94+
95+ const responsiveSettings = getResponsiveSettings ( )
96+
97+ // Handle dot click
98+ const scrollTo = useCallback ( ( index : number ) => {
99+ api ?. scrollTo ( index )
100+ } , [ api ] )
101+
102+ // Handle mouse events for pause on hover
103+ const handleMouseEnter = useCallback ( ( ) => {
104+ if ( pauseOnHover ) {
105+ setIsHovered ( true )
106+ }
107+ } , [ pauseOnHover ] )
108+
109+ const handleMouseLeave = useCallback ( ( ) => {
110+ if ( pauseOnHover ) {
111+ setIsHovered ( false )
112+ }
113+ } , [ pauseOnHover ] )
114+
115+ if ( ! slides || slides . length === 0 ) {
116+ return (
117+ < div
118+ style = { {
119+ height : responsiveSettings . height ,
120+ width,
121+ maxWidth,
122+ padding,
123+ borderRadius,
124+ boxShadow : shadow ? '0 10px 25px rgba(0, 0, 0, 0.1)' : 'none' ,
125+ } }
126+ className = "flex items-center justify-center bg-gray-100 text-gray-500"
127+ >
128+ < p > No slides to display</ p >
129+ </ div >
130+ )
131+ }
132+
133+ return (
134+ < div
135+ style = { {
136+ width,
137+ maxWidth,
138+ padding,
139+ borderRadius,
140+ boxShadow : shadow ? '0 10px 25px rgba(0, 0, 0, 0.1)' : 'none' ,
141+ } }
142+ className = "relative"
143+ onMouseEnter = { handleMouseEnter }
144+ onMouseLeave = { handleMouseLeave }
145+ >
146+ < Carousel
147+ setApi = { setApi }
148+ opts = { {
149+ loop,
150+ align : 'start' ,
151+ } }
152+ className = "w-full"
153+ >
154+ < CarouselContent >
155+ { slides . map ( ( slide : CarouselSlide , index : number ) => (
156+ < CarouselItem key = { slide . id || index } >
157+ < div
158+ style = { {
159+ height : responsiveSettings . height ,
160+ backgroundColor : slide . backgroundColor || '#000000' ,
161+ position : 'relative' ,
162+ overflow : 'hidden' ,
163+ } }
164+ className = "relative flex items-center justify-center"
165+ >
166+ { /* Background Image */ }
167+ { slide . imageUrl && (
168+ < >
169+ < Image
170+ src = { slide . imageUrl }
171+ alt = { slide . title || `Slide ${ index + 1 } ` }
172+ fill
173+ style = { { objectFit : 'cover' } }
174+ priority = { index === 0 }
175+ />
176+ { /* Overlay */ }
177+ < div
178+ style = { {
179+ backgroundColor : `rgba(0, 0, 0, ${ slide . overlayOpacity || 0.3 } )` ,
180+ position : 'absolute' ,
181+ top : 0 ,
182+ left : 0 ,
183+ right : 0 ,
184+ bottom : 0 ,
185+ } }
186+ />
187+ </ >
188+ ) }
189+
190+ { /* Content */ }
191+ < div
192+ style = { {
193+ position : 'relative' ,
194+ zIndex : 10 ,
195+ color : slide . textColor || '#ffffff' ,
196+ textAlign : slide . textAlignment || 'center' ,
197+ padding : '2rem' ,
198+ maxWidth : '800px' ,
199+ width : '100%' ,
200+ } }
201+ className = "flex flex-col items-center justify-center"
202+ >
203+ { slide . title && (
204+ < h2
205+ className = "text-3xl md:text-4xl lg:text-5xl font-bold mb-4"
206+ style = { { textAlign : slide . textAlignment || 'center' } }
207+ >
208+ { slide . title }
209+ </ h2 >
210+ ) }
211+
212+ { slide . description && (
213+ < p
214+ className = "text-lg md:text-xl mb-6 leading-relaxed"
215+ style = { { textAlign : slide . textAlignment || 'center' } }
216+ >
217+ { slide . description }
218+ </ p >
219+ ) }
220+
221+ { slide . buttonText && slide . buttonUrl && (
222+ < Button
223+ asChild
224+ size = "lg"
225+ className = "px-8 py-3 text-lg font-semibold"
226+ style = { {
227+ backgroundColor : slide . textColor || '#ffffff' ,
228+ color : slide . backgroundColor || '#000000' ,
229+ } }
230+ >
231+ < a href = { slide . buttonUrl } >
232+ { slide . buttonText }
233+ </ a >
234+ </ Button >
235+ ) }
236+ </ div >
237+ </ div >
238+ </ CarouselItem >
239+ ) ) }
240+ </ CarouselContent >
241+
242+ { /* Navigation Arrows */ }
243+ { responsiveSettings . showArrows && slides . length > 1 && (
244+ < >
245+ < CarouselPrevious
246+ style = { {
247+ color : arrowColor ,
248+ backgroundColor : arrowBackgroundColor ,
249+ border : 'none' ,
250+ } }
251+ className = "left-4"
252+ />
253+ < CarouselNext
254+ style = { {
255+ color : arrowColor ,
256+ backgroundColor : arrowBackgroundColor ,
257+ border : 'none' ,
258+ } }
259+ className = "right-4"
260+ />
261+ </ >
262+ ) }
263+
264+ { /* Dots Indicator */ }
265+ { responsiveSettings . showDots && slides . length > 1 && (
266+ < div className = "absolute bottom-4 left-1/2 transform -translate-x-1/2 flex space-x-2" >
267+ { slides . map ( ( _ , index ) => (
268+ < button
269+ key = { index }
270+ onClick = { ( ) => scrollTo ( index ) }
271+ className = "w-3 h-3 rounded-full transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-white/50"
272+ style = { {
273+ backgroundColor : index === current ? dotActiveColor : dotColor ,
274+ } }
275+ aria-label = { `Go to slide ${ index + 1 } ` }
276+ />
277+ ) ) }
278+ </ div >
279+ ) }
280+ </ Carousel >
281+ </ div >
282+ )
283+ }
0 commit comments