tee3apps-cms-sdk-react 0.0.1

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.
Files changed (43) hide show
  1. package/.env +11 -0
  2. package/README.md +255 -0
  3. package/dist/index.d.ts +5 -0
  4. package/dist/index.js +13 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/index.mjs +13 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/package.json +33 -0
  9. package/rollup.config.js +43 -0
  10. package/src/Components/BoxRenderer.tsx +108 -0
  11. package/src/Components/ComponentRenderer.tsx +29 -0
  12. package/src/Components/ImageComponent.tsx +68 -0
  13. package/src/Components/RowComponent.tsx +66 -0
  14. package/src/Components/TextComponent.tsx +47 -0
  15. package/src/ErrorBoundary.tsx +35 -0
  16. package/src/Page.tsx +124 -0
  17. package/src/PageComponents/BoxComponent.tsx +397 -0
  18. package/src/PageComponents/RowComponent.tsx +113 -0
  19. package/src/PageComponents/Visual-Components/CarouselComponent.tsx +366 -0
  20. package/src/PageComponents/Visual-Components/GroupBrandComponent.tsx +391 -0
  21. package/src/PageComponents/Visual-Components/GroupCategoryComponent.tsx +425 -0
  22. package/src/PageComponents/Visual-Components/GroupImageList.tsx +669 -0
  23. package/src/PageComponents/Visual-Components/GroupProductComponent.tsx +671 -0
  24. package/src/PageComponents/Visual-Components/GroupVideoList.tsx +590 -0
  25. package/src/PageComponents/Visual-Components/ImageComponent.tsx +163 -0
  26. package/src/PageComponents/Visual-Components/LinkComponent.tsx +68 -0
  27. package/src/PageComponents/Visual-Components/LottieComponent.tsx +213 -0
  28. package/src/PageComponents/Visual-Components/NavigationComponent.tsx +178 -0
  29. package/src/PageComponents/Visual-Components/Styles/ProductListViewOne.tsx +102 -0
  30. package/src/PageComponents/Visual-Components/Styles/ProductListViewTwo.tsx +104 -0
  31. package/src/PageComponents/Visual-Components/Styles/product-list-view-one.css +166 -0
  32. package/src/PageComponents/Visual-Components/Styles/product-list-view-two.css +182 -0
  33. package/src/PageComponents/Visual-Components/TabComponent.tsx +1169 -0
  34. package/src/PageComponents/Visual-Components/TextComponent.tsx +114 -0
  35. package/src/PageComponents/Visual-Components/VideoComponent.tsx +191 -0
  36. package/src/PageComponents/Visual-Components/tab.css +697 -0
  37. package/src/common.interface.ts +216 -0
  38. package/src/const.ts +6 -0
  39. package/src/env.d.ts +15 -0
  40. package/src/index.css +82 -0
  41. package/src/index.ts +2 -0
  42. package/src/types.ts +234 -0
  43. package/tsconfig.json +20 -0
