tee3apps-cms-sdk-react 0.0.19 → 0.0.22
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.
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Page.tsx +2 -2
- package/src/PageComponents/BoxComponent.tsx +142 -92
- package/src/PageComponents/Visual-Components/CarouselComponent.tsx +497 -364
- package/src/PageComponents/Visual-Components/GroupBrandComponent.tsx +396 -390
- package/src/PageComponents/Visual-Components/GroupCategoryComponent.tsx +21 -13
- package/src/PageComponents/Visual-Components/GroupImageList.tsx +642 -668
- package/src/PageComponents/Visual-Components/GroupProductComponent.tsx +46 -20
- package/src/PageComponents/Visual-Components/GroupVideoList.tsx +693 -589
- package/src/PageComponents/Visual-Components/ImageComponent.tsx +72 -17
- package/src/PageComponents/Visual-Components/LottieComponent.tsx +14 -8
- package/src/PageComponents/Visual-Components/NavigationComponent.tsx +74 -27
- package/src/PageComponents/Visual-Components/RichTextComponent.tsx +1 -1
- package/src/PageComponents/Visual-Components/TabComponent.tsx +1625 -875
- package/src/PageComponents/Visual-Components/TextComponent.tsx +24 -10
- package/src/PageComponents/Visual-Components/VideoComponent.tsx +33 -11
- package/src/PageComponents/Visual-Components/tab.css +645 -611
- package/src/index.css +126 -80
- package/src/Components/BoxRenderer.tsx +0 -108
- package/src/Components/ComponentRenderer.tsx +0 -29
- package/src/Components/ImageComponent.tsx +0 -68
- package/src/Components/RowComponent.tsx +0 -66
- package/src/Components/TextComponent.tsx +0 -47
|
@@ -1,364 +1,497 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
-
import { Linodeurl } from '../../const';
|
|
3
|
-
|
|
4
|
-
// Types for the carousel props
|
|
5
|
-
interface Slide {
|
|
6
|
-
sl_id: string;
|
|
7
|
-
name: string;
|
|
8
|
-
sl_type: string;
|
|
9
|
-
image: {
|
|
10
|
-
isDynamic: boolean;
|
|
11
|
-
url: string;
|
|
12
|
-
alt: string;
|
|
13
|
-
width: number | null;
|
|
14
|
-
height: number | null;
|
|
15
|
-
};
|
|
16
|
-
video
|
|
17
|
-
url?: string;
|
|
18
|
-
alt?: string;
|
|
19
|
-
};
|
|
20
|
-
link_type: string; // NONE | EXTERNALLINK | PRODUCT | TAG | PAGE
|
|
21
|
-
product: any;
|
|
22
|
-
tag: any;
|
|
23
|
-
page: any;
|
|
24
|
-
external_link: {
|
|
25
|
-
url: string;
|
|
26
|
-
target: string;
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface SlideMode {
|
|
31
|
-
web: {
|
|
32
|
-
slidesToShow: number;
|
|
33
|
-
};
|
|
34
|
-
mobileweb: {
|
|
35
|
-
slidesToShow: number;
|
|
36
|
-
};
|
|
37
|
-
mobileapp: {
|
|
38
|
-
slidesToShow: number;
|
|
39
|
-
};
|
|
40
|
-
tablet: {
|
|
41
|
-
slidesToShow: number;
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface SlidesData {
|
|
46
|
-
all: Slide[];
|
|
47
|
-
mode: SlideMode;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface CarouselProps {
|
|
51
|
-
name: string;
|
|
52
|
-
code: string;
|
|
53
|
-
scrollMode: string;
|
|
54
|
-
autoplay: boolean;
|
|
55
|
-
autoplayInterval: number;
|
|
56
|
-
pauseOnHover: boolean;
|
|
57
|
-
speed: number;
|
|
58
|
-
height: string;
|
|
59
|
-
vertical: boolean;
|
|
60
|
-
sliderArrowVisible: boolean;
|
|
61
|
-
pagingDotVisible: boolean;
|
|
62
|
-
objectFit: string;
|
|
63
|
-
slides: SlidesData[];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
interface CarouselComponentMainProps {
|
|
67
|
-
props: CarouselProps;
|
|
68
|
-
deviceMode?: string;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const CarouselComponent: React.FC<CarouselComponentMainProps> = ({
|
|
72
|
-
props,
|
|
73
|
-
deviceMode = 'web'
|
|
74
|
-
}) => {
|
|
75
|
-
const [currentSlide, setCurrentSlide] = useState(0);
|
|
76
|
-
const [isPlaying, setIsPlaying] = useState(props.autoplay);
|
|
77
|
-
const [isHovered, setIsHovered] = useState(false);
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
//
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
//
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { Linodeurl } from '../../const';
|
|
3
|
+
|
|
4
|
+
// Types for the carousel props
|
|
5
|
+
interface Slide {
|
|
6
|
+
sl_id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
sl_type: string;
|
|
9
|
+
image: {
|
|
10
|
+
isDynamic: boolean;
|
|
11
|
+
url: string;
|
|
12
|
+
alt: string;
|
|
13
|
+
width: number | null;
|
|
14
|
+
height: number | null;
|
|
15
|
+
};
|
|
16
|
+
video?: {
|
|
17
|
+
url?: string;
|
|
18
|
+
alt?: string;
|
|
19
|
+
};
|
|
20
|
+
link_type: string; // NONE | EXTERNALLINK | PRODUCT | TAG | PAGE
|
|
21
|
+
product: any;
|
|
22
|
+
tag: any;
|
|
23
|
+
page: any;
|
|
24
|
+
external_link: {
|
|
25
|
+
url: string;
|
|
26
|
+
target: string;
|
|
27
|
+
} | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface SlideMode {
|
|
31
|
+
web: {
|
|
32
|
+
slidesToShow: number;
|
|
33
|
+
};
|
|
34
|
+
mobileweb: {
|
|
35
|
+
slidesToShow: number;
|
|
36
|
+
};
|
|
37
|
+
mobileapp: {
|
|
38
|
+
slidesToShow: number;
|
|
39
|
+
};
|
|
40
|
+
tablet: {
|
|
41
|
+
slidesToShow: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface SlidesData {
|
|
46
|
+
all: Slide[];
|
|
47
|
+
mode: SlideMode;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface CarouselProps {
|
|
51
|
+
name: string;
|
|
52
|
+
code: string;
|
|
53
|
+
scrollMode: string;
|
|
54
|
+
autoplay: boolean;
|
|
55
|
+
autoplayInterval: number;
|
|
56
|
+
pauseOnHover: boolean;
|
|
57
|
+
speed: number;
|
|
58
|
+
height: string;
|
|
59
|
+
vertical: boolean;
|
|
60
|
+
sliderArrowVisible: boolean;
|
|
61
|
+
pagingDotVisible: boolean;
|
|
62
|
+
objectFit: string;
|
|
63
|
+
slides: SlidesData[] | Slide[][]; // Support both formats: SlidesData[] or nested array Slide[][]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface CarouselComponentMainProps {
|
|
67
|
+
props: CarouselProps;
|
|
68
|
+
deviceMode?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const CarouselComponent: React.FC<CarouselComponentMainProps> = ({
|
|
72
|
+
props,
|
|
73
|
+
deviceMode = 'web'
|
|
74
|
+
}) => {
|
|
75
|
+
const [currentSlide, setCurrentSlide] = useState(0);
|
|
76
|
+
const [isPlaying, setIsPlaying] = useState(props.autoplay);
|
|
77
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
78
|
+
const [prevButtonHovered, setPrevButtonHovered] = useState(false);
|
|
79
|
+
const [nextButtonHovered, setNextButtonHovered] = useState(false);
|
|
80
|
+
|
|
81
|
+
// Detect data structure and safely get slides with fallback
|
|
82
|
+
// Check if slides[0] is an array of Slide objects (new format) or has 'all' property (old format)
|
|
83
|
+
const firstSlideItem = props.slides && props.slides[0] ? props.slides[0] : null;
|
|
84
|
+
|
|
85
|
+
// Check if it's the new nested array format (array of Slide objects)
|
|
86
|
+
const isNestedArrayFormat = Array.isArray(firstSlideItem) &&
|
|
87
|
+
firstSlideItem.length > 0 &&
|
|
88
|
+
typeof firstSlideItem[0] === 'object' &&
|
|
89
|
+
'sl_id' in (firstSlideItem[0] as any);
|
|
90
|
+
|
|
91
|
+
let slides: Slide[] = [];
|
|
92
|
+
let slidesData: SlidesData | null = null;
|
|
93
|
+
|
|
94
|
+
if (isNestedArrayFormat) {
|
|
95
|
+
// New format: slides is Slide[][] - first element is an array of Slide objects
|
|
96
|
+
slides = (firstSlideItem as Slide[]) || [];
|
|
97
|
+
} else if (firstSlideItem && typeof firstSlideItem === 'object' && 'all' in firstSlideItem) {
|
|
98
|
+
// Old format: slides is SlidesData[] - has 'all' property
|
|
99
|
+
slidesData = firstSlideItem as SlidesData;
|
|
100
|
+
slides = slidesData?.all || [];
|
|
101
|
+
} else {
|
|
102
|
+
// Fallback: empty array
|
|
103
|
+
slides = [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const totalSlides = slides.length;
|
|
107
|
+
|
|
108
|
+
// Get current mode settings
|
|
109
|
+
const getCurrentMode = () => {
|
|
110
|
+
// If using nested array format, mode data doesn't exist, use defaults
|
|
111
|
+
if (isNestedArrayFormat || !slidesData || !slidesData.mode) {
|
|
112
|
+
// Return default mode based on device
|
|
113
|
+
const defaultSlidesToShow = deviceMode === 'mobileweb' || deviceMode === 'mobileapp' ? 1 :
|
|
114
|
+
deviceMode === 'tablet' ? 2 : 1;
|
|
115
|
+
return { slidesToShow: defaultSlidesToShow };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
switch(deviceMode) {
|
|
119
|
+
case 'mobileweb':
|
|
120
|
+
return slidesData.mode.mobileweb || { slidesToShow: 1 };
|
|
121
|
+
case 'mobileapp':
|
|
122
|
+
return slidesData.mode.mobileapp || { slidesToShow: 1 };
|
|
123
|
+
case 'tablet':
|
|
124
|
+
return slidesData.mode.tablet || { slidesToShow: 1 };
|
|
125
|
+
case 'web':
|
|
126
|
+
default:
|
|
127
|
+
return slidesData.mode.web || { slidesToShow: 1 };
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const currentMode = getCurrentMode();
|
|
132
|
+
const slidesToShow = Math.max(1, currentMode.slidesToShow || 1);
|
|
133
|
+
|
|
134
|
+
// Function to go to next slide (advance by 1 to support wrap pairs)
|
|
135
|
+
const nextSlide = useCallback(() => {
|
|
136
|
+
setCurrentSlide((prev) => (prev + 1) % totalSlides);
|
|
137
|
+
}, [totalSlides]);
|
|
138
|
+
|
|
139
|
+
// Function to go to previous slide
|
|
140
|
+
const prevSlide = useCallback(() => {
|
|
141
|
+
setCurrentSlide((prev) => (prev - 1 + totalSlides) % totalSlides);
|
|
142
|
+
}, [totalSlides]);
|
|
143
|
+
|
|
144
|
+
// Function to go to specific slide start index
|
|
145
|
+
const goToSlide = (index: number) => {
|
|
146
|
+
setCurrentSlide(index % totalSlides);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Handle autoplay
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
// Only pause on hover if pauseOnHover is enabled
|
|
152
|
+
const shouldPause = props.pauseOnHover && isHovered;
|
|
153
|
+
if (!props.autoplay || !isPlaying || shouldPause) return;
|
|
154
|
+
|
|
155
|
+
const interval = setInterval(() => {
|
|
156
|
+
nextSlide();
|
|
157
|
+
}, props.autoplayInterval);
|
|
158
|
+
|
|
159
|
+
return () => clearInterval(interval);
|
|
160
|
+
}, [props.autoplay, props.autoplayInterval, props.pauseOnHover, isPlaying, isHovered, nextSlide]);
|
|
161
|
+
|
|
162
|
+
// Handle pause on hover
|
|
163
|
+
const handleMouseEnter = () => {
|
|
164
|
+
setIsHovered(true);
|
|
165
|
+
if (props.pauseOnHover) {
|
|
166
|
+
setIsPlaying(false);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const handleMouseLeave = () => {
|
|
171
|
+
setIsHovered(false);
|
|
172
|
+
if (props.pauseOnHover) {
|
|
173
|
+
setIsPlaying(true);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const resolveAssetUrl = (url?: string) => {
|
|
178
|
+
if (!url) return '';
|
|
179
|
+
if (url.startsWith('http://') || url.startsWith('https://')) return url;
|
|
180
|
+
return `${Linodeurl}${url}`;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const buildLinkForSlide = (slide: Slide) => {
|
|
184
|
+
console.warn(slide);
|
|
185
|
+
switch (slide.link_type) {
|
|
186
|
+
case 'NONE':
|
|
187
|
+
return null;
|
|
188
|
+
case 'EXTERNALLINK':
|
|
189
|
+
return `${slide.external_link?.url || ''}`;
|
|
190
|
+
case 'PRODUCT': {
|
|
191
|
+
const product: any = slide.product || {};
|
|
192
|
+
const pdType = product?.pd_type || product?.product_type;
|
|
193
|
+
const code = product?.code || '';
|
|
194
|
+
if (!code) return null;
|
|
195
|
+
|
|
196
|
+
return pdType === 'VARIANT'
|
|
197
|
+
? `/${code}`
|
|
198
|
+
: `/model/${code}`;
|
|
199
|
+
}
|
|
200
|
+
case 'TAG': {
|
|
201
|
+
const tag: any = slide.tag || {};
|
|
202
|
+
if (!tag.code) return null;
|
|
203
|
+
return `/tag/${tag?.code}`;
|
|
204
|
+
}
|
|
205
|
+
case 'PAGE': {
|
|
206
|
+
const page: any = slide.page || {};
|
|
207
|
+
const pageId = page._id;
|
|
208
|
+
const pgType = page.page_type;
|
|
209
|
+
return `/page/${pageId}?type=${pgType}`;
|
|
210
|
+
}
|
|
211
|
+
default:
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const handleSlideClick = (slide: Slide) => {
|
|
217
|
+
const link = buildLinkForSlide(slide);
|
|
218
|
+
if (link === null) {
|
|
219
|
+
// If no link, navigate to next slide
|
|
220
|
+
nextSlide();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (typeof link === 'string') {
|
|
224
|
+
if (link.startsWith('http')) {
|
|
225
|
+
window.open(link, slide.external_link?.target || '_blank');
|
|
226
|
+
} else {
|
|
227
|
+
window.location.href = link;
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Calculate which group the current slide belongs to
|
|
234
|
+
const getCurrentGroupIndex = () => {
|
|
235
|
+
return Math.floor(currentSlide / slidesToShow);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Calculate total number of groups needed
|
|
239
|
+
const getTotalGroups = () => {
|
|
240
|
+
return Math.ceil(totalSlides / slidesToShow);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// Calculate transform offset for slide animation
|
|
244
|
+
// Transform based on group index, not slide index
|
|
245
|
+
const calculateTransform = () => {
|
|
246
|
+
const groupIndex = getCurrentGroupIndex();
|
|
247
|
+
if (props.vertical) {
|
|
248
|
+
// Vertical sliding: moves up and down
|
|
249
|
+
return `translateY(-${groupIndex * 100}%)`;
|
|
250
|
+
} else {
|
|
251
|
+
// Horizontal sliding: moves left and right
|
|
252
|
+
return `translateX(-${groupIndex * 100}%)`;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
return (
|
|
257
|
+
<div
|
|
258
|
+
className="carousel-container"
|
|
259
|
+
style={{
|
|
260
|
+
position: 'relative',
|
|
261
|
+
height: props.height,
|
|
262
|
+
overflow: 'hidden',
|
|
263
|
+
width: '100%'
|
|
264
|
+
}}
|
|
265
|
+
onMouseEnter={handleMouseEnter}
|
|
266
|
+
onMouseLeave={handleMouseLeave}
|
|
267
|
+
>
|
|
268
|
+
{/* Slides container */}
|
|
269
|
+
{totalSlides > 0 ? (
|
|
270
|
+
<div
|
|
271
|
+
className="carousel-slides"
|
|
272
|
+
style={{
|
|
273
|
+
display: 'flex',
|
|
274
|
+
height: '100%',
|
|
275
|
+
width: '100%',
|
|
276
|
+
// vertical = true: slides move up/down (column direction)
|
|
277
|
+
// vertical = false: slides move left/right (row direction)
|
|
278
|
+
flexDirection: props.vertical ? 'column' : 'row',
|
|
279
|
+
transition: `transform ${props.speed}ms ease-in-out`,
|
|
280
|
+
transform: calculateTransform(),
|
|
281
|
+
willChange: 'transform'
|
|
282
|
+
}}
|
|
283
|
+
>
|
|
284
|
+
{Array.from({ length: getTotalGroups() }).map((_, groupIndex) => {
|
|
285
|
+
// Calculate starting slide index for this group
|
|
286
|
+
const startIndex = groupIndex * slidesToShow;
|
|
287
|
+
// Calculate how many slides are in this group (last group may have fewer)
|
|
288
|
+
const slidesInGroup = Math.min(slidesToShow, totalSlides - startIndex);
|
|
289
|
+
|
|
290
|
+
return (
|
|
291
|
+
<div
|
|
292
|
+
key={groupIndex}
|
|
293
|
+
className="carousel-group"
|
|
294
|
+
style={{
|
|
295
|
+
flexShrink: 0,
|
|
296
|
+
display: 'flex',
|
|
297
|
+
// Items within each slide are ALWAYS in a horizontal row
|
|
298
|
+
flexDirection: 'row',
|
|
299
|
+
width: '100%',
|
|
300
|
+
height: '100%',
|
|
301
|
+
gap: '0px'
|
|
302
|
+
}}
|
|
303
|
+
>
|
|
304
|
+
{Array.from({ length: slidesToShow }).map((_, offset) => {
|
|
305
|
+
const slideIndex = startIndex + offset;
|
|
306
|
+
|
|
307
|
+
// If this position is beyond available slides, render empty space
|
|
308
|
+
if (slideIndex >= totalSlides) {
|
|
309
|
+
return (
|
|
310
|
+
<div
|
|
311
|
+
key={`empty-${offset}`}
|
|
312
|
+
className="carousel-slide-empty"
|
|
313
|
+
style={{
|
|
314
|
+
flexShrink: 0,
|
|
315
|
+
width: `${100 / slidesToShow}%`,
|
|
316
|
+
height: '100%',
|
|
317
|
+
// Empty space, no content
|
|
318
|
+
}}
|
|
319
|
+
/>
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const slide = slides[slideIndex];
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<div
|
|
327
|
+
key={`${slide.sl_id}-${offset}`}
|
|
328
|
+
className="carousel-slide"
|
|
329
|
+
style={{
|
|
330
|
+
flexShrink: 0,
|
|
331
|
+
// Items always take equal width in horizontal row
|
|
332
|
+
width: `${100 / slidesToShow}%`,
|
|
333
|
+
height: '100%',
|
|
334
|
+
overflow: 'hidden',
|
|
335
|
+
display: 'flex',
|
|
336
|
+
alignItems: 'center',
|
|
337
|
+
justifyContent: 'center',
|
|
338
|
+
position: 'relative',
|
|
339
|
+
cursor: 'pointer'
|
|
340
|
+
}}
|
|
341
|
+
onClick={() => handleSlideClick(slide)}
|
|
342
|
+
>
|
|
343
|
+
{slide.sl_type === 'video' ? (
|
|
344
|
+
<video
|
|
345
|
+
src={resolveAssetUrl(slide.video?.url)}
|
|
346
|
+
style={{
|
|
347
|
+
width: '100%',
|
|
348
|
+
height: '100%',
|
|
349
|
+
objectFit: props.objectFit as any,
|
|
350
|
+
display: 'block',
|
|
351
|
+
pointerEvents: 'none'
|
|
352
|
+
}}
|
|
353
|
+
autoPlay={props.autoplay}
|
|
354
|
+
muted
|
|
355
|
+
loop
|
|
356
|
+
controls={!props.autoplay}
|
|
357
|
+
/>
|
|
358
|
+
) : (
|
|
359
|
+
<img
|
|
360
|
+
src={resolveAssetUrl(slide.image?.url)}
|
|
361
|
+
alt={slide.image?.alt || ''}
|
|
362
|
+
style={{
|
|
363
|
+
width: '100%',
|
|
364
|
+
height: '100%',
|
|
365
|
+
objectFit: props.objectFit as any,
|
|
366
|
+
display: 'block',
|
|
367
|
+
pointerEvents: 'none'
|
|
368
|
+
}}
|
|
369
|
+
/>
|
|
370
|
+
)}
|
|
371
|
+
</div>
|
|
372
|
+
);
|
|
373
|
+
})}
|
|
374
|
+
</div>
|
|
375
|
+
);
|
|
376
|
+
})}
|
|
377
|
+
</div>
|
|
378
|
+
) : (
|
|
379
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', color: '#999' }}>
|
|
380
|
+
No slides added yet
|
|
381
|
+
</div>
|
|
382
|
+
)}
|
|
383
|
+
|
|
384
|
+
{/* Navigation arrows */}
|
|
385
|
+
{props.sliderArrowVisible && totalSlides > slidesToShow && (
|
|
386
|
+
<>
|
|
387
|
+
<button
|
|
388
|
+
onClick={prevSlide}
|
|
389
|
+
onMouseEnter={() => setPrevButtonHovered(true)}
|
|
390
|
+
onMouseLeave={() => setPrevButtonHovered(false)}
|
|
391
|
+
style={{
|
|
392
|
+
position: 'absolute',
|
|
393
|
+
...(props.vertical
|
|
394
|
+
? { top: '8px', right: '16px' }
|
|
395
|
+
: { left: '8px', top: '50%', transform: 'translateY(-50%)' }
|
|
396
|
+
),
|
|
397
|
+
background: prevButtonHovered ? 'rgba(255, 255, 255, 0.5)' : 'transparent',
|
|
398
|
+
transition: 'all 0.2s ease',
|
|
399
|
+
padding: '8px',
|
|
400
|
+
borderRadius: '4px',
|
|
401
|
+
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
|
|
402
|
+
zIndex: 10,
|
|
403
|
+
border: 'none',
|
|
404
|
+
cursor: 'pointer',
|
|
405
|
+
display: 'flex',
|
|
406
|
+
alignItems: 'center',
|
|
407
|
+
justifyContent: 'center'
|
|
408
|
+
}}
|
|
409
|
+
>
|
|
410
|
+
{props.vertical ? (
|
|
411
|
+
<span style={{ color: '#374151', fontSize: '16px', fontWeight: 'bold' }}>^</span>
|
|
412
|
+
) : (
|
|
413
|
+
<span style={{ color: '#374151', fontSize: '20px', fontWeight: 'bold' }}><</span>
|
|
414
|
+
)}
|
|
415
|
+
</button>
|
|
416
|
+
<button
|
|
417
|
+
onClick={nextSlide}
|
|
418
|
+
onMouseEnter={() => setNextButtonHovered(true)}
|
|
419
|
+
onMouseLeave={() => setNextButtonHovered(false)}
|
|
420
|
+
style={{
|
|
421
|
+
position: 'absolute',
|
|
422
|
+
...(props.vertical
|
|
423
|
+
? { bottom: '8px', right: '16px' }
|
|
424
|
+
: { right: '8px', top: '50%', transform: 'translateY(-50%)' }
|
|
425
|
+
),
|
|
426
|
+
background: nextButtonHovered ? 'rgba(255, 255, 255, 0.5)' : 'transparent',
|
|
427
|
+
transition: 'all 0.2s ease',
|
|
428
|
+
padding: '8px',
|
|
429
|
+
borderRadius: '4px',
|
|
430
|
+
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
|
|
431
|
+
zIndex: 10,
|
|
432
|
+
border: 'none',
|
|
433
|
+
cursor: 'pointer',
|
|
434
|
+
display: 'flex',
|
|
435
|
+
alignItems: 'center',
|
|
436
|
+
justifyContent: 'center'
|
|
437
|
+
}}
|
|
438
|
+
>
|
|
439
|
+
{props.vertical ? (
|
|
440
|
+
<span style={{ color: '#374151', fontSize: '16px', fontWeight: 'bold' }}>v</span>
|
|
441
|
+
) : (
|
|
442
|
+
<span style={{ color: '#374151', fontSize: '20px', fontWeight: 'bold' }}>></span>
|
|
443
|
+
)}
|
|
444
|
+
</button>
|
|
445
|
+
</>
|
|
446
|
+
)}
|
|
447
|
+
|
|
448
|
+
{/* Pagination dots */}
|
|
449
|
+
{props.pagingDotVisible && getTotalGroups() > 1 && (
|
|
450
|
+
<div
|
|
451
|
+
className="carousel-dots"
|
|
452
|
+
style={{
|
|
453
|
+
position: 'absolute',
|
|
454
|
+
...(props.vertical
|
|
455
|
+
? { top: '50%', right: '16px', transform: 'translateY(-50%)' }
|
|
456
|
+
: { bottom: '16px', left: '50%', transform: 'translateX(-50%)' }
|
|
457
|
+
),
|
|
458
|
+
display: 'flex',
|
|
459
|
+
justifyContent: 'center',
|
|
460
|
+
alignItems: 'center',
|
|
461
|
+
flexDirection: props.vertical ? 'column' : 'row',
|
|
462
|
+
gap: '8px',
|
|
463
|
+
zIndex: 100
|
|
464
|
+
}}
|
|
465
|
+
>
|
|
466
|
+
{Array.from({ length: getTotalGroups() }).map((_, groupIndex) => {
|
|
467
|
+
const currentGroupIndex = getCurrentGroupIndex();
|
|
468
|
+
// Navigate to the first slide of the selected group
|
|
469
|
+
const goToGroup = () => {
|
|
470
|
+
const firstSlideInGroup = groupIndex * slidesToShow;
|
|
471
|
+
goToSlide(firstSlideInGroup);
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
return (
|
|
475
|
+
<button
|
|
476
|
+
key={groupIndex}
|
|
477
|
+
aria-label={`Go to group ${groupIndex + 1}`}
|
|
478
|
+
onClick={goToGroup}
|
|
479
|
+
style={{
|
|
480
|
+
width: currentGroupIndex === groupIndex ? '12px' : '10px',
|
|
481
|
+
height: currentGroupIndex === groupIndex ? '12px' : '10px',
|
|
482
|
+
borderRadius: '50%',
|
|
483
|
+
border: 'none',
|
|
484
|
+
background: currentGroupIndex === groupIndex ? '#000' : '#ccc',
|
|
485
|
+
cursor: 'pointer',
|
|
486
|
+
transition: 'all 200ms ease'
|
|
487
|
+
}}
|
|
488
|
+
/>
|
|
489
|
+
);
|
|
490
|
+
})}
|
|
491
|
+
</div>
|
|
492
|
+
)}
|
|
493
|
+
</div>
|
|
494
|
+
);
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
export default CarouselComponent;
|