@codesinger0/shared-components 1.1.106 → 1.1.108
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -4,19 +4,26 @@ import Autoplay from 'embla-carousel-autoplay';
|
|
|
4
4
|
|
|
5
5
|
const FullscreenCarousel = ({
|
|
6
6
|
slides = [],
|
|
7
|
-
|
|
7
|
+
desktopHeight = '100vh',
|
|
8
|
+
mobileHeight = '100vh',
|
|
8
9
|
autoplay = true,
|
|
9
10
|
scrollInterval = 5000,
|
|
10
11
|
loop = true,
|
|
11
12
|
showDots = true,
|
|
12
13
|
showArrows = true,
|
|
13
14
|
dotsColor = 'rgba(255, 255, 255, 1)',
|
|
15
|
+
dotsPosition = { mobile: 'inside', desktop: 'inside' }, // 'inside' | 'below'
|
|
14
16
|
className = '',
|
|
15
17
|
...props
|
|
16
18
|
}) => {
|
|
19
|
+
// Normalize dotsPosition to always be an object
|
|
20
|
+
const normalizedDotsPosition = typeof dotsPosition === 'string'
|
|
21
|
+
? { mobile: dotsPosition, desktop: dotsPosition }
|
|
22
|
+
: { mobile: dotsPosition?.mobile || 'inside', desktop: dotsPosition?.desktop || 'inside' };
|
|
17
23
|
const [prevBtnEnabled, setPrevBtnEnabled] = useState(false);
|
|
18
24
|
const [nextBtnEnabled, setNextBtnEnabled] = useState(false);
|
|
19
25
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
26
|
+
const [scrollSnaps, setScrollSnaps] = useState([]);
|
|
20
27
|
|
|
21
28
|
// Embla carousel setup
|
|
22
29
|
const autoplayOptions = {
|
|
@@ -51,13 +58,14 @@ const FullscreenCarousel = ({
|
|
|
51
58
|
useEffect(() => {
|
|
52
59
|
if (!emblaApi) return;
|
|
53
60
|
onSelect();
|
|
61
|
+
setScrollSnaps(emblaApi.scrollSnapList());
|
|
54
62
|
emblaApi.on('select', onSelect);
|
|
55
63
|
emblaApi.on('reInit', onSelect);
|
|
56
64
|
}, [emblaApi, onSelect]);
|
|
57
65
|
|
|
58
66
|
if (!slides || slides.length === 0) {
|
|
59
67
|
return (
|
|
60
|
-
<div className="w-full flex items-center justify-center bg-gray-100" style={{ height }}>
|
|
68
|
+
<div className="w-full flex items-center justify-center bg-gray-100" style={{ height: desktopHeight }}>
|
|
61
69
|
<p className="text-gray-500">No slides to display</p>
|
|
62
70
|
</div>
|
|
63
71
|
);
|
|
@@ -65,17 +73,79 @@ const FullscreenCarousel = ({
|
|
|
65
73
|
|
|
66
74
|
return (
|
|
67
75
|
<div className={`fullscreen-carousel-wrapper ${className}`} {...props}>
|
|
68
|
-
<div className="fullscreen-carousel"
|
|
76
|
+
<div className="fullscreen-carousel" dir="rtl">
|
|
69
77
|
<div className="fullscreen-carousel__viewport" ref={emblaRef}>
|
|
70
78
|
<div className="fullscreen-carousel__container">
|
|
71
|
-
{slides.map((slide, index) =>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
{slides.map((slide, index) => {
|
|
80
|
+
const isSingleSlide = !slide.leftContent && !slide.rightContent;
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div key={index} className="fullscreen-carousel__slide">
|
|
84
|
+
{isSingleSlide ? (
|
|
85
|
+
/* Single Slide Mode */
|
|
86
|
+
<>
|
|
87
|
+
<div
|
|
88
|
+
className="hidden md:block w-full h-full"
|
|
89
|
+
style={{ height: desktopHeight }}
|
|
90
|
+
>
|
|
91
|
+
{slide.content}
|
|
92
|
+
</div>
|
|
93
|
+
<div
|
|
94
|
+
className="block md:hidden w-full h-full"
|
|
95
|
+
style={{ height: mobileHeight }}
|
|
96
|
+
>
|
|
97
|
+
{slide.content}
|
|
98
|
+
</div>
|
|
99
|
+
</>
|
|
100
|
+
) : (
|
|
101
|
+
<>
|
|
102
|
+
{/* Desktop Layout - Horizontal Split */}
|
|
103
|
+
<div
|
|
104
|
+
className="hidden md:grid md:grid-cols-2 w-full h-full"
|
|
105
|
+
style={{ height: desktopHeight }}
|
|
106
|
+
>
|
|
107
|
+
<div className="w-full h-full">
|
|
108
|
+
{slide.leftContent}
|
|
109
|
+
</div>
|
|
110
|
+
<div className="w-full h-full">
|
|
111
|
+
{slide.rightContent}
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{/* Mobile Layout - Vertical Split */}
|
|
116
|
+
<div
|
|
117
|
+
className="md:hidden grid grid-rows-2 w-full h-full"
|
|
118
|
+
style={{ height: mobileHeight }}
|
|
119
|
+
>
|
|
120
|
+
<div className="w-full h-full">
|
|
121
|
+
{slide.leftContent}
|
|
122
|
+
</div>
|
|
123
|
+
<div className="w-full h-full">
|
|
124
|
+
{slide.rightContent}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</>
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
})}
|
|
76
132
|
</div>
|
|
77
133
|
</div>
|
|
78
134
|
|
|
135
|
+
{/* Dots indicator - inside content (absolute positioned) */}
|
|
136
|
+
{showDots && slides.length > 1 && (
|
|
137
|
+
<div className={`fullscreen-carousel__dots fullscreen-carousel__dots--mobile-${normalizedDotsPosition.mobile} fullscreen-carousel__dots--desktop-${normalizedDotsPosition.desktop}`}>
|
|
138
|
+
{scrollSnaps.map((_, index) => (
|
|
139
|
+
<button
|
|
140
|
+
key={index}
|
|
141
|
+
className={`fullscreen-carousel__dot ${index === selectedIndex ? 'fullscreen-carousel__dot--selected' : ''}`}
|
|
142
|
+
onClick={() => scrollTo(index)}
|
|
143
|
+
aria-label={`Go to slide ${index + 1}`}
|
|
144
|
+
/>
|
|
145
|
+
))}
|
|
146
|
+
</div>
|
|
147
|
+
)}
|
|
148
|
+
|
|
79
149
|
{/* Navigation buttons */}
|
|
80
150
|
{showArrows && slides.length > 1 && (
|
|
81
151
|
<>
|
|
@@ -101,20 +171,6 @@ const FullscreenCarousel = ({
|
|
|
101
171
|
</button>
|
|
102
172
|
</>
|
|
103
173
|
)}
|
|
104
|
-
|
|
105
|
-
{/* Dots indicator */}
|
|
106
|
-
{showDots && slides.length > 1 && (
|
|
107
|
-
<div className="fullscreen-carousel__dots">
|
|
108
|
-
{slides.map((_, index) => (
|
|
109
|
-
<button
|
|
110
|
-
key={index}
|
|
111
|
-
className={`fullscreen-carousel__dot ${index === selectedIndex ? 'fullscreen-carousel__dot--selected' : ''}`}
|
|
112
|
-
onClick={() => scrollTo(index)}
|
|
113
|
-
aria-label={`Go to slide ${index + 1}`}
|
|
114
|
-
/>
|
|
115
|
-
))}
|
|
116
|
-
</div>
|
|
117
|
-
)}
|
|
118
174
|
</div>
|
|
119
175
|
|
|
120
176
|
{/* Styles */}
|
|
@@ -122,6 +178,7 @@ const FullscreenCarousel = ({
|
|
|
122
178
|
.fullscreen-carousel-wrapper {
|
|
123
179
|
position: relative;
|
|
124
180
|
width: 100%;
|
|
181
|
+
overflow: hidden;
|
|
125
182
|
}
|
|
126
183
|
|
|
127
184
|
.fullscreen-carousel {
|
|
@@ -132,20 +189,17 @@ const FullscreenCarousel = ({
|
|
|
132
189
|
.fullscreen-carousel__viewport {
|
|
133
190
|
overflow: hidden;
|
|
134
191
|
width: 100%;
|
|
135
|
-
height: 100%;
|
|
136
192
|
}
|
|
137
|
-
|
|
193
|
+
|
|
138
194
|
.fullscreen-carousel__container {
|
|
139
195
|
display: flex;
|
|
140
196
|
width: 100%;
|
|
141
|
-
height: 100%;
|
|
142
197
|
}
|
|
143
198
|
|
|
144
199
|
.fullscreen-carousel__slide {
|
|
145
200
|
flex: 0 0 100%;
|
|
146
201
|
min-width: 0;
|
|
147
202
|
position: relative;
|
|
148
|
-
height: 100%;
|
|
149
203
|
}
|
|
150
204
|
|
|
151
205
|
.fullscreen-carousel__button {
|
|
@@ -217,17 +271,33 @@ const FullscreenCarousel = ({
|
|
|
217
271
|
display: flex;
|
|
218
272
|
justify-content: center;
|
|
219
273
|
gap: 0.5rem;
|
|
220
|
-
|
|
274
|
+
left: 50%;
|
|
275
|
+
transform: translateX(-50%);
|
|
221
276
|
z-index: 10;
|
|
222
277
|
}
|
|
223
278
|
|
|
279
|
+
/* Mobile: inside (default) */
|
|
280
|
+
.fullscreen-carousel__dots--mobile-inside {
|
|
281
|
+
position: absolute;
|
|
282
|
+
bottom: 2rem;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* Mobile: below */
|
|
286
|
+
.fullscreen-carousel__dots--mobile-below {
|
|
287
|
+
position: relative;
|
|
288
|
+
padding: 1rem 0;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* Desktop overrides */
|
|
224
292
|
@media (min-width: 768px) {
|
|
225
|
-
.fullscreen-carousel__dots {
|
|
293
|
+
.fullscreen-carousel__dots--desktop-inside {
|
|
226
294
|
position: absolute;
|
|
227
295
|
bottom: 2rem;
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.fullscreen-carousel__dots--desktop-below {
|
|
299
|
+
position: relative;
|
|
300
|
+
padding: 1rem 0;
|
|
231
301
|
}
|
|
232
302
|
}
|
|
233
303
|
|