@@ -0,0 +1,1169 @@
1
+ import React, { useState, useCallback } from 'react'
2
+ import './tab.css';
3
+ import { Linodeurl } from '../../const';
4
+ interface TabImage {
5
+ isDynamic: boolean;
6
+ alt: string;
7
+ url: string;
8
+ width: number | null;
9
+ height: number | null;
10
+ }
11
+
12
+ interface TabVideo {
13
+ alt: string;
14
+ url: string;
15
+ }
16
+
17
+ interface TabContentImage {
18
+ image: TabImage;
19
+ link_type: string;
20
+ product: any;
21
+ tag: any;
22
+ page: any;
23
+ external_link: any;
24
+ }
25
+
26
+ interface TabContentVideo {
27
+ video: TabVideo;
28
+ link_type: string;
29
+ product: any;
30
+ tag: any;
31
+ page: any;
32
+ external_link: any;
33
+ }
34
+
35
+ interface Product {
36
+ _id: string;
37
+ code: string;
38
+ name: string | { all: string };
39
+ image: any;
40
+ isActive: boolean;
41
+ price?: any;
42
+ }
43
+
44
+ interface TabContentGroup {
45
+ type: string;
46
+ groupImageId: string | null;
47
+ groupProductId: string | null;
48
+ groupVideoId: string | null;
49
+ dynamicImages?: any[];
50
+ dynamicVideos?: any[];
51
+ dynamicProducts?: any[];
52
+ showItems?: Product[];
53
+ }
54
+
55
+ interface TabMode {
56
+ web: { layout: string };
57
+ mobileweb: { layout: string };
58
+ mobileapp: { layout: string };
59
+ tablet: { layout: string };
60
+ }
61
+
62
+ interface TabItem {
63
+ tabHeaderType: string;
64
+ tabHeaderText: string;
65
+ tabHeaderImage: TabImage;
66
+ tabContentType: string;
67
+ tabContentImage?: TabContentImage;
68
+ tabContentVideo?: TabContentVideo;
69
+ tabContentGroupImage: TabContentGroup;
70
+ tabContentGroupVideo: TabContentGroup;
71
+ tabContentGroupProduct: TabContentGroup;
72
+ mode: TabMode;
73
+ }
74
+
75
+ interface FontStyle {
76
+ isBold: boolean;
77
+ isItalic: boolean;
78
+ isUnderLine: boolean;
79
+ }
80
+
81
+ interface TitleStyle {
82
+ titleText: { all: string;[key: string]: string };
83
+ fontSize: number;
84
+ fontStyle: FontStyle;
85
+ fontColor: string;
86
+ alignment: string;
87
+ }
88
+
89
+ interface HeaderStyle {
90
+ fontSize: number;
91
+ fontStyle: FontStyle;
92
+ defaultColorBg: string;
93
+ defaultColorText: string;
94
+ hoverColorBg: string;
95
+ hoverColorText: string;
96
+ activeColorBg: string;
97
+ activeColorText: string;
98
+ }
99
+
100
+ interface TabsData {
101
+ all: TabItem[];
102
+ }
103
+
104
+ export interface TabComponentProps {
105
+ name: string;
106
+ code: string;
107
+ orientation: 'horizontal' | 'vertical';
108
+ height: number;
109
+ showTitle: boolean;
110
+ title: TitleStyle;
111
+ header: HeaderStyle;
112
+ tabs: TabsData[];
113
+ }
114
+
115
+ interface TabComponentMainProps {
116
+ props: TabComponentProps;
117
+ deviceMode?: string;
118
+ }
119
+
120
+ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'web' }) => {
121
+ const [activeTab, setActiveTab] = useState(0);
122
+ const [carouselIndex, setCarouselIndex] = useState(0);
123
+
124
+ const buildLinkForSlide = (slide: any) => {
125
+ switch (slide.link_type) {
126
+ case 'NONE':
127
+ return null;
128
+ case 'EXTERNALLINK':
129
+ return `${slide.external_link?.url || ''}`;
130
+ case 'PRODUCT': {
131
+ const product: any = slide.product || {};
132
+ const pdType = product?.pd_type || product?.product_type;
133
+ const code = product?.code || '';
134
+ if (!code) return null;
135
+
136
+ return pdType === 'VARIANT'
137
+ ? `/variant/${code}`
138
+ : `model/${code}`;
139
+ }
140
+ case 'TAG': {
141
+ const tag: any = slide.tag || {};
142
+ if (!tag.code) return null;
143
+ return `/tag/${tag?.code}`;
144
+
145
+ }
146
+ case 'PAGE': {
147
+ const page: any = slide.page || {};
148
+ const pageId = page._id
149
+ const pgType=page.page_type
150
+ return `/page/${pageId}?type=${pgType}`;
151
+ }
152
+ default:
153
+ return null;
154
+ }
155
+ };
156
+
157
+ const getImageUrl = (url: string) => {
158
+ if (!url) return '';
159
+ if (url.startsWith('http://') || url.startsWith('https://')) return url;
160
+ return `${Linodeurl}${url}`;
161
+ };
162
+
163
+ // Inline Product Card Component
164
+ const ProductCard: React.FC<{ product: any; layout?: string }> = ({ product, layout = '1x1' }) => {
165
+ const formatPrice = (price: any) => {
166
+ if (typeof price === 'object' && price.$numberDecimal) {
167
+ return parseFloat(price.$numberDecimal).toLocaleString('en-IN', {
168
+ style: 'currency',
169
+ currency: 'INR',
170
+ minimumFractionDigits: 0,
171
+ maximumFractionDigits: 0
172
+ });
173
+ }
174
+ return price;
175
+ };
176
+
177
+ const calculateOffer = (mrp: any, sp: any) => {
178
+ const mrpValue = parseFloat(mrp.$numberDecimal || mrp);
179
+ const spValue = parseFloat(sp.$numberDecimal || sp);
180
+ if (mrpValue > spValue) {
181
+ const discount = ((mrpValue - spValue) / mrpValue) * 100;
182
+ return Math.round(discount);
183
+ }
184
+ return 0;
185
+ };
186
+
187
+ const renderStars = (rating: number) => {
188
+ const stars = [];
189
+ for (let i = 1; i <= 5; i++) {
190
+ stars.push(
191
+ <span key={i} className={i <= rating ? 'star' : 'star empty'}>
192
+
193
+ </span>
194
+ );
195
+ }
196
+ return stars;
197
+ };
198
+
199
+ const formatRating = (rating: number) => {
200
+ return rating ? `(${rating.toFixed(1)} *)` : '(0.0 *)';
201
+ };
202
+
203
+ const getVariantText = (variant: any) => {
204
+ return variant.valueId?.name?.all || variant.valueId?.name || '';
205
+ };
206
+
207
+ const mrp = product.price?.MRP;
208
+ const sp = product.price?.SP;
209
+ const offer = calculateOffer(mrp, sp);
210
+
211
+ const cardMode = layout === '1x1' ? 'carousel-mode' : layout === '2x1' ? 'layout-2x1' : 'grid-mode';
212
+ const productSlug = product._id;
213
+ const productLink = `/${productSlug}`;
214
+
215
+ const cardContent = (
216
+ <>
217
+ {/* Product Image */}
218
+ <div className="product-image-container">
219
+ <img
220
+ src={getImageUrl(product.image?.all?.url || product.model?.image?.url)}
221
+ alt={product.name?.all || product.name?.displayName}
222
+ className="product-image"
223
+ />
224
+ </div>
225
+
226
+ {/* Product Details */}
227
+ <div className="product-details">
228
+ <div className="product-header">
229
+ <div className="product-brand">{product.brand?.name?.all || product.brand?.name}</div>
230
+ <div className="product-name">{product.name?.all || product.name}</div>
231
+ <div className="product-code">Code: {product.code}</div>
232
+ </div>
233
+
234
+ {/* Rating - Show first for grid mode */}
235
+ {layout !== '1x1' && layout !== '2x2' && (
236
+ <div className="product-rating">
237
+ <div className="stars">
238
+ {renderStars(product.starrating || 0)}
239
+ </div>
240
+ <span className="rating-text">
241
+ {formatRating(product.starrating || 0)}
242
+ </span>
243
+ </div>
244
+ )}
245
+
246
+ {/* Variants - Only show for carousel mode */}
247
+ {layout === '1x1' && product.variant && product.variant.length > 0 && (
248
+ <div className="product-variants">
249
+ {product.variant.map((variant: any, index: number) => (
250
+ <span key={index} className="variant-tag">
251
+ {getVariantText(variant)}
252
+ </span>
253
+ ))}
254
+ </div>
255
+ )}
256
+ {/* {layout === '1x1' && product.variant && product.variant.length > 0 && (
257
+ <div className="product-variants">
258
+ {product.variant.map((variant: any, index: number) => (
259
+ <span key={index} className="variant-tag">
260
+ {getVariantText(variant)}
261
+ </span>
262
+ ))}
263
+ </div>
264
+ )} */}
265
+ {/* Pricing */}
266
+ <div className="product-pricing">
267
+ <div className="price-row">
268
+ <span className="current-price">{formatPrice(sp)}</span>
269
+ {mrp && sp && parseFloat(mrp.$numberDecimal || mrp) > parseFloat(sp.$numberDecimal || sp) && (
270
+ <>
271
+ <span className="original-price">{formatPrice(mrp)}</span>
272
+ {offer > 0 && <span className="offer-badge">{offer}% OFF</span>}
273
+ </>
274
+ )}
275
+ </div>
276
+ </div>
277
+
278
+ {/* Rating - Show for carousel mode */}
279
+ {layout === '1x1' && (
280
+ <div className="product-rating">
281
+ <div className="stars">
282
+ {renderStars(product.starrating || 0)}
283
+ </div>
284
+ <span className="rating-text">
285
+ ({product.startRatingCount || 0} reviews)
286
+ </span>
287
+ </div>
288
+ )}
289
+
290
+
291
+ </div>
292
+ </>
293
+ );
294
+
295
+ return (
296
+ <div className={`product-card ${cardMode}`}>
297
+ {layout === '1x1' ? (
298
+ <a href={productLink} target="_blank" style={{ textDecoration: 'none', color: 'inherit', display: 'contents' }}>
299
+ {cardContent}
300
+ </a>
301
+ ) : (
302
+ <a href={productLink} target="_blank" style={{ textDecoration: 'none', color: 'inherit', display: 'contents' }}>
303
+ {cardContent}
304
+ </a>
305
+ )}
306
+ </div>
307
+ );
308
+ };
309
+
310
+ // Carousel navigation functions
311
+ const nextCarouselItem = useCallback(() => {
312
+ setCarouselIndex((prev) => (prev + 1));
313
+ }, []);
314
+
315
+ const prevCarouselItem = useCallback(() => {
316
+ setCarouselIndex((prev) => Math.max(0, prev - 1));
317
+ }, []);
318
+
319
+ const goToCarouselItem = (index: number) => {
320
+ setCarouselIndex(index);
321
+ };
322
+
323
+ // Reset carousel when switching tabs
324
+ const handleTabChange = (index: number) => {
325
+ setActiveTab(index);
326
+ setCarouselIndex(0);
327
+ };
328
+
329
+ const getCurrentLayout = (tab: TabItem): string => {
330
+ switch (deviceMode) {
331
+ case 'mobileweb': return tab.mode.mobileweb.layout;
332
+ case 'mobileapp': return tab.mode.mobileapp.layout;
333
+ case 'tablet': return tab.mode.tablet.layout;
334
+ case 'web':
335
+ default: return tab.mode.web.layout;
336
+ }
337
+ };
338
+
339
+ const getTabHeaderStyle = (index: number) => {
340
+ const isActive = activeTab === index;
341
+
342
+ return {
343
+ backgroundColor: 'transparent',
344
+ color: isActive ? '#000000' : '#6b7280', // black for active, grey for inactive
345
+ fontSize: `${props.header.fontSize}px`,
346
+ fontWeight: props.header.fontStyle.isBold ? 'bold' : '500',
347
+ fontStyle: props.header.fontStyle.isItalic ? 'italic' : 'normal',
348
+ textDecoration: props.header.fontStyle.isUnderLine ? 'underline' : 'none',
349
+ padding: '8px 12px',
350
+ border: 'none',
351
+ cursor: 'pointer',
352
+ transition: 'all 0.3s ease',
353
+ borderRadius: '0',
354
+ background: 'none',
355
+ whiteSpace: 'nowrap' as const,
356
+ };
357
+ };
358
+
359
+
360
+ const getTitleStyle = () => {
361
+ return {
362
+ fontSize: `${props?.title?.fontSize}px`,
363
+ fontWeight: props?.title?.fontStyle?.isBold ? 'bold' : '600',
364
+ fontStyle: props?.title?.fontStyle?.isItalic ? 'italic' : 'normal',
365
+ textDecoration: props?.title?.fontStyle?.isUnderLine ? 'underline' : 'none',
366
+ color: props?.title?.fontColor,
367
+ textAlign: props?.title?.alignment as 'left' | 'center' | 'right',
368
+ margin: '0',
369
+ lineHeight: '1.2'
370
+ };
371
+ };
372
+
373
+ const renderTabContent = (tab: TabItem) => {
374
+ const layout = getCurrentLayout(tab);
375
+ console.warn(tab)
376
+ const getGridColumns = () => {
377
+ switch (layout) {
378
+ case '1x1': return '1fr';
379
+ case '1x2': return 'repeat(2, 1fr)';
380
+ case '2x1': return '1fr';
381
+ case '2x2': return 'repeat(2, 1fr)';
382
+ default: return '1fr';
383
+ }
384
+ };
385
+
386
+ const getGridRows = () => {
387
+ switch (layout) {
388
+ case '1x1': return '1fr';
389
+ case '1x2': return '1fr';
390
+ case '2x1': return 'repeat(2, 1fr)';
391
+ case '2x2': return 'repeat(2, 1fr)';
392
+ default: return '1fr';
393
+ }
394
+ };
395
+
396
+ const contentStyle = {
397
+ display: 'grid',
398
+ gridTemplateColumns: getGridColumns(),
399
+ gridTemplateRows: getGridRows(),
400
+ gap: '3px',
401
+ height: `${props.height}px`, // force tab content height
402
+ overflow: 'hidden'
403
+ };
404
+
405
+
406
+ switch (tab.tabContentType) {
407
+ case 'IMAGE':
408
+ if (tab.tabContentImage) {
409
+ console.warn(tab.tabContentImage, buildLinkForSlide(tab.tabContentImage));
410
+ return (
411
+ <a
412
+ href={buildLinkForSlide(tab.tabContentImage) || undefined}
413
+ target={
414
+ tab.tabContentImage.link_type === 'external_link'
415
+ ? tab.tabContentImage.external_link?.target
416
+ : '_blank'
417
+ }
418
+ >
419
+ <div style={contentStyle}>
420
+ <div className="media-box">
421
+ <img
422
+ src={getImageUrl(tab.tabContentImage.image.url)}
423
+ alt={tab.tabContentImage.image.alt}
424
+ className="media-content"
425
+ />
426
+ </div>
427
+ </div>
428
+ </a>
429
+ );
430
+ }
431
+ break;
432
+ case 'VIDEO':
433
+ if (tab.tabContentVideo) {
434
+ return (
435
+ <div style={contentStyle}>
436
+ <div className="media-box">
437
+ <video
438
+ src={getImageUrl(tab.tabContentVideo.video.url)}
439
+ controls
440
+ className="media-content"
441
+
442
+ />
443
+ </div>
444
+ </div>
445
+ );
446
+ }
447
+ break;
448
+
449
+ case 'GROUPIMAGE':
450
+ if (tab.tabContentGroupImage) {
451
+ // Handle both DYNAMIC and STATIC types
452
+ const images = tab.tabContentGroupImage.type === 'DYNAMIC'
453
+ ? tab.tabContentGroupImage.dynamicImages
454
+ : tab.tabContentGroupImage.showItems;
455
+
456
+ if (!images || images.length === 0) {
457
+ return <div>No images available</div>;
458
+ }
459
+
460
+ // 1x1 layout: carousel display
461
+ if (layout === '1x1') {
462
+ const currentImage = images[carouselIndex] || images[0];
463
+ const linkUrl = currentImage ? buildLinkForSlide(currentImage) : null;
464
+
465
+ const imageElement = currentImage ? (
466
+ <div className="media-box">
467
+ <img
468
+ src={getImageUrl(
469
+ tab.tabContentGroupImage.type === 'DYNAMIC'
470
+ ? currentImage.image.url
471
+ : currentImage.attr?.url
472
+ )}
473
+ alt={
474
+ tab.tabContentGroupImage.type === 'DYNAMIC'
475
+ ? currentImage.image.alt
476
+ : currentImage.attr?.alt
477
+ }
478
+ className="media-content"
479
+ />
480
+ </div>
481
+ ) : null;
482
+
483
+ return (
484
+ <div
485
+ style={{
486
+ position: 'relative',
487
+ width: '100%',
488
+ height: 'auto',
489
+ overflow: 'hidden',
490
+ display: 'flex',
491
+ alignItems: 'center',
492
+ justifyContent: 'center',
493
+ }}
494
+ >
495
+ {linkUrl ? (
496
+ <a
497
+ href={linkUrl}
498
+ target={
499
+ currentImage?.link_type === 'EXTERNALLINK'
500
+ ? currentImage.external_link?.target || '_blank'
501
+ : '_self'
502
+ }
503
+ style={{ width: '100%', height: '100%' }}
504
+ >
505
+ {imageElement}
506
+ </a>
507
+ ) : (
508
+ imageElement
509
+ )}
510
+
511
+ {/* Carousel Navigation */}
512
+ {images.length > 1 && (
513
+ <>
514
+ {/* Previous Button */}
515
+ <button
516
+ onClick={prevCarouselItem}
517
+ disabled={carouselIndex === 0}
518
+ style={{
519
+ position: 'absolute',
520
+ left: '10px',
521
+ top: '50%',
522
+ transform: 'translateY(-50%)',
523
+ background: 'rgba(0,0,0,0.5)',
524
+ color: 'white',
525
+ border: 'none',
526
+ borderRadius: '50%',
527
+ width: '40px',
528
+ height: '40px',
529
+ cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
530
+ opacity: carouselIndex === 0 ? 0.5 : 1,
531
+ display: 'flex',
532
+ alignItems: 'center',
533
+ justifyContent: 'center',
534
+ fontSize: '18px',
535
+ zIndex: 10,
536
+ }}
537
+ >
538
+
539
+ </button>
540
+
541
+ {/* Next Button */}
542
+ <button
543
+ onClick={nextCarouselItem}
544
+ disabled={carouselIndex >= images.length - 1}
545
+ style={{
546
+ position: 'absolute',
547
+ right: '10px',
548
+ top: '50%',
549
+ transform: 'translateY(-50%)',
550
+ background: 'rgba(0,0,0,0.5)',
551
+ color: 'white',
552
+ border: 'none',
553
+ borderRadius: '50%',
554
+ width: '40px',
555
+ height: '40px',
556
+ cursor: carouselIndex >= images.length - 1 ? 'not-allowed' : 'pointer',
557
+ opacity: carouselIndex >= images.length - 1 ? 0.5 : 1,
558
+ display: 'flex',
559
+ alignItems: 'center',
560
+ justifyContent: 'center',
561
+ fontSize: '18px',
562
+ zIndex: 10,
563
+ }}
564
+ >
565
+
566
+ </button>
567
+
568
+ {/* Dots Indicator */}
569
+ <div
570
+ style={{
571
+ position: 'absolute',
572
+ bottom: '10px',
573
+ left: '50%',
574
+ transform: 'translateX(-50%)',
575
+ display: 'flex',
576
+ gap: '8px',
577
+ zIndex: 10,
578
+ }}
579
+ >
580
+ {images.map((_, index) => (
581
+ <button
582
+ key={index}
583
+ onClick={() => goToCarouselItem(index)}
584
+ style={{
585
+ width: index === carouselIndex ? '12px' : '8px',
586
+ height: index === carouselIndex ? '12px' : '8px',
587
+ borderRadius: '50%',
588
+ border: 'none',
589
+ background: index === carouselIndex ? 'white' : 'rgba(255,255,255,0.5)',
590
+ cursor: 'pointer',
591
+ transition: 'all 0.3s ease',
592
+ }}
593
+ />
594
+ ))}
595
+ </div>
596
+ </>
597
+ )}
598
+ </div>
599
+ );
600
+ }
601
+
602
+ // Other layouts: grid display with limited images
603
+ const getMaxImages = () => {
604
+ switch (layout) {
605
+ case '1x2': return 2;
606
+ case '2x1': return 2;
607
+ case '2x2': return 4;
608
+ default: return images.length;
609
+ }
610
+ };
611
+
612
+ const maxImages = getMaxImages();
613
+ const displayImages = images.slice(0, maxImages);
614
+
615
+ // Create dynamic content style for GROUPIMAGE
616
+ const groupImageContentStyle = {
617
+ display: 'grid',
618
+ gridTemplateColumns: getGridColumns(),
619
+ gridTemplateRows: getGridRows(),
620
+ gap: '3px',
621
+ height: '100%', // Use auto height for group images
622
+ overflow: 'hidden'
623
+ };
624
+
625
+ return (
626
+ <div style={groupImageContentStyle} className='media-box'>
627
+ {displayImages.map((image, index) => {
628
+ const linkUrl = buildLinkForSlide(image);
629
+ const imageElement = (
630
+ <div key={index} className="media-box">
631
+ <img
632
+ src={getImageUrl(
633
+ tab.tabContentGroupImage.type === 'DYNAMIC'
634
+ ? image.image.url
635
+ : image.attr?.url
636
+ )}
637
+ alt={
638
+ tab.tabContentGroupImage.type === 'DYNAMIC'
639
+ ? image.image.alt
640
+ : image.attr?.alt
641
+ }
642
+ className="media-content"
643
+ />
644
+ </div>
645
+ );
646
+
647
+ return linkUrl ? (
648
+ <a
649
+ key={index}
650
+ href={linkUrl}
651
+ target={
652
+ image.link_type === 'EXTERNALLINK'
653
+ ? image.external_link?.target || '_blank'
654
+ : '_self'
655
+ }
656
+ className='media-box'
657
+ >
658
+ {imageElement}
659
+ </a>
660
+ ) : (
661
+ <div key={index} style={{ width: '100%', height: '100%' }}>
662
+ {imageElement}
663
+ </div>
664
+ );
665
+ })}
666
+ </div>
667
+ );
668
+
669
+ }
670
+ break;
671
+
672
+ case 'GROUPVIDEO':
673
+ if (tab.tabContentGroupVideo) {
674
+ // Handle both DYNAMIC and STATIC types
675
+ const videos = tab.tabContentGroupVideo.type === 'DYNAMIC'
676
+ ? tab.tabContentGroupVideo.dynamicVideos
677
+ : tab.tabContentGroupVideo.showItems;
678
+
679
+ if (!videos || videos.length === 0) {
680
+ return <div>No videos available</div>;
681
+ }
682
+
683
+ // 1x1 layout: carousel display
684
+ if (layout === '1x1') {
685
+ const currentVideo = videos[carouselIndex] || videos[0];
686
+ const linkUrl = currentVideo ? buildLinkForSlide(currentVideo) : null;
687
+
688
+ const videoElement = currentVideo ? (
689
+ <div className="media-box">
690
+ <video
691
+ src={getImageUrl(
692
+ tab.tabContentGroupVideo.type === 'DYNAMIC'
693
+ ? currentVideo.video?.url
694
+ : currentVideo.attr?.url
695
+ )}
696
+ title={
697
+ tab.tabContentGroupVideo.type === 'DYNAMIC'
698
+ ? currentVideo.video?.alt || currentVideo.name
699
+ : currentVideo.attr?.alt }
700
+ className="media-content"
701
+ controls
702
+ style={{ width: '100%', height: 'auto', objectFit: 'cover' }}
703
+ />
704
+ </div>
705
+ ) : null;
706
+
707
+ return (
708
+ <div
709
+ style={{
710
+ position: 'relative',
711
+ width: '100%',
712
+ height: 'auto',
713
+ overflow: 'hidden',
714
+ display: 'flex',
715
+ alignItems: 'stretch',
716
+ justifyContent: 'center',
717
+ }}
718
+ >
719
+ {currentVideo && (
720
+ <div style={{ width: '100%', height: '100%', display: 'flex' }}>
721
+ {linkUrl ? (
722
+ <a
723
+ href={linkUrl}
724
+ target={
725
+ currentVideo.link_type === 'EXTERNALLINK'
726
+ ? currentVideo.external_link?.target || '_blank'
727
+ : '_self'
728
+ }
729
+ style={{ textDecoration: 'none', color: 'inherit', display: 'contents' }}
730
+ >
731
+ {videoElement}
732
+ </a>
733
+ ) : (
734
+ videoElement
735
+ )}
736
+ </div>
737
+ )}
738
+
739
+ {/* Carousel Navigation */}
740
+ {videos.length > 1 && (
741
+ <>
742
+ {/* Prev Button */}
743
+ <button
744
+ onClick={prevCarouselItem}
745
+ disabled={carouselIndex === 0}
746
+ style={{
747
+ position: 'absolute',
748
+ left: '10px',
749
+ top: '50%',
750
+ transform: 'translateY(-50%)',
751
+ background: 'rgba(0,0,0,0.5)',
752
+ color: 'white',
753
+ border: 'none',
754
+ borderRadius: '50%',
755
+ width: '40px',
756
+ height: '40px',
757
+ cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
758
+ opacity: carouselIndex === 0 ? 0.5 : 1,
759
+ display: 'flex',
760
+ alignItems: 'center',
761
+ justifyContent: 'center',
762
+ fontSize: '18px',
763
+ zIndex: 10,
764
+ }}
765
+ >
766
+
767
+ </button>
768
+
769
+ {/* Next Button */}
770
+ <button
771
+ onClick={nextCarouselItem}
772
+ disabled={carouselIndex >= videos.length - 1}
773
+ style={{
774
+ position: 'absolute',
775
+ right: '10px',
776
+ top: '50%',
777
+ transform: 'translateY(-50%)',
778
+ background: 'rgba(0,0,0,0.5)',
779
+ color: 'white',
780
+ border: 'none',
781
+ borderRadius: '50%',
782
+ width: '40px',
783
+ height: '40px',
784
+ cursor: carouselIndex >= videos.length - 1 ? 'not-allowed' : 'pointer',
785
+ opacity: carouselIndex >= videos.length - 1 ? 0.5 : 1,
786
+ display: 'flex',
787
+ alignItems: 'center',
788
+ justifyContent: 'center',
789
+ fontSize: '18px',
790
+ zIndex: 10,
791
+ }}
792
+ >
793
+
794
+ </button>
795
+
796
+ {/* Dots Indicator */}
797
+ <div
798
+ style={{
799
+ position: 'absolute',
800
+ bottom: '10px',
801
+ left: '50%',
802
+ transform: 'translateX(-50%)',
803
+ display: 'flex',
804
+ gap: '8px',
805
+ zIndex: 10,
806
+ }}
807
+ >
808
+ {videos.map((_, index) => (
809
+ <button
810
+ key={index}
811
+ onClick={() => goToCarouselItem(index)}
812
+ style={{
813
+ width: index === carouselIndex ? '12px' : '8px',
814
+ height: index === carouselIndex ? '12px' : '8px',
815
+ borderRadius: '50%',
816
+ border: 'none',
817
+ background: index === carouselIndex ? 'white' : 'rgba(255,255,255,0.5)',
818
+ cursor: 'pointer',
819
+ transition: 'all 0.3s ease',
820
+ }}
821
+ />
822
+ ))}
823
+ </div>
824
+ </>
825
+ )}
826
+ </div>
827
+ );
828
+ }
829
+
830
+ // Other layouts: grid display with limited videos
831
+ const getMaxVideos = () => {
832
+ switch (layout) {
833
+ case '1x2': return 2;
834
+ case '2x1': return 2;
835
+ case '2x2': return 4;
836
+ default: return videos.length;
837
+ }
838
+ };
839
+
840
+ const maxVideos = getMaxVideos();
841
+ const displayVideos = videos.slice(0, maxVideos);
842
+
843
+ // Create dynamic content style for GROUPVIDEO
844
+ const groupVideoContentStyle = {
845
+ display: 'grid',
846
+ gridTemplateColumns: getGridColumns(),
847
+ gridTemplateRows: getGridRows(),
848
+ gap: '3px',
849
+ height: 'auto', // Use auto height for group videos
850
+ overflow: 'hidden'
851
+ };
852
+
853
+ return (
854
+ <div style={groupVideoContentStyle} className='media-box'>
855
+ {displayVideos.map((video, index) => {
856
+ const linkUrl = buildLinkForSlide(video);
857
+
858
+ const videoElement = (
859
+ <div className="media-box">
860
+ <video
861
+ src={getImageUrl(
862
+ tab.tabContentGroupVideo.type === 'DYNAMIC'
863
+ ? video.video?.url
864
+ : video.attr?.url
865
+ )}
866
+ title={
867
+ tab.tabContentGroupVideo.type === 'DYNAMIC'
868
+ ? video.video?.alt || video.name
869
+ : video.attr?.alt
870
+ }
871
+ className="media-content"
872
+ controls
873
+ style={{ width: '100%', height: 'auto', objectFit: 'cover' }}
874
+ />
875
+ </div>
876
+ );
877
+
878
+ return linkUrl ? (
879
+ <a
880
+ key={index}
881
+ href={linkUrl}
882
+ target={
883
+ video.link_type === 'EXTERNALLINK'
884
+ ? video.external_link?.target || '_blank'
885
+ : '_self'
886
+ }
887
+ className='media-box'
888
+ >
889
+ {videoElement}
890
+ </a>
891
+ ) : (
892
+ <div key={index} style={{ width: '100%', height: '100%' }}>
893
+ {videoElement}
894
+ </div>
895
+ );
896
+ })}
897
+ </div>
898
+ );
899
+
900
+ }
901
+ break;
902
+
903
+ case 'GROUPPRODUCT':
904
+ if (tab.tabContentGroupProduct && tab.tabContentGroupProduct.showItems) {
905
+ const products = tab.tabContentGroupProduct.showItems;
906
+
907
+ // 1x1 layout: Carousel view
908
+ if (layout === '1x1') {
909
+ const currentProduct = products[carouselIndex] || products[0];
910
+
911
+ return (
912
+ <div
913
+ style={{
914
+ position: 'relative',
915
+ width: '100%',
916
+ height: `${props.height}px`, // Full height of tab content
917
+ overflow: 'hidden',
918
+ display: 'flex',
919
+ alignItems: 'stretch',
920
+ justifyContent: 'center',
921
+ }}
922
+ >
923
+ {currentProduct && (
924
+ <div style={{ width: '100%', height: '100%', display: 'flex' }}>
925
+ <ProductCard product={currentProduct} layout={layout} />
926
+ </div>
927
+ )}
928
+
929
+ {/* Carousel Navigation */}
930
+ {products.length > 1 && (
931
+ <>
932
+ {/* Prev Button */}
933
+ <button
934
+ onClick={prevCarouselItem}
935
+ disabled={carouselIndex === 0}
936
+ style={{
937
+ position: 'absolute',
938
+ left: '10px',
939
+ top: '50%',
940
+ transform: 'translateY(-50%)',
941
+ background: 'rgba(0,0,0,0.5)',
942
+ color: 'white',
943
+ border: 'none',
944
+ borderRadius: '50%',
945
+ width: '40px',
946
+ height: '40px',
947
+ cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
948
+ opacity: carouselIndex === 0 ? 0.5 : 1,
949
+ display: 'flex',
950
+ alignItems: 'center',
951
+ justifyContent: 'center',
952
+ fontSize: '18px',
953
+ zIndex: 10,
954
+ }}
955
+ >
956
+
957
+ </button>
958
+
959
+ {/* Next Button */}
960
+ <button
961
+ onClick={nextCarouselItem}
962
+ disabled={carouselIndex >= products.length - 1}
963
+ style={{
964
+ position: 'absolute',
965
+ right: '10px',
966
+ top: '50%',
967
+ transform: 'translateY(-50%)',
968
+ background: 'rgba(0,0,0,0.5)',
969
+ color: 'white',
970
+ border: 'none',
971
+ borderRadius: '50%',
972
+ width: '40px',
973
+ height: '40px',
974
+ cursor:
975
+ carouselIndex >= products.length - 1
976
+ ? 'not-allowed'
977
+ : 'pointer',
978
+ opacity: carouselIndex >= products.length - 1 ? 0.5 : 1,
979
+ display: 'flex',
980
+ alignItems: 'center',
981
+ justifyContent: 'center',
982
+ fontSize: '18px',
983
+ zIndex: 10,
984
+ }}
985
+ >
986
+
987
+ </button>
988
+
989
+ {/* Dots */}
990
+ <div
991
+ style={{
992
+ position: 'absolute',
993
+ bottom: '10px',
994
+ left: '50%',
995
+ transform: 'translateX(-50%)',
996
+ display: 'flex',
997
+ gap: '8px',
998
+ zIndex: 10,
999
+ }}
1000
+ >
1001
+ {products.map((_, index) => (
1002
+ <button
1003
+ key={index}
1004
+ onClick={() => goToCarouselItem(index)}
1005
+ style={{
1006
+ width: index === carouselIndex ? '12px' : '8px',
1007
+ height: index === carouselIndex ? '12px' : '8px',
1008
+ borderRadius: '50%',
1009
+ border: 'none',
1010
+ background:
1011
+ index === carouselIndex
1012
+ ? 'white'
1013
+ : 'rgba(255,255,255,0.5)',
1014
+ cursor: 'pointer',
1015
+ transition: 'all 0.3s ease',
1016
+ }}
1017
+ />
1018
+ ))}
1019
+ </div>
1020
+ </>
1021
+ )}
1022
+ </div>
1023
+ );
1024
+ }
1025
+
1026
+ // Other layouts: Grid view with limited products
1027
+ const getMaxProducts = () => {
1028
+ switch (layout) {
1029
+ case '1x2': return 2;
1030
+ case '2x1': return 2;
1031
+ case '2x2': return 4;
1032
+ default: return products.length;
1033
+ }
1034
+ };
1035
+
1036
+ const maxProducts = getMaxProducts();
1037
+ const displayProducts = products.slice(0, maxProducts);
1038
+
1039
+ // Create dynamic content style for GROUPPRODUCT
1040
+ const groupProductContentStyle = {
1041
+ display: 'grid',
1042
+ gridTemplateColumns: getGridColumns(),
1043
+ gridTemplateRows: getGridRows(),
1044
+ gap: '12px',
1045
+ height: 'auto', // Use auto height for group products
1046
+ overflow: 'hidden',
1047
+ padding: '12px',
1048
+ alignItems: 'stretch',
1049
+ };
1050
+
1051
+ return (
1052
+ <div style={groupProductContentStyle}>
1053
+ {displayProducts.map((product, index) => (
1054
+ <div
1055
+ key={index}
1056
+ style={{
1057
+ width: '100%',
1058
+ height: '100%',
1059
+ display: 'flex',
1060
+ alignItems: 'stretch',
1061
+ justifyContent: 'center',
1062
+ minHeight: '200px',
1063
+ }}
1064
+ >
1065
+ <ProductCard product={product} layout={layout} />
1066
+ </div>
1067
+ ))}
1068
+ </div>
1069
+ );
1070
+ }
1071
+ break;
1072
+
1073
+ default:
1074
+ return (
1075
+ <div style={{
1076
+ ...contentStyle,
1077
+ alignItems: 'center',
1078
+ justifyContent: 'center',
1079
+ backgroundColor: '#f8f9fa',
1080
+ borderRadius: '8px'
1081
+ }}>
1082
+ <p style={{ color: '#6c757d', textAlign: 'center' }}>No content available</p>
1083
+ </div>
1084
+ );
1085
+ }
1086
+
1087
+ return null;
1088
+ };
1089
+
1090
+ const tabs = props.tabs[0]?.all || [];
1091
+
1092
+ return (
1093
+ <div style={{
1094
+ width: '100%',
1095
+ minHeight: `${props.height}px`,
1096
+ backgroundColor: '#ffffff',
1097
+ overflow: 'hidden',
1098
+ boxShadow: '0 4px 20px rgba(0,0,0,0.08)',
1099
+ border: '1px solid #e5e7eb'
1100
+ }}>
1101
+ {/* Header Section */}
1102
+ <div style={{
1103
+ display: 'flex',
1104
+ alignItems: 'center',
1105
+ justifyContent: 'space-between',
1106
+ padding: '20px 32px',
1107
+ backgroundColor: '#ffffff',
1108
+ borderBottom: '1px solid #e5e7eb'
1109
+ }}>
1110
+ {/* Title */}
1111
+ {props.showTitle && (
1112
+ <h1 style={getTitleStyle()}>
1113
+ {props?.title?.titleText?.all}
1114
+ </h1>
1115
+ )}
1116
+
1117
+ {/* Tab Headers */}
1118
+ <div style={{
1119
+ display: 'flex',
1120
+ alignItems: 'center',
1121
+ }}>
1122
+ {tabs.map((tab, index) => (
1123
+ <React.Fragment key={index}>
1124
+ <button
1125
+ style={getTabHeaderStyle(index)}
1126
+ onClick={() => handleTabChange(index)}
1127
+ >
1128
+ {tab.tabHeaderType === 'text' ? (
1129
+ tab.tabHeaderText
1130
+ ) : tab.tabHeaderImage.url ? (
1131
+ <img
1132
+ src={getImageUrl(tab.tabHeaderImage.url)}
1133
+ alt={tab.tabHeaderImage.alt}
1134
+ style={{ height: '20px', width: 'auto' }}
1135
+ />
1136
+ ) : (
1137
+ tab.tabHeaderText
1138
+ )}
1139
+ </button>
1140
+
1141
+ {/* Add separator | except after last tab */}
1142
+ {index < tabs.length - 1 && (
1143
+ <span style={{ color: '#9ca3af', margin: '0 4px' }}>|</span>
1144
+ )}
1145
+ </React.Fragment>
1146
+ ))}
1147
+ </div>
1148
+
1149
+ </div>
1150
+
1151
+ {/* Tab Content */}
1152
+ <div style={{
1153
+ position: 'relative',
1154
+ height: 'auto'
1155
+ }}>
1156
+ {tabs.length > 0 && tabs[activeTab] && (
1157
+ <div style={{
1158
+ width: '100%',
1159
+
1160
+ }}>
1161
+ {renderTabContent(tabs[activeTab])}
1162
+ </div>
1163
+ )}
1164
+ </div>
1165
+ </div>
1166
+ );
1167
+ };
1168
+
1169
+ export default TabComponent;