tee3apps-cms-sdk-react 0.0.34 → 0.0.36

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.
@@ -127,6 +127,8 @@ export interface TabComponentProps {
127
127
  showTitle: boolean;
128
128
  title: TitleStyle;
129
129
  header: HeaderStyle;
130
+ showsp?: boolean;
131
+ showdiscount?: boolean;
130
132
  tabs: TabItem[] | TabsData; // Support both array (new) and object with locale keys (old)
131
133
  }
132
134
 
@@ -141,14 +143,14 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
141
143
  const [hoveredTab, setHoveredTab] = useState<number | null>(null);
142
144
  const [hasError, setHasError] = useState(false);
143
145
  const [errorMessage, setErrorMessage] = useState<string>('');
144
-
146
+
145
147
  // Get scroll type and objectFit from props
146
148
  const scrollType = props.scroll || 'horizontal';
147
149
  const objectFit = props.objectFit || 'fill';
148
150
 
149
151
  // Extract tabs - handle both new structure (array) and old structure (object with locale keys)
150
- const tabs = Array.isArray(props.tabs)
151
- ? props.tabs
152
+ const tabs = Array.isArray(props.tabs)
153
+ ? props.tabs
152
154
  : (props.tabs?.all || props.tabs?.['en-IN'] || []);
153
155
 
154
156
  // Validate props structure
@@ -163,8 +165,8 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
163
165
  }
164
166
 
165
167
  // Check if tabs is array or object with valid structure
166
- const tabs = Array.isArray(props.tabs)
167
- ? props.tabs
168
+ const tabs = Array.isArray(props.tabs)
169
+ ? props.tabs
168
170
  : (props.tabs?.all || props.tabs?.['en-IN'] || []);
169
171
 
170
172
  if (!Array.isArray(tabs) || tabs.length === 0) {
@@ -301,7 +303,7 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
301
303
  // Helper function to extract YouTube video ID from various URL formats
302
304
  const getYouTubeVideoId = (url: string): string | null => {
303
305
  if (!url) return null;
304
-
306
+
305
307
  // Handle various YouTube URL formats
306
308
  const patterns = [
307
309
  /(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?v=([^&]+)/,
@@ -315,7 +317,7 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
315
317
  return match[1];
316
318
  }
317
319
  }
318
-
320
+
319
321
  return null;
320
322
  };
321
323
 
@@ -324,19 +326,19 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
324
326
  if (isDynamic) {
325
327
  return videoData?.video?.playerType === 'Youtube' || videoData?.video?.playerType === 'youtube';
326
328
  } else {
327
- return videoData?.attr?.all?.playerType === 'Youtube' ||
328
- videoData?.attr?.all?.playerType === 'youtube' ||
329
- videoData?.attr?.playerType === 'Youtube' ||
330
- videoData?.attr?.playerType === 'youtube';
329
+ return videoData?.attr?.all?.playerType === 'Youtube' ||
330
+ videoData?.attr?.all?.playerType === 'youtube' ||
331
+ videoData?.attr?.playerType === 'Youtube' ||
332
+ videoData?.attr?.playerType === 'youtube';
331
333
  }
332
334
  };
333
335
 
334
336
  // Render video player (YouTube or HTML5)
335
337
  const renderVideoPlayer = (videoData: any, isDynamic: boolean, className: string = 'media-content', style: React.CSSProperties = {}) => {
336
338
  console.log('renderVideoPlayer called with:', { videoData, isDynamic });
337
-
339
+
338
340
  const isYouTube = isYouTubeVideo(videoData, isDynamic);
339
-
341
+
340
342
  // Get video URL with proper fallback - handle both dynamic and static videos
341
343
  let videoUrl = '';
342
344
  if (isDynamic) {
@@ -345,21 +347,21 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
345
347
  // Static videos: check multiple possible locations
346
348
  videoUrl = videoData?.attr?.all?.url || videoData?.attr?.url || videoData?.video?.url || videoData?.url || '';
347
349
  }
348
-
350
+
349
351
  console.log('Video URL extracted:', videoUrl, 'isYouTube:', isYouTube);
350
-
352
+
351
353
  // Get video attributes - handle both dynamic and static videos
352
- const videoAlt = isDynamic
354
+ const videoAlt = isDynamic
353
355
  ? (videoData?.video?.alt || videoData?.alt || videoData?.name || 'Video')
354
356
  : (videoData?.attr?.all?.alt || videoData?.attr?.alt || videoData?.video?.alt || videoData?.alt || 'Video');
355
-
357
+
356
358
  // Get controls setting with proper defaults (default to true if undefined)
357
- const controls = isDynamic
359
+ const controls = isDynamic
358
360
  ? (videoData?.video?.controls !== undefined ? videoData.video.controls : true)
359
361
  : (videoData?.attr?.all?.controls !== undefined ? videoData.attr.all.controls : (videoData?.attr?.controls !== undefined ? videoData.attr.controls : true));
360
-
362
+
361
363
  // Get loop setting (default to false if undefined)
362
- const loop = isDynamic
364
+ const loop = isDynamic
363
365
  ? (videoData?.video?.loop === true)
364
366
  : (videoData?.attr?.all?.loop === true || videoData?.attr?.loop === true);
365
367
 
@@ -401,7 +403,7 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
401
403
  // Fallback to HTML5 video - always use Linode URL
402
404
  const finalVideoUrl = getImageUrl(videoUrl);
403
405
  console.log('Rendering HTML5 video with URL:', finalVideoUrl);
404
-
406
+
405
407
  // Create a video component with proper handling for no-controls videos
406
408
  const VideoComponent = () => {
407
409
  const videoRef = useRef<HTMLVideoElement>(null);
@@ -446,12 +448,12 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
446
448
  muted={autoplay || !controls} // Mute if autoplay or no controls (required for autoplay in browsers)
447
449
  playsInline={true} // Required for mobile devices
448
450
  onClick={handleVideoClick}
449
- style={{
450
- width: '100%',
451
- height: '100%',
452
- objectFit: (props.objectFit || 'contain') as any,
451
+ style={{
452
+ width: '100%',
453
+ height: '100%',
454
+ objectFit: (props.objectFit || 'contain') as any,
453
455
  cursor: controls ? 'default' : 'pointer',
454
- ...style
456
+ ...style
455
457
  }}
456
458
  onError={(e) => {
457
459
  console.error('Video failed to load:', e, 'URL:', finalVideoUrl);
@@ -541,8 +543,8 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
541
543
  const sp = product.price?.SP;
542
544
  const offer = calculateOffer(mrp, sp);
543
545
 
544
- const cardMode = layout === '1x1' ? 'carousel-mode' : layout === '2x1' ? 'layout-2x1' : 'grid-mode';
545
-
546
+ const cardMode = layout === '1x1' ? 'carousel-mode' : layout === '2x1' ? 'layout-2x1' : 'grid-mode';
547
+
546
548
  const cardContent = (
547
549
  <>
548
550
  {/* Product Image */}
@@ -584,7 +586,7 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
584
586
  ))}
585
587
  </div>
586
588
  )}
587
- {/* {layout === '1x1' && product.variant && product.variant.length > 0 && (
589
+ {/* {layout === '1x1' && product.variant && product.variant.length > 0 && (
588
590
  <div className="product-variants">
589
591
  {product.variant.map((variant: any, index: number) => (
590
592
  <span key={index} className="variant-tag">
@@ -595,17 +597,27 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
595
597
  )} */}
596
598
  {/* Pricing */}
597
599
  <div className="product-pricing">
598
- <div className="price-row">
599
- <span className="current-price">{formatPrice(sp)}</span>
600
- {mrp && sp && (() => {
601
- const mrpValue = parseFloat(mrp?.$numberDecimal || mrp || 0);
602
- const spValue = parseFloat(sp?.$numberDecimal || sp || 0);
603
- return mrpValue > spValue && mrpValue > 0;
604
- })() && (
605
- <>
606
- <span className="original-price">{formatPrice(mrp)}</span>
607
- {offer > 0 && <span className="offer-badge">{offer}% OFF</span>}
608
- </>
600
+ <div className="price-row" style={{ display: 'flex', flexDirection: 'column', gap: '2px' }}>
601
+ {props.showsp !== false && (
602
+ <div style={{ display: 'flex', alignItems: 'center', gap: '6px', flexWrap: 'wrap' }}>
603
+ <span className="current-price" style={{ fontSize: '15px', fontWeight: 'bold' }}>
604
+ SP: {formatPrice(sp)} (incl of all tax)
605
+ </span>
606
+ {props.showdiscount !== false && mrp && sp && (() => {
607
+ const mrpValue = parseFloat(mrp?.$numberDecimal || mrp || 0);
608
+ const spValue = parseFloat(sp?.$numberDecimal || sp || 0);
609
+ return mrpValue > spValue && mrpValue > 0;
610
+ })() && (
611
+ <span className="original-price" style={{ fontSize: '12px', color: '#666', textDecoration: 'line-through' }}>
612
+ MRP: {formatPrice(mrp)}
613
+ </span>
614
+ )}
615
+ </div>
616
+ )}
617
+ {props.showdiscount !== false && offer > 0 && (
618
+ <span className="offer-badge" style={{ fontSize: '11px', color: '#28a745', fontWeight: 'bold', width: 'fit-content' }}>
619
+ {offer}% OFF
620
+ </span>
609
621
  )}
610
622
  </div>
611
623
  </div>
@@ -622,11 +634,11 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
622
634
  </div>
623
635
  )}
624
636
 
625
-
637
+
626
638
  </div>
627
639
  </>
628
640
  );
629
-
641
+
630
642
  const productCode = typeof product.code === 'string' ? product.code : getDisplayText(product.code) || product.sku || '';
631
643
  return (
632
644
  <a href={productCode ? `/${productCode}` : '#'} className={`product-card ${cardMode}`} style={{ textDecoration: 'none', color: 'inherit' }}>
@@ -641,34 +653,34 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
641
653
  // Get current tab to determine max slides
642
654
  const currentTab = tabs[activeTab];
643
655
  if (!currentTab) return prev;
644
-
656
+
645
657
  const layout = getCurrentLayout(currentTab);
646
658
  const itemsPerSlide = layout === '2x2' ? 4 : (layout === '1x2' || layout === '2x1') ? 2 : 1;
647
-
659
+
648
660
  let maxSlides = 0;
649
661
  if (currentTab.tabContentType === 'GROUPIMAGE' && currentTab.tabContentGroupImage) {
650
662
  const items = currentTab.tabContentGroupImage.showItems && currentTab.tabContentGroupImage.showItems.length > 0
651
663
  ? currentTab.tabContentGroupImage.showItems
652
- : (currentTab.tabContentGroupImage.type === 'DYNAMIC'
653
- ? currentTab.tabContentGroupImage.dynamicImages
664
+ : (currentTab.tabContentGroupImage.type === 'DYNAMIC'
665
+ ? currentTab.tabContentGroupImage.dynamicImages
654
666
  : (currentTab.tabContentGroupImage.staticImages || []));
655
667
  maxSlides = items?.length ? (layout !== '1x1' ? Math.ceil(items.length / itemsPerSlide) : items.length) : 0;
656
668
  } else if (currentTab.tabContentType === 'GROUPVIDEO' && currentTab.tabContentGroupVideo) {
657
669
  const items = currentTab.tabContentGroupVideo.showItems && currentTab.tabContentGroupVideo.showItems.length > 0
658
670
  ? currentTab.tabContentGroupVideo.showItems
659
- : (currentTab.tabContentGroupVideo.type === 'DYNAMIC'
660
- ? currentTab.tabContentGroupVideo.dynamicVideos
671
+ : (currentTab.tabContentGroupVideo.type === 'DYNAMIC'
672
+ ? currentTab.tabContentGroupVideo.dynamicVideos
661
673
  : (currentTab.tabContentGroupVideo.staticVideos || []));
662
674
  maxSlides = items?.length ? (layout !== '1x1' ? Math.ceil(items.length / itemsPerSlide) : items.length) : 0;
663
675
  } else if (currentTab.tabContentType === 'GROUPPRODUCT' && currentTab.tabContentGroupProduct) {
664
676
  const items = currentTab.tabContentGroupProduct.showItems && currentTab.tabContentGroupProduct.showItems.length > 0
665
677
  ? currentTab.tabContentGroupProduct.showItems
666
- : (currentTab.tabContentGroupProduct.type === 'DYNAMIC'
667
- ? currentTab.tabContentGroupProduct.dynamic?.list
678
+ : (currentTab.tabContentGroupProduct.type === 'DYNAMIC'
679
+ ? currentTab.tabContentGroupProduct.dynamic?.list
668
680
  : currentTab.tabContentGroupProduct.staticProducts);
669
681
  maxSlides = items?.length ? (layout !== '1x1' ? Math.ceil(items.length / itemsPerSlide) : items.length) : 0;
670
682
  }
671
-
683
+
672
684
  return maxSlides > 0 ? Math.min(prev + 1, maxSlides - 1) : prev;
673
685
  });
674
686
  }, [activeTab, tabs]);
@@ -764,618 +776,113 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
764
776
 
765
777
  const layout = getCurrentLayout(tab);
766
778
  console.warn(tab)
767
- const getGridColumns = () => {
768
- switch (layout) {
769
- case '1x1': return '1fr';
770
- case '1x2': return 'repeat(2, 1fr)';
771
- case '2x1': return '1fr';
772
- case '2x2': return 'repeat(2, 1fr)';
773
- default: return '1fr';
774
- }
775
- };
776
-
777
- const getGridRows = () => {
778
- switch (layout) {
779
- case '1x1': return '1fr';
780
- case '1x2': return '1fr';
781
- case '2x1': return 'repeat(2, minmax(0, 1fr))';
782
- case '2x2': return 'repeat(2, minmax(0, 1fr))';
783
- default: return '1fr';
784
- }
785
- };
786
-
787
- const contentStyle = {
788
- display: 'grid',
789
- gridTemplateColumns: getGridColumns(),
790
- gridTemplateRows: getGridRows(),
791
- gap: '3px',
792
- height: '100%', // force tab content height
793
- overflow: 'hidden'
794
- };
795
-
796
-
797
- switch (tab.tabContentType) {
798
- case 'IMAGE':
799
- if (tab.tabContentImage) {
800
- return (
801
- <div style={contentStyle}>
802
- <div className="media-box">
803
- <img
804
- src={getImageUrl(tab.tabContentImage.image.url)}
805
- alt={tab.tabContentImage.image.alt}
806
- className="media-content"
807
- style={{ objectFit: objectFit as any, width: '100%', height: '100%' }}
808
- />
809
- </div>
810
- </div>
811
- );
779
+ const getGridColumns = () => {
780
+ switch (layout) {
781
+ case '1x1': return '1fr';
782
+ case '1x2': return 'repeat(2, 1fr)';
783
+ case '2x1': return '1fr';
784
+ case '2x2': return 'repeat(2, 1fr)';
785
+ default: return '1fr';
812
786
  }
813
- break;
814
- case 'VIDEO':
815
- if (tab.tabContentVideo) {
816
- const videoUrl = tab.tabContentVideo.video.url;
817
- const videoAlt = tab.tabContentVideo.video.alt || 'Video';
818
- // Access playerType from video object (may not be in type definition but exists in runtime)
819
- const videoData = tab.tabContentVideo.video as any;
820
- const playerType = videoData?.playerType;
821
-
822
- if (!videoUrl) {
823
- return <div style={{ padding: '20px', textAlign: 'center' }}>No video URL available</div>;
824
- }
825
-
826
- // Check if it's a YouTube video (by playerType or URL)
827
- const isYouTube = playerType === 'Youtube' || playerType === 'youtube' || getYouTubeVideoId(videoUrl) !== null;
828
- const youtubeId = getYouTubeVideoId(videoUrl);
829
-
830
- return (
831
- <div style={contentStyle}>
832
- <div className="media-box">
833
- {isYouTube && youtubeId ? (
834
- <iframe
835
- src={`https://www.youtube.com/embed/${youtubeId}`}
836
- title={videoAlt}
837
- className="media-content"
838
- style={{ width: '100%', height: '100%', border: 'none' }}
839
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
840
- allowFullScreen
841
- />
842
- ) : (
843
- <video
844
- src={getImageUrl(videoUrl)}
845
- controls
846
- className="media-content"
847
- style={{ width: '100%', height: '100%', objectFit: objectFit as any }}
848
- />
849
- )}
850
- </div>
851
- </div>
852
- );
787
+ };
788
+
789
+ const getGridRows = () => {
790
+ switch (layout) {
791
+ case '1x1': return '1fr';
792
+ case '1x2': return '1fr';
793
+ case '2x1': return 'repeat(2, minmax(0, 1fr))';
794
+ case '2x2': return 'repeat(2, minmax(0, 1fr))';
795
+ default: return '1fr';
853
796
  }
854
- break;
797
+ };
855
798
 
856
- case 'GROUPIMAGE':
857
- if (tab.tabContentGroupImage) {
858
- // Prioritize showItems, fallback to dynamic/static arrays
859
- const images = tab.tabContentGroupImage.showItems && tab.tabContentGroupImage.showItems.length > 0
860
- ? tab.tabContentGroupImage.showItems
861
- : (tab.tabContentGroupImage.type === 'DYNAMIC'
862
- ? tab.tabContentGroupImage.dynamicImages
863
- : (tab.tabContentGroupImage.staticImages || []));
864
-
865
- if (!images || images.length === 0) {
866
- return <div>No images available</div>;
867
- }
868
-
869
- // 1x1 layout: use scrollbar if scroll type is set, otherwise carousel
870
- if (layout === '1x1' && scrollType) {
871
- // Use scrollable container with scrollbar - each item takes full width/height
872
- const scrollDirection = scrollType === 'horizontal' ? 'row' : 'column';
873
-
799
+ const contentStyle = {
800
+ display: 'grid',
801
+ gridTemplateColumns: getGridColumns(),
802
+ gridTemplateRows: getGridRows(),
803
+ gap: '3px',
804
+ height: '100%', // force tab content height
805
+ overflow: 'hidden'
806
+ };
807
+
808
+
809
+ switch (tab.tabContentType) {
810
+ case 'IMAGE':
811
+ if (tab.tabContentImage) {
874
812
  return (
875
- <div
876
- style={{
877
- width: '100%',
878
- height: '100%',
879
- overflow: scrollType === 'horizontal' ? 'auto' : 'auto',
880
- display: 'flex',
881
- flexDirection: scrollDirection as any,
882
- scrollSnapType: scrollType === 'horizontal' ? 'x mandatory' : 'y mandatory',
883
- gap: 0,
884
- padding: 0,
885
- }}
886
- className="scroll-container"
887
- >
888
- {images.map((image, index) => (
889
- <div
890
- key={index}
891
- className="media-box"
892
- style={{
893
- width: scrollType === 'horizontal' ? '100%' : '100%',
894
- height: scrollType === 'vertical' ? '100%' : '100%',
895
- minWidth: scrollType === 'horizontal' ? '100%' : '100%',
896
- minHeight: scrollType === 'vertical' ? '100%' : '100%',
897
- flexShrink: 0,
898
- scrollSnapAlign: 'start',
899
- }}
900
- >
901
- <img
902
- src={getImageUrl(
903
- getImageItemUrl(image, tab.tabContentGroupImage.type)
904
- )}
905
- alt={
906
- getImageItemAlt(image, tab.tabContentGroupImage.type)
907
- }
908
- className="media-content"
909
- style={{ objectFit: objectFit as any, width: '100%', height: '100%' }}
910
- />
911
- </div>
912
- ))}
813
+ <div style={contentStyle}>
814
+ <div className="media-box">
815
+ <img
816
+ src={getImageUrl(tab.tabContentImage.image.url)}
817
+ alt={tab.tabContentImage.image.alt}
818
+ className="media-content"
819
+ style={{ objectFit: objectFit as any, width: '100%', height: '100%' }}
820
+ />
821
+ </div>
913
822
  </div>
914
823
  );
915
- }
916
-
917
- // 1x1 layout: carousel display (fallback when scroll is not set)
918
- if (layout === '1x1') {
919
- const currentImage = images[carouselIndex] || images[0];
920
-
921
- const imageElement = currentImage ? (
922
- <div className="media-box" style={{ width: '100%', height: '100%' }}>
923
- <img
924
- src={getImageUrl(
925
- getImageItemUrl(currentImage, tab.tabContentGroupImage.type)
926
- )}
927
- alt={
928
- getImageItemAlt(currentImage, tab.tabContentGroupImage.type)
929
- }
930
- className="media-content"
931
- style={{ objectFit: objectFit as any, width: '100%', height: '100%' }}
932
- />
933
- </div>
934
- ) : null;
935
-
936
- return (
937
- <div
938
- style={{
939
- position: 'relative',
940
- width: '100%',
941
- height: '100%',
942
- overflow: 'hidden',
943
- display: 'flex',
944
- alignItems: 'center',
945
- justifyContent: 'center',
946
- }}
947
- >
948
- {imageElement}
949
-
950
- {/* Carousel Navigation */}
951
- {images.length > 1 && (
952
- <>
953
- {/* Previous Button */}
954
- <button
955
- onClick={prevCarouselItem}
956
- disabled={carouselIndex === 0}
957
- style={{
958
- position: 'absolute',
959
- left: '10px',
960
- top: '50%',
961
- transform: 'translateY(-50%)',
962
- background: 'rgba(0,0,0,0.5)',
963
- color: 'white',
964
- border: 'none',
965
- borderRadius: '50%',
966
- width: '40px',
967
- height: '40px',
968
- cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
969
- opacity: carouselIndex === 0 ? 0.5 : 1,
970
- display: 'flex',
971
- alignItems: 'center',
972
- justifyContent: 'center',
973
- fontSize: '18px',
974
- zIndex: 10,
975
- }}
976
- >
977
-
978
- </button>
979
-
980
- {/* Next Button */}
981
- <button
982
- onClick={nextCarouselItem}
983
- disabled={carouselIndex >= images.length - 1}
984
- style={{
985
- position: 'absolute',
986
- right: '10px',
987
- top: '50%',
988
- transform: 'translateY(-50%)',
989
- background: 'rgba(0,0,0,0.5)',
990
- color: 'white',
991
- border: 'none',
992
- borderRadius: '50%',
993
- width: '40px',
994
- height: '40px',
995
- cursor: carouselIndex >= images.length - 1 ? 'not-allowed' : 'pointer',
996
- opacity: carouselIndex >= images.length - 1 ? 0.5 : 1,
997
- display: 'flex',
998
- alignItems: 'center',
999
- justifyContent: 'center',
1000
- fontSize: '18px',
1001
- zIndex: 10,
1002
- }}
1003
- >
1004
-
1005
- </button>
1006
-
1007
- {/* Dots Indicator */}
1008
- <div
1009
- style={{
1010
- position: 'absolute',
1011
- bottom: '10px',
1012
- left: '50%',
1013
- transform: 'translateX(-50%)',
1014
- display: 'flex',
1015
- gap: '8px',
1016
- zIndex: 10,
1017
- }}
1018
- >
1019
- {images.map((_, index) => (
1020
- <button
1021
- key={index}
1022
- onClick={() => goToCarouselItem(index)}
1023
- style={{
1024
- width: index === carouselIndex ? '12px' : '8px',
1025
- height: index === carouselIndex ? '12px' : '8px',
1026
- borderRadius: '50%',
1027
- border: 'none',
1028
- background: index === carouselIndex ? 'white' : 'rgba(255,255,255,0.5)',
1029
- cursor: 'pointer',
1030
- transition: 'all 0.3s ease',
1031
- }}
1032
- />
1033
- ))}
1034
- </div>
1035
- </>
1036
- )}
1037
- </div>
1038
- );
1039
- }
1040
-
1041
- // Other layouts: grid display with limited images
1042
- const getMaxImages = () => {
1043
- switch (layout) {
1044
- case '1x2': return 2;
1045
- case '2x1': return 2;
1046
- case '2x2': return 4;
1047
- default: return images.length;
1048
- }
1049
- };
1050
-
1051
- // For grid layouts (1x2, 2x1, 2x2), show one grid per slide
1052
- // For 1x1 layout with scroll, show one item at a time
1053
- if (layout !== '1x1' && scrollType) {
1054
- // Group items into slides based on layout
1055
- const itemsPerSlide = layout === '2x2' ? 4 : 2; // 4 for 2x2, 2 for 1x2/2x1
1056
- const slides: any[][] = [];
1057
- for (let i = 0; i < images.length; i += itemsPerSlide) {
1058
- slides.push(images.slice(i, i + itemsPerSlide));
824
+ }
825
+ break;
826
+ case 'VIDEO':
827
+ if (tab.tabContentVideo) {
828
+ const videoUrl = tab.tabContentVideo.video.url;
829
+ const videoAlt = tab.tabContentVideo.video.alt || 'Video';
830
+ // Access playerType from video object (may not be in type definition but exists in runtime)
831
+ const videoData = tab.tabContentVideo.video as any;
832
+ const playerType = videoData?.playerType;
833
+
834
+ if (!videoUrl) {
835
+ return <div style={{ padding: '20px', textAlign: 'center' }}>No video URL available</div>;
1059
836
  }
1060
-
1061
- const scrollDirection = scrollType === 'horizontal' ? 'row' : 'column';
1062
-
1063
- return (
1064
- <div
1065
- style={{
1066
- width: '100%',
1067
- height: '100%',
1068
- overflow: scrollType === 'horizontal' ? 'auto' : 'auto',
1069
- display: 'flex',
1070
- flexDirection: scrollDirection as any,
1071
- scrollSnapType: scrollType === 'horizontal' ? 'x mandatory' : 'y mandatory',
1072
- gap: 0,
1073
- padding: 0,
1074
- }}
1075
- className="scroll-container"
1076
- >
1077
- {slides.map((slideImages, slideIndex) => (
1078
- <div
1079
- key={slideIndex}
1080
- style={{
1081
- width: scrollType === 'horizontal' ? '100%' : '100%',
1082
- height: scrollType === 'vertical' ? '100%' : '100%',
1083
- minWidth: scrollType === 'horizontal' ? '100%' : '100%',
1084
- minHeight: scrollType === 'vertical' ? '100%' : '100%',
1085
- flexShrink: 0,
1086
- scrollSnapAlign: 'start',
1087
- display: 'grid',
1088
- gridTemplateColumns: getGridColumns(),
1089
- gridTemplateRows: getGridRows(),
1090
- gap: '3px',
1091
- overflow: 'hidden',
1092
- boxSizing: 'border-box',
1093
- }}
1094
- >
1095
- {slideImages.map((image, index) => (
1096
- <div
1097
- key={index}
1098
- className="media-box"
1099
- style={{
1100
- width: '100%',
1101
- height: '100%',
1102
- minHeight: 0,
1103
- overflow: 'hidden',
1104
- display: 'flex',
1105
- alignItems: 'center',
1106
- justifyContent: 'center',
1107
- boxSizing: 'border-box',
1108
- }}
1109
- >
1110
- <img
1111
- src={getImageUrl(
1112
- tab.tabContentGroupImage.type === 'DYNAMIC'
1113
- ? image.image?.url || image.url
1114
- : image.attr?.all?.url || image.attr?.url || image.url
1115
- )}
1116
- alt={
1117
- tab.tabContentGroupImage.type === 'DYNAMIC'
1118
- ? image.image?.alt || image.alt || ''
1119
- : image.attr?.all?.alt || image.attr?.alt || image.alt || ''
1120
- }
1121
- className="media-content"
1122
- style={{ objectFit: objectFit as any, width: '100%', height: '100%' }}
1123
- />
1124
- </div>
1125
- ))}
1126
- </div>
1127
- ))}
1128
- </div>
1129
- );
1130
- }
1131
-
1132
- // For 1x1 layout with scroll, show one item at a time (carousel-like)
1133
- if (layout === '1x1' && scrollType) {
1134
- const scrollDirection = scrollType === 'horizontal' ? 'row' : 'column';
837
+
838
+ // Check if it's a YouTube video (by playerType or URL)
839
+ const isYouTube = playerType === 'Youtube' || playerType === 'youtube' || getYouTubeVideoId(videoUrl) !== null;
840
+ const youtubeId = getYouTubeVideoId(videoUrl);
841
+
1135
842
  return (
1136
- <div
1137
- style={{
1138
- width: '100%',
1139
- height: '100%',
1140
- overflow: scrollType === 'horizontal' ? 'auto' : 'auto',
1141
- display: 'flex',
1142
- flexDirection: scrollDirection as any,
1143
- scrollSnapType: scrollType === 'horizontal' ? 'x mandatory' : 'y mandatory',
1144
- gap: 0,
1145
- padding: 0,
1146
- }}
1147
- className="scroll-container"
1148
- >
1149
- {images.map((image, index) => (
1150
- <div
1151
- key={index}
1152
- className="media-box"
1153
- style={{
1154
- width: scrollType === 'horizontal' ? '100%' : '100%',
1155
- height: scrollType === 'vertical' ? '100%' : '100%',
1156
- minWidth: scrollType === 'horizontal' ? '100%' : '100%',
1157
- minHeight: scrollType === 'vertical' ? '100%' : '100%',
1158
- flexShrink: 0,
1159
- scrollSnapAlign: 'start',
1160
- }}
1161
- >
1162
- <img
1163
- src={getImageUrl(
1164
- getImageItemUrl(image, tab.tabContentGroupImage.type)
1165
- )}
1166
- alt={
1167
- getImageItemAlt(image, tab.tabContentGroupImage.type)
1168
- }
843
+ <div style={contentStyle}>
844
+ <div className="media-box">
845
+ {isYouTube && youtubeId ? (
846
+ <iframe
847
+ src={`https://www.youtube.com/embed/${youtubeId}`}
848
+ title={videoAlt}
1169
849
  className="media-content"
1170
- style={{ objectFit: objectFit as any, width: '100%', height: '100%' }}
850
+ style={{ width: '100%', height: '100%', border: 'none' }}
851
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
852
+ allowFullScreen
1171
853
  />
1172
- </div>
1173
- ))}
1174
- </div>
1175
- );
1176
- }
1177
-
1178
- // Grid layout without scroll - show one grid per slide with carousel navigation
1179
- if (layout !== '1x1') {
1180
- // Group items into slides based on layout
1181
- const itemsPerSlide = layout === '2x2' ? 4 : 2; // 4 for 2x2, 2 for 1x2/2x1
1182
- const slides: any[][] = [];
1183
- for (let i = 0; i < images.length; i += itemsPerSlide) {
1184
- slides.push(images.slice(i, i + itemsPerSlide));
1185
- }
1186
-
1187
- const currentSlide = slides[carouselIndex] || slides[0] || [];
1188
-
1189
- const groupImageContentStyle: React.CSSProperties = {
1190
- display: 'grid',
1191
- gridTemplateColumns: getGridColumns(),
1192
- gridTemplateRows: getGridRows(),
1193
- gap: '3px',
1194
- height: '100%',
1195
- width: '100%',
1196
- overflow: 'hidden',
1197
- alignContent: 'stretch',
1198
- boxSizing: 'border-box',
1199
- };
1200
-
1201
- return (
1202
- <div style={{ position: 'relative', width: '100%', height: '100%', overflow: 'hidden' }}>
1203
- <div style={groupImageContentStyle}>
1204
- {currentSlide.map((image, index) => (
1205
- <div
1206
- key={index}
1207
- className="media-box"
1208
- style={{
1209
- width: '100%',
1210
- height: '100%',
1211
- minHeight: 0,
1212
- minWidth: 0,
1213
- overflow: 'hidden',
1214
- display: 'flex',
1215
- alignItems: 'center',
1216
- justifyContent: 'center',
1217
- boxSizing: 'border-box',
1218
- }}
1219
- >
1220
- <img
1221
- src={getImageUrl(
1222
- tab.tabContentGroupImage.type === 'DYNAMIC'
1223
- ? image.image?.url || image.url
1224
- : image.attr?.all?.url || image.attr?.url || image.url
1225
- )}
1226
- alt={
1227
- tab.tabContentGroupImage.type === 'DYNAMIC'
1228
- ? image.image?.alt || image.alt || ''
1229
- : image.attr?.all?.alt || image.attr?.alt || image.alt || ''
1230
- }
1231
- className="media-content"
1232
- style={{ objectFit: objectFit as any, width: '100%', height: '100%' }}
1233
- />
1234
- </div>
1235
- ))}
854
+ ) : (
855
+ <video
856
+ src={getImageUrl(videoUrl)}
857
+ controls
858
+ className="media-content"
859
+ style={{ width: '100%', height: '100%', objectFit: objectFit as any }}
860
+ />
861
+ )}
1236
862
  </div>
1237
-
1238
- {/* Carousel Navigation for grid slides */}
1239
- {slides.length > 1 && (
1240
- <>
1241
- <button
1242
- onClick={prevCarouselItem}
1243
- disabled={carouselIndex === 0}
1244
- style={{
1245
- position: 'absolute',
1246
- left: '10px',
1247
- top: '50%',
1248
- transform: 'translateY(-50%)',
1249
- background: 'rgba(0,0,0,0.5)',
1250
- color: 'white',
1251
- border: 'none',
1252
- borderRadius: '50%',
1253
- width: '40px',
1254
- height: '40px',
1255
- cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
1256
- opacity: carouselIndex === 0 ? 0.5 : 1,
1257
- display: 'flex',
1258
- alignItems: 'center',
1259
- justifyContent: 'center',
1260
- fontSize: '18px',
1261
- zIndex: 10,
1262
- }}
1263
- >
1264
-
1265
- </button>
1266
- <button
1267
- onClick={nextCarouselItem}
1268
- disabled={carouselIndex >= slides.length - 1}
1269
- style={{
1270
- position: 'absolute',
1271
- right: '10px',
1272
- top: '50%',
1273
- transform: 'translateY(-50%)',
1274
- background: 'rgba(0,0,0,0.5)',
1275
- color: 'white',
1276
- border: 'none',
1277
- borderRadius: '50%',
1278
- width: '40px',
1279
- height: '40px',
1280
- cursor: carouselIndex >= slides.length - 1 ? 'not-allowed' : 'pointer',
1281
- opacity: carouselIndex >= slides.length - 1 ? 0.5 : 1,
1282
- display: 'flex',
1283
- alignItems: 'center',
1284
- justifyContent: 'center',
1285
- fontSize: '18px',
1286
- zIndex: 10,
1287
- }}
1288
- >
1289
-
1290
- </button>
1291
- <div
1292
- style={{
1293
- position: 'absolute',
1294
- bottom: '10px',
1295
- left: '50%',
1296
- transform: 'translateX(-50%)',
1297
- display: 'flex',
1298
- gap: '8px',
1299
- zIndex: 10,
1300
- }}
1301
- >
1302
- {slides.map((_, index) => (
1303
- <button
1304
- key={index}
1305
- onClick={() => goToCarouselItem(index)}
1306
- style={{
1307
- width: index === carouselIndex ? '12px' : '8px',
1308
- height: index === carouselIndex ? '12px' : '8px',
1309
- borderRadius: '50%',
1310
- border: 'none',
1311
- background: index === carouselIndex ? 'white' : 'rgba(255,255,255,0.5)',
1312
- cursor: 'pointer',
1313
- transition: 'all 0.3s ease',
1314
- }}
1315
- />
1316
- ))}
1317
- </div>
1318
- </>
1319
- )}
1320
863
  </div>
1321
864
  );
1322
- }
1323
-
1324
- // Fallback for 1x1 or other cases
1325
- const maxImages = getMaxImages();
1326
- const displayImages = images.slice(0, maxImages);
1327
-
1328
- const groupImageContentStyle = {
1329
- display: 'grid',
1330
- gridTemplateColumns: getGridColumns(),
1331
- gridTemplateRows: getGridRows(),
1332
- gap: '3px',
1333
- height: '100%',
1334
- width: '100%',
1335
- overflow: 'hidden'
1336
- };
1337
-
1338
- return (
1339
- <div style={groupImageContentStyle}>
1340
- {displayImages.map((image, index) => (
1341
- <div key={index} className="media-box" style={{ width: '100%', height: '100%', minHeight: 0, minWidth: 0 }}>
1342
- <img
1343
- src={getImageUrl(
1344
- getImageItemUrl(image, tab.tabContentGroupImage.type)
1345
- )}
1346
- alt={
1347
- getImageItemAlt(image, tab.tabContentGroupImage.type)
1348
- }
1349
- className="media-content"
1350
- style={{ objectFit: objectFit as any, width: '100%', height: '100%' }}
1351
- />
1352
- </div>
1353
- ))}
1354
- </div>
1355
- );
1356
-
865
+ }
866
+ break;
867
+
868
+ case 'GROUPIMAGE':
869
+ if (tab.tabContentGroupImage) {
870
+ // Prioritize showItems, fallback to dynamic/static arrays
871
+ const images = tab.tabContentGroupImage.showItems && tab.tabContentGroupImage.showItems.length > 0
872
+ ? tab.tabContentGroupImage.showItems
873
+ : (tab.tabContentGroupImage.type === 'DYNAMIC'
874
+ ? tab.tabContentGroupImage.dynamicImages
875
+ : (tab.tabContentGroupImage.staticImages || []));
876
+
877
+ if (!images || images.length === 0) {
878
+ return <div>No images available</div>;
1357
879
  }
1358
- break;
1359
880
 
1360
- case 'GROUPVIDEO':
1361
- if (tab.tabContentGroupVideo) {
1362
- // Prioritize showItems, fallback to dynamic/static arrays
1363
- const videos = tab.tabContentGroupVideo.showItems && tab.tabContentGroupVideo.showItems.length > 0
1364
- ? tab.tabContentGroupVideo.showItems
1365
- : (tab.tabContentGroupVideo.type === 'DYNAMIC'
1366
- ? tab.tabContentGroupVideo.dynamicVideos
1367
- : (tab.tabContentGroupVideo.staticVideos || []));
1368
-
1369
- if (!videos || videos.length === 0) {
1370
- return <div>No videos available</div>;
1371
- }
1372
-
1373
- // 1x1 layout: use scrollbar if scroll type is set, otherwise carousel
1374
- if (layout === '1x1' && scrollType) {
881
+ // 1x1 layout: use scrollbar if scroll type is set, otherwise carousel
882
+ if (layout === '1x1' && scrollType) {
1375
883
  // Use scrollable container with scrollbar - each item takes full width/height
1376
884
  const scrollDirection = scrollType === 'horizontal' ? 'row' : 'column';
1377
- const isDynamic = tab.tabContentGroupVideo.type === 'DYNAMIC';
1378
-
885
+
1379
886
  return (
1380
887
  <div
1381
888
  style={{
@@ -1390,7 +897,7 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
1390
897
  }}
1391
898
  className="scroll-container"
1392
899
  >
1393
- {videos.map((video, index) => (
900
+ {images.map((image, index) => (
1394
901
  <div
1395
902
  key={index}
1396
903
  className="media-box"
@@ -1403,722 +910,1176 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
1403
910
  scrollSnapAlign: 'start',
1404
911
  }}
1405
912
  >
1406
- {renderVideoPlayer(video, isDynamic, 'media-content', {
1407
- width: '100%',
1408
- height: '100%',
1409
- objectFit: objectFit as any
1410
- })}
913
+ <img
914
+ src={getImageUrl(
915
+ getImageItemUrl(image, tab.tabContentGroupImage.type)
916
+ )}
917
+ alt={
918
+ getImageItemAlt(image, tab.tabContentGroupImage.type)
919
+ }
920
+ className="media-content"
921
+ style={{ objectFit: objectFit as any, width: '100%', height: '100%' }}
922
+ />
1411
923
  </div>
1412
924
  ))}
1413
925
  </div>
1414
926
  );
1415
- }
1416
-
1417
- // 1x1 layout: carousel display (fallback when scroll is not set)
1418
- if (layout === '1x1') {
1419
- const currentVideo = videos[carouselIndex] || videos[0];
1420
- const isDynamic = tab.tabContentGroupVideo.type === 'DYNAMIC';
1421
-
1422
- const videoElement = currentVideo ? (
1423
- <div className="media-box" style={{ width: '100%', height: '100%' }}>
1424
- {renderVideoPlayer(currentVideo, isDynamic, 'media-content', {
1425
- width: '100%',
1426
- height: '100%',
1427
- objectFit: objectFit as any
1428
- })}
1429
- </div>
927
+ }
928
+
929
+ // 1x1 layout: carousel display (fallback when scroll is not set)
930
+ if (layout === '1x1') {
931
+ const currentImage = images[carouselIndex] || images[0];
932
+
933
+ const imageElement = currentImage ? (
934
+ <div className="media-box" style={{ width: '100%', height: '100%' }}>
935
+ <img
936
+ src={getImageUrl(
937
+ getImageItemUrl(currentImage, tab.tabContentGroupImage.type)
938
+ )}
939
+ alt={
940
+ getImageItemAlt(currentImage, tab.tabContentGroupImage.type)
941
+ }
942
+ className="media-content"
943
+ style={{ objectFit: objectFit as any, width: '100%', height: '100%' }}
944
+ />
945
+ </div>
1430
946
  ) : null;
1431
-
1432
- return (
1433
- <div
947
+
948
+ return (
949
+ <div
1434
950
  style={{
1435
- position: 'relative',
1436
- width: '100%',
1437
- height: '100%',
1438
- overflow: 'hidden',
1439
- display: 'flex',
1440
- alignItems: 'stretch',
1441
- justifyContent: 'center',
951
+ position: 'relative',
952
+ width: '100%',
953
+ height: '100%',
954
+ overflow: 'hidden',
955
+ display: 'flex',
956
+ alignItems: 'center',
957
+ justifyContent: 'center',
1442
958
  }}
1443
- >
1444
- {currentVideo && (
1445
- <div style={{ width: '100%', height: '100%', display: 'flex' }}>
1446
- {videoElement}
1447
- </div>
1448
- )}
1449
-
959
+ >
960
+ {imageElement}
961
+
1450
962
  {/* Carousel Navigation */}
1451
- {videos.length > 1 && (
1452
- <>
1453
- {/* Prev Button */}
1454
- <button
1455
- onClick={prevCarouselItem}
1456
- disabled={carouselIndex === 0}
1457
- style={{
1458
- position: 'absolute',
1459
- left: '10px',
1460
- top: '50%',
1461
- transform: 'translateY(-50%)',
1462
- background: 'rgba(0,0,0,0.5)',
1463
- color: 'white',
1464
- border: 'none',
1465
- borderRadius: '50%',
1466
- width: '40px',
1467
- height: '40px',
1468
- cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
1469
- opacity: carouselIndex === 0 ? 0.5 : 1,
1470
- display: 'flex',
1471
- alignItems: 'center',
1472
- justifyContent: 'center',
1473
- fontSize: '18px',
1474
- zIndex: 10,
1475
- }}
1476
- >
1477
-
1478
- </button>
1479
-
1480
- {/* Next Button */}
1481
- <button
1482
- onClick={nextCarouselItem}
1483
- disabled={carouselIndex >= videos.length - 1}
1484
- style={{
1485
- position: 'absolute',
1486
- right: '10px',
1487
- top: '50%',
1488
- transform: 'translateY(-50%)',
1489
- background: 'rgba(0,0,0,0.5)',
1490
- color: 'white',
1491
- border: 'none',
1492
- borderRadius: '50%',
1493
- width: '40px',
1494
- height: '40px',
1495
- cursor: carouselIndex >= videos.length - 1 ? 'not-allowed' : 'pointer',
1496
- opacity: carouselIndex >= videos.length - 1 ? 0.5 : 1,
1497
- display: 'flex',
1498
- alignItems: 'center',
1499
- justifyContent: 'center',
1500
- fontSize: '18px',
1501
- zIndex: 10,
1502
- }}
1503
- >
1504
-
1505
- </button>
1506
-
1507
- {/* Dots Indicator */}
1508
- <div
1509
- style={{
1510
- position: 'absolute',
1511
- bottom: '10px',
1512
- left: '50%',
1513
- transform: 'translateX(-50%)',
1514
- display: 'flex',
1515
- gap: '8px',
1516
- zIndex: 10,
1517
- }}
1518
- >
1519
- {videos.map((_, index) => (
963
+ {images.length > 1 && (
964
+ <>
965
+ {/* Previous Button */}
1520
966
  <button
1521
- key={index}
1522
- onClick={() => goToCarouselItem(index)}
1523
- style={{
1524
- width: index === carouselIndex ? '12px' : '8px',
1525
- height: index === carouselIndex ? '12px' : '8px',
1526
- borderRadius: '50%',
1527
- border: 'none',
1528
- background: index === carouselIndex ? 'white' : 'rgba(255,255,255,0.5)',
1529
- cursor: 'pointer',
1530
- transition: 'all 0.3s ease',
1531
- }}
1532
- />
1533
- ))}
1534
- </div>
1535
- </>
1536
- )}
1537
- </div>
1538
- );
1539
- }
1540
-
1541
- // Other layouts: grid display with limited videos
1542
- const getMaxVideos = () => {
1543
- switch (layout) {
1544
- case '1x2': return 2;
1545
- case '2x1': return 2;
1546
- case '2x2': return 4;
1547
- default: return videos.length;
1548
- }
1549
- };
1550
-
1551
- const isDynamic = tab.tabContentGroupVideo.type === 'DYNAMIC';
1552
-
1553
- // For grid layouts (1x2, 2x1, 2x2), show one grid per slide
1554
- // For 1x1 layout with scroll, show one item at a time
1555
- if (layout !== '1x1' && scrollType) {
1556
- // Group items into slides based on layout
1557
- const itemsPerSlide = layout === '2x2' ? 4 : 2; // 4 for 2x2, 2 for 1x2/2x1
1558
- const slides: any[][] = [];
1559
- for (let i = 0; i < videos.length; i += itemsPerSlide) {
1560
- slides.push(videos.slice(i, i + itemsPerSlide));
1561
- }
1562
-
1563
- const scrollDirection = scrollType === 'horizontal' ? 'row' : 'column';
1564
-
1565
- return (
1566
- <div
1567
- style={{
1568
- width: '100%',
1569
- height: '100%',
1570
- overflow: scrollType === 'horizontal' ? 'auto' : 'auto',
1571
- display: 'flex',
1572
- flexDirection: scrollDirection as any,
1573
- scrollSnapType: scrollType === 'horizontal' ? 'x mandatory' : 'y mandatory',
1574
- gap: 0,
1575
- padding: 0,
1576
- }}
1577
- className="scroll-container"
1578
- >
1579
- {slides.map((slideVideos, slideIndex) => (
1580
- <div
1581
- key={slideIndex}
1582
- style={{
1583
- width: scrollType === 'horizontal' ? '100%' : '100%',
1584
- height: scrollType === 'vertical' ? '100%' : '100%',
1585
- minWidth: scrollType === 'horizontal' ? '100%' : '100%',
1586
- minHeight: scrollType === 'vertical' ? '100%' : '100%',
1587
- flexShrink: 0,
1588
- scrollSnapAlign: 'start',
1589
- display: 'grid',
1590
- gridTemplateColumns: getGridColumns(),
1591
- gridTemplateRows: getGridRows(),
1592
- gap: '3px',
1593
- overflow: 'hidden',
1594
- alignContent: 'stretch',
1595
- boxSizing: 'border-box',
1596
- }}
1597
- >
1598
- {slideVideos.map((video, index) => (
1599
- <div
1600
- key={index}
1601
- className="media-box"
1602
- style={{
1603
- width: '100%',
1604
- height: '100%',
1605
- minHeight: 0,
1606
- overflow: 'hidden',
967
+ onClick={prevCarouselItem}
968
+ disabled={carouselIndex === 0}
969
+ style={{
970
+ position: 'absolute',
971
+ left: '10px',
972
+ top: '50%',
973
+ transform: 'translateY(-50%)',
974
+ background: 'rgba(0,0,0,0.5)',
975
+ color: 'white',
976
+ border: 'none',
977
+ borderRadius: '50%',
978
+ width: '40px',
979
+ height: '40px',
980
+ cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
981
+ opacity: carouselIndex === 0 ? 0.5 : 1,
1607
982
  display: 'flex',
1608
983
  alignItems: 'center',
1609
984
  justifyContent: 'center',
1610
- boxSizing: 'border-box',
985
+ fontSize: '18px',
986
+ zIndex: 10,
1611
987
  }}
1612
988
  >
1613
- {renderVideoPlayer(video, isDynamic, 'media-content', {
1614
- width: '100%',
1615
- height: '100%',
1616
- objectFit: objectFit as any
1617
- })}
989
+
990
+ </button>
991
+
992
+ {/* Next Button */}
993
+ <button
994
+ onClick={nextCarouselItem}
995
+ disabled={carouselIndex >= images.length - 1}
996
+ style={{
997
+ position: 'absolute',
998
+ right: '10px',
999
+ top: '50%',
1000
+ transform: 'translateY(-50%)',
1001
+ background: 'rgba(0,0,0,0.5)',
1002
+ color: 'white',
1003
+ border: 'none',
1004
+ borderRadius: '50%',
1005
+ width: '40px',
1006
+ height: '40px',
1007
+ cursor: carouselIndex >= images.length - 1 ? 'not-allowed' : 'pointer',
1008
+ opacity: carouselIndex >= images.length - 1 ? 0.5 : 1,
1009
+ display: 'flex',
1010
+ alignItems: 'center',
1011
+ justifyContent: 'center',
1012
+ fontSize: '18px',
1013
+ zIndex: 10,
1014
+ }}
1015
+ >
1016
+
1017
+ </button>
1018
+
1019
+ {/* Dots Indicator */}
1020
+ <div
1021
+ style={{
1022
+ position: 'absolute',
1023
+ bottom: '10px',
1024
+ left: '50%',
1025
+ transform: 'translateX(-50%)',
1026
+ display: 'flex',
1027
+ gap: '8px',
1028
+ zIndex: 10,
1029
+ }}
1030
+ >
1031
+ {images.map((_, index) => (
1032
+ <button
1033
+ key={index}
1034
+ onClick={() => goToCarouselItem(index)}
1035
+ style={{
1036
+ width: index === carouselIndex ? '12px' : '8px',
1037
+ height: index === carouselIndex ? '12px' : '8px',
1038
+ borderRadius: '50%',
1039
+ border: 'none',
1040
+ background: index === carouselIndex ? 'white' : 'rgba(255,255,255,0.5)',
1041
+ cursor: 'pointer',
1042
+ transition: 'all 0.3s ease',
1043
+ }}
1044
+ />
1045
+ ))}
1046
+ </div>
1047
+ </>
1048
+ )}
1049
+ </div>
1050
+ );
1051
+ }
1052
+
1053
+ // Other layouts: grid display with limited images
1054
+ const getMaxImages = () => {
1055
+ switch (layout) {
1056
+ case '1x2': return 2;
1057
+ case '2x1': return 2;
1058
+ case '2x2': return 4;
1059
+ default: return images.length;
1060
+ }
1061
+ };
1062
+
1063
+ // For grid layouts (1x2, 2x1, 2x2), show one grid per slide
1064
+ // For 1x1 layout with scroll, show one item at a time
1065
+ if (layout !== '1x1' && scrollType) {
1066
+ // Group items into slides based on layout
1067
+ const itemsPerSlide = layout === '2x2' ? 4 : 2; // 4 for 2x2, 2 for 1x2/2x1
1068
+ const slides: any[][] = [];
1069
+ for (let i = 0; i < images.length; i += itemsPerSlide) {
1070
+ slides.push(images.slice(i, i + itemsPerSlide));
1071
+ }
1072
+
1073
+ const scrollDirection = scrollType === 'horizontal' ? 'row' : 'column';
1074
+
1075
+ return (
1076
+ <div
1077
+ style={{
1078
+ width: '100%',
1079
+ height: '100%',
1080
+ overflow: scrollType === 'horizontal' ? 'auto' : 'auto',
1081
+ display: 'flex',
1082
+ flexDirection: scrollDirection as any,
1083
+ scrollSnapType: scrollType === 'horizontal' ? 'x mandatory' : 'y mandatory',
1084
+ gap: 0,
1085
+ padding: 0,
1086
+ }}
1087
+ className="scroll-container"
1088
+ >
1089
+ {slides.map((slideImages, slideIndex) => (
1090
+ <div
1091
+ key={slideIndex}
1092
+ style={{
1093
+ width: scrollType === 'horizontal' ? '100%' : '100%',
1094
+ height: scrollType === 'vertical' ? '100%' : '100%',
1095
+ minWidth: scrollType === 'horizontal' ? '100%' : '100%',
1096
+ minHeight: scrollType === 'vertical' ? '100%' : '100%',
1097
+ flexShrink: 0,
1098
+ scrollSnapAlign: 'start',
1099
+ display: 'grid',
1100
+ gridTemplateColumns: getGridColumns(),
1101
+ gridTemplateRows: getGridRows(),
1102
+ gap: '3px',
1103
+ overflow: 'hidden',
1104
+ boxSizing: 'border-box',
1105
+ }}
1106
+ >
1107
+ {slideImages.map((image, index) => (
1108
+ <div
1109
+ key={index}
1110
+ className="media-box"
1111
+ style={{
1112
+ width: '100%',
1113
+ height: '100%',
1114
+ minHeight: 0,
1115
+ overflow: 'hidden',
1116
+ display: 'flex',
1117
+ alignItems: 'center',
1118
+ justifyContent: 'center',
1119
+ boxSizing: 'border-box',
1120
+ }}
1121
+ >
1122
+ <img
1123
+ src={getImageUrl(
1124
+ tab.tabContentGroupImage.type === 'DYNAMIC'
1125
+ ? image.image?.url || image.url
1126
+ : image.attr?.all?.url || image.attr?.url || image.url
1127
+ )}
1128
+ alt={
1129
+ tab.tabContentGroupImage.type === 'DYNAMIC'
1130
+ ? image.image?.alt || image.alt || ''
1131
+ : image.attr?.all?.alt || image.attr?.alt || image.alt || ''
1132
+ }
1133
+ className="media-content"
1134
+ style={{ objectFit: objectFit as any, width: '100%', height: '100%' }}
1135
+ />
1136
+ </div>
1137
+ ))}
1138
+ </div>
1139
+ ))}
1140
+ </div>
1141
+ );
1142
+ }
1143
+
1144
+ // For 1x1 layout with scroll, show one item at a time (carousel-like)
1145
+ if (layout === '1x1' && scrollType) {
1146
+ const scrollDirection = scrollType === 'horizontal' ? 'row' : 'column';
1147
+ return (
1148
+ <div
1149
+ style={{
1150
+ width: '100%',
1151
+ height: '100%',
1152
+ overflow: scrollType === 'horizontal' ? 'auto' : 'auto',
1153
+ display: 'flex',
1154
+ flexDirection: scrollDirection as any,
1155
+ scrollSnapType: scrollType === 'horizontal' ? 'x mandatory' : 'y mandatory',
1156
+ gap: 0,
1157
+ padding: 0,
1158
+ }}
1159
+ className="scroll-container"
1160
+ >
1161
+ {images.map((image, index) => (
1162
+ <div
1163
+ key={index}
1164
+ className="media-box"
1165
+ style={{
1166
+ width: scrollType === 'horizontal' ? '100%' : '100%',
1167
+ height: scrollType === 'vertical' ? '100%' : '100%',
1168
+ minWidth: scrollType === 'horizontal' ? '100%' : '100%',
1169
+ minHeight: scrollType === 'vertical' ? '100%' : '100%',
1170
+ flexShrink: 0,
1171
+ scrollSnapAlign: 'start',
1172
+ }}
1173
+ >
1174
+ <img
1175
+ src={getImageUrl(
1176
+ getImageItemUrl(image, tab.tabContentGroupImage.type)
1177
+ )}
1178
+ alt={
1179
+ getImageItemAlt(image, tab.tabContentGroupImage.type)
1180
+ }
1181
+ className="media-content"
1182
+ style={{ objectFit: objectFit as any, width: '100%', height: '100%' }}
1183
+ />
1184
+ </div>
1185
+ ))}
1186
+ </div>
1187
+ );
1188
+ }
1189
+
1190
+ // Grid layout without scroll - show one grid per slide with carousel navigation
1191
+ if (layout !== '1x1') {
1192
+ // Group items into slides based on layout
1193
+ const itemsPerSlide = layout === '2x2' ? 4 : 2; // 4 for 2x2, 2 for 1x2/2x1
1194
+ const slides: any[][] = [];
1195
+ for (let i = 0; i < images.length; i += itemsPerSlide) {
1196
+ slides.push(images.slice(i, i + itemsPerSlide));
1197
+ }
1198
+
1199
+ const currentSlide = slides[carouselIndex] || slides[0] || [];
1200
+
1201
+ const groupImageContentStyle: React.CSSProperties = {
1202
+ display: 'grid',
1203
+ gridTemplateColumns: getGridColumns(),
1204
+ gridTemplateRows: getGridRows(),
1205
+ gap: '3px',
1206
+ height: '100%',
1207
+ width: '100%',
1208
+ overflow: 'hidden',
1209
+ alignContent: 'stretch',
1210
+ boxSizing: 'border-box',
1211
+ };
1212
+
1213
+ return (
1214
+ <div style={{ position: 'relative', width: '100%', height: '100%', overflow: 'hidden' }}>
1215
+ <div style={groupImageContentStyle}>
1216
+ {currentSlide.map((image, index) => (
1217
+ <div
1218
+ key={index}
1219
+ className="media-box"
1220
+ style={{
1221
+ width: '100%',
1222
+ height: '100%',
1223
+ minHeight: 0,
1224
+ minWidth: 0,
1225
+ overflow: 'hidden',
1226
+ display: 'flex',
1227
+ alignItems: 'center',
1228
+ justifyContent: 'center',
1229
+ boxSizing: 'border-box',
1230
+ }}
1231
+ >
1232
+ <img
1233
+ src={getImageUrl(
1234
+ tab.tabContentGroupImage.type === 'DYNAMIC'
1235
+ ? image.image?.url || image.url
1236
+ : image.attr?.all?.url || image.attr?.url || image.url
1237
+ )}
1238
+ alt={
1239
+ tab.tabContentGroupImage.type === 'DYNAMIC'
1240
+ ? image.image?.alt || image.alt || ''
1241
+ : image.attr?.all?.alt || image.attr?.alt || image.alt || ''
1242
+ }
1243
+ className="media-content"
1244
+ style={{ objectFit: objectFit as any, width: '100%', height: '100%' }}
1245
+ />
1246
+ </div>
1247
+ ))}
1248
+ </div>
1249
+
1250
+ {/* Carousel Navigation for grid slides */}
1251
+ {slides.length > 1 && (
1252
+ <>
1253
+ <button
1254
+ onClick={prevCarouselItem}
1255
+ disabled={carouselIndex === 0}
1256
+ style={{
1257
+ position: 'absolute',
1258
+ left: '10px',
1259
+ top: '50%',
1260
+ transform: 'translateY(-50%)',
1261
+ background: 'rgba(0,0,0,0.5)',
1262
+ color: 'white',
1263
+ border: 'none',
1264
+ borderRadius: '50%',
1265
+ width: '40px',
1266
+ height: '40px',
1267
+ cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
1268
+ opacity: carouselIndex === 0 ? 0.5 : 1,
1269
+ display: 'flex',
1270
+ alignItems: 'center',
1271
+ justifyContent: 'center',
1272
+ fontSize: '18px',
1273
+ zIndex: 10,
1274
+ }}
1275
+ >
1276
+
1277
+ </button>
1278
+ <button
1279
+ onClick={nextCarouselItem}
1280
+ disabled={carouselIndex >= slides.length - 1}
1281
+ style={{
1282
+ position: 'absolute',
1283
+ right: '10px',
1284
+ top: '50%',
1285
+ transform: 'translateY(-50%)',
1286
+ background: 'rgba(0,0,0,0.5)',
1287
+ color: 'white',
1288
+ border: 'none',
1289
+ borderRadius: '50%',
1290
+ width: '40px',
1291
+ height: '40px',
1292
+ cursor: carouselIndex >= slides.length - 1 ? 'not-allowed' : 'pointer',
1293
+ opacity: carouselIndex >= slides.length - 1 ? 0.5 : 1,
1294
+ display: 'flex',
1295
+ alignItems: 'center',
1296
+ justifyContent: 'center',
1297
+ fontSize: '18px',
1298
+ zIndex: 10,
1299
+ }}
1300
+ >
1301
+
1302
+ </button>
1303
+ <div
1304
+ style={{
1305
+ position: 'absolute',
1306
+ bottom: '10px',
1307
+ left: '50%',
1308
+ transform: 'translateX(-50%)',
1309
+ display: 'flex',
1310
+ gap: '8px',
1311
+ zIndex: 10,
1312
+ }}
1313
+ >
1314
+ {slides.map((_, index) => (
1315
+ <button
1316
+ key={index}
1317
+ onClick={() => goToCarouselItem(index)}
1318
+ style={{
1319
+ width: index === carouselIndex ? '12px' : '8px',
1320
+ height: index === carouselIndex ? '12px' : '8px',
1321
+ borderRadius: '50%',
1322
+ border: 'none',
1323
+ background: index === carouselIndex ? 'white' : 'rgba(255,255,255,0.5)',
1324
+ cursor: 'pointer',
1325
+ transition: 'all 0.3s ease',
1326
+ }}
1327
+ />
1328
+ ))}
1329
+ </div>
1330
+ </>
1331
+ )}
1332
+ </div>
1333
+ );
1334
+ }
1335
+
1336
+ // Fallback for 1x1 or other cases
1337
+ const maxImages = getMaxImages();
1338
+ const displayImages = images.slice(0, maxImages);
1339
+
1340
+ const groupImageContentStyle = {
1341
+ display: 'grid',
1342
+ gridTemplateColumns: getGridColumns(),
1343
+ gridTemplateRows: getGridRows(),
1344
+ gap: '3px',
1345
+ height: '100%',
1346
+ width: '100%',
1347
+ overflow: 'hidden'
1348
+ };
1349
+
1350
+ return (
1351
+ <div style={groupImageContentStyle}>
1352
+ {displayImages.map((image, index) => (
1353
+ <div key={index} className="media-box" style={{ width: '100%', height: '100%', minHeight: 0, minWidth: 0 }}>
1354
+ <img
1355
+ src={getImageUrl(
1356
+ getImageItemUrl(image, tab.tabContentGroupImage.type)
1357
+ )}
1358
+ alt={
1359
+ getImageItemAlt(image, tab.tabContentGroupImage.type)
1360
+ }
1361
+ className="media-content"
1362
+ style={{ objectFit: objectFit as any, width: '100%', height: '100%' }}
1363
+ />
1364
+ </div>
1365
+ ))}
1366
+ </div>
1367
+ );
1368
+
1369
+ }
1370
+ break;
1371
+
1372
+ case 'GROUPVIDEO':
1373
+ if (tab.tabContentGroupVideo) {
1374
+ // Prioritize showItems, fallback to dynamic/static arrays
1375
+ const videos = tab.tabContentGroupVideo.showItems && tab.tabContentGroupVideo.showItems.length > 0
1376
+ ? tab.tabContentGroupVideo.showItems
1377
+ : (tab.tabContentGroupVideo.type === 'DYNAMIC'
1378
+ ? tab.tabContentGroupVideo.dynamicVideos
1379
+ : (tab.tabContentGroupVideo.staticVideos || []));
1380
+
1381
+ if (!videos || videos.length === 0) {
1382
+ return <div>No videos available</div>;
1383
+ }
1384
+
1385
+ // 1x1 layout: use scrollbar if scroll type is set, otherwise carousel
1386
+ if (layout === '1x1' && scrollType) {
1387
+ // Use scrollable container with scrollbar - each item takes full width/height
1388
+ const scrollDirection = scrollType === 'horizontal' ? 'row' : 'column';
1389
+ const isDynamic = tab.tabContentGroupVideo.type === 'DYNAMIC';
1390
+
1391
+ return (
1392
+ <div
1393
+ style={{
1394
+ width: '100%',
1395
+ height: '100%',
1396
+ overflow: scrollType === 'horizontal' ? 'auto' : 'auto',
1397
+ display: 'flex',
1398
+ flexDirection: scrollDirection as any,
1399
+ scrollSnapType: scrollType === 'horizontal' ? 'x mandatory' : 'y mandatory',
1400
+ gap: 0,
1401
+ padding: 0,
1402
+ }}
1403
+ className="scroll-container"
1404
+ >
1405
+ {videos.map((video, index) => (
1406
+ <div
1407
+ key={index}
1408
+ className="media-box"
1409
+ style={{
1410
+ width: scrollType === 'horizontal' ? '100%' : '100%',
1411
+ height: scrollType === 'vertical' ? '100%' : '100%',
1412
+ minWidth: scrollType === 'horizontal' ? '100%' : '100%',
1413
+ minHeight: scrollType === 'vertical' ? '100%' : '100%',
1414
+ flexShrink: 0,
1415
+ scrollSnapAlign: 'start',
1416
+ }}
1417
+ >
1418
+ {renderVideoPlayer(video, isDynamic, 'media-content', {
1419
+ width: '100%',
1420
+ height: '100%',
1421
+ objectFit: objectFit as any
1422
+ })}
1423
+ </div>
1424
+ ))}
1425
+ </div>
1426
+ );
1427
+ }
1428
+
1429
+ // 1x1 layout: carousel display (fallback when scroll is not set)
1430
+ if (layout === '1x1') {
1431
+ const currentVideo = videos[carouselIndex] || videos[0];
1432
+ const isDynamic = tab.tabContentGroupVideo.type === 'DYNAMIC';
1433
+
1434
+ const videoElement = currentVideo ? (
1435
+ <div className="media-box" style={{ width: '100%', height: '100%' }}>
1436
+ {renderVideoPlayer(currentVideo, isDynamic, 'media-content', {
1437
+ width: '100%',
1438
+ height: '100%',
1439
+ objectFit: objectFit as any
1440
+ })}
1441
+ </div>
1442
+ ) : null;
1443
+
1444
+ return (
1445
+ <div
1446
+ style={{
1447
+ position: 'relative',
1448
+ width: '100%',
1449
+ height: '100%',
1450
+ overflow: 'hidden',
1451
+ display: 'flex',
1452
+ alignItems: 'stretch',
1453
+ justifyContent: 'center',
1454
+ }}
1455
+ >
1456
+ {currentVideo && (
1457
+ <div style={{ width: '100%', height: '100%', display: 'flex' }}>
1458
+ {videoElement}
1459
+ </div>
1460
+ )}
1461
+
1462
+ {/* Carousel Navigation */}
1463
+ {videos.length > 1 && (
1464
+ <>
1465
+ {/* Prev Button */}
1466
+ <button
1467
+ onClick={prevCarouselItem}
1468
+ disabled={carouselIndex === 0}
1469
+ style={{
1470
+ position: 'absolute',
1471
+ left: '10px',
1472
+ top: '50%',
1473
+ transform: 'translateY(-50%)',
1474
+ background: 'rgba(0,0,0,0.5)',
1475
+ color: 'white',
1476
+ border: 'none',
1477
+ borderRadius: '50%',
1478
+ width: '40px',
1479
+ height: '40px',
1480
+ cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
1481
+ opacity: carouselIndex === 0 ? 0.5 : 1,
1482
+ display: 'flex',
1483
+ alignItems: 'center',
1484
+ justifyContent: 'center',
1485
+ fontSize: '18px',
1486
+ zIndex: 10,
1487
+ }}
1488
+ >
1489
+
1490
+ </button>
1491
+
1492
+ {/* Next Button */}
1493
+ <button
1494
+ onClick={nextCarouselItem}
1495
+ disabled={carouselIndex >= videos.length - 1}
1496
+ style={{
1497
+ position: 'absolute',
1498
+ right: '10px',
1499
+ top: '50%',
1500
+ transform: 'translateY(-50%)',
1501
+ background: 'rgba(0,0,0,0.5)',
1502
+ color: 'white',
1503
+ border: 'none',
1504
+ borderRadius: '50%',
1505
+ width: '40px',
1506
+ height: '40px',
1507
+ cursor: carouselIndex >= videos.length - 1 ? 'not-allowed' : 'pointer',
1508
+ opacity: carouselIndex >= videos.length - 1 ? 0.5 : 1,
1509
+ display: 'flex',
1510
+ alignItems: 'center',
1511
+ justifyContent: 'center',
1512
+ fontSize: '18px',
1513
+ zIndex: 10,
1514
+ }}
1515
+ >
1516
+
1517
+ </button>
1518
+
1519
+ {/* Dots Indicator */}
1520
+ <div
1521
+ style={{
1522
+ position: 'absolute',
1523
+ bottom: '10px',
1524
+ left: '50%',
1525
+ transform: 'translateX(-50%)',
1526
+ display: 'flex',
1527
+ gap: '8px',
1528
+ zIndex: 10,
1529
+ }}
1530
+ >
1531
+ {videos.map((_, index) => (
1532
+ <button
1533
+ key={index}
1534
+ onClick={() => goToCarouselItem(index)}
1535
+ style={{
1536
+ width: index === carouselIndex ? '12px' : '8px',
1537
+ height: index === carouselIndex ? '12px' : '8px',
1538
+ borderRadius: '50%',
1539
+ border: 'none',
1540
+ background: index === carouselIndex ? 'white' : 'rgba(255,255,255,0.5)',
1541
+ cursor: 'pointer',
1542
+ transition: 'all 0.3s ease',
1543
+ }}
1544
+ />
1545
+ ))}
1618
1546
  </div>
1619
- ))}
1620
- </div>
1621
- ))}
1622
- </div>
1623
- );
1624
- }
1625
-
1626
- // For 1x1 layout with scroll, show one item at a time (carousel-like)
1627
- if (layout === '1x1' && scrollType) {
1628
- const scrollDirection = scrollType === 'horizontal' ? 'row' : 'column';
1629
- return (
1630
- <div
1631
- style={{
1632
- width: '100%',
1633
- height: '100%',
1634
- overflow: scrollType === 'horizontal' ? 'auto' : 'auto',
1635
- display: 'flex',
1636
- flexDirection: scrollDirection as any,
1637
- scrollSnapType: scrollType === 'horizontal' ? 'x mandatory' : 'y mandatory',
1638
- gap: 0,
1639
- padding: 0,
1640
- }}
1641
- className="scroll-container"
1642
- >
1643
- {videos.map((video, index) => (
1644
- <div
1645
- key={index}
1646
- className="media-box"
1647
- style={{
1648
- width: scrollType === 'horizontal' ? '100%' : '100%',
1649
- height: scrollType === 'vertical' ? '100%' : '100%',
1650
- minWidth: scrollType === 'horizontal' ? '100%' : '100%',
1651
- minHeight: scrollType === 'vertical' ? '100%' : '100%',
1652
- flexShrink: 0,
1653
- scrollSnapAlign: 'start',
1654
- }}
1655
- >
1656
- {renderVideoPlayer(video, isDynamic, 'media-content', {
1657
- width: '100%',
1658
- height: '100%',
1659
- objectFit: objectFit as any
1660
- })}
1661
- </div>
1662
- ))}
1663
- </div>
1664
- );
1665
- }
1666
-
1667
- // Grid layout without scroll - show one grid per slide with carousel navigation
1668
- if (layout !== '1x1') {
1669
- // Group items into slides based on layout
1670
- const itemsPerSlide = layout === '2x2' ? 4 : 2; // 4 for 2x2, 2 for 1x2/2x1
1671
- const slides: any[][] = [];
1672
- for (let i = 0; i < videos.length; i += itemsPerSlide) {
1673
- slides.push(videos.slice(i, i + itemsPerSlide));
1547
+ </>
1548
+ )}
1549
+ </div>
1550
+ );
1674
1551
  }
1675
-
1676
- const currentSlide = slides[carouselIndex] || slides[0] || [];
1677
-
1678
- const groupVideoContentStyle: React.CSSProperties = {
1679
- display: 'grid',
1680
- gridTemplateColumns: getGridColumns(),
1681
- gridTemplateRows: getGridRows(),
1682
- gap: '3px',
1683
- height: '100%',
1684
- width: '100%',
1685
- overflow: 'hidden',
1686
- alignContent: 'stretch',
1687
- boxSizing: 'border-box',
1552
+
1553
+ // Other layouts: grid display with limited videos
1554
+ const getMaxVideos = () => {
1555
+ switch (layout) {
1556
+ case '1x2': return 2;
1557
+ case '2x1': return 2;
1558
+ case '2x2': return 4;
1559
+ default: return videos.length;
1560
+ }
1688
1561
  };
1689
-
1690
- return (
1691
- <div style={{ position: 'relative', width: '100%', height: '100%', overflow: 'hidden' }}>
1692
- <div style={groupVideoContentStyle}>
1693
- {currentSlide.map((video, index) => (
1694
- <div
1695
- key={index}
1696
- className="media-box"
1697
- style={{
1698
- width: '100%',
1699
- height: '100%',
1700
- minHeight: 0,
1701
- minWidth: 0,
1562
+
1563
+ const isDynamic = tab.tabContentGroupVideo.type === 'DYNAMIC';
1564
+
1565
+ // For grid layouts (1x2, 2x1, 2x2), show one grid per slide
1566
+ // For 1x1 layout with scroll, show one item at a time
1567
+ if (layout !== '1x1' && scrollType) {
1568
+ // Group items into slides based on layout
1569
+ const itemsPerSlide = layout === '2x2' ? 4 : 2; // 4 for 2x2, 2 for 1x2/2x1
1570
+ const slides: any[][] = [];
1571
+ for (let i = 0; i < videos.length; i += itemsPerSlide) {
1572
+ slides.push(videos.slice(i, i + itemsPerSlide));
1573
+ }
1574
+
1575
+ const scrollDirection = scrollType === 'horizontal' ? 'row' : 'column';
1576
+
1577
+ return (
1578
+ <div
1579
+ style={{
1580
+ width: '100%',
1581
+ height: '100%',
1582
+ overflow: scrollType === 'horizontal' ? 'auto' : 'auto',
1583
+ display: 'flex',
1584
+ flexDirection: scrollDirection as any,
1585
+ scrollSnapType: scrollType === 'horizontal' ? 'x mandatory' : 'y mandatory',
1586
+ gap: 0,
1587
+ padding: 0,
1588
+ }}
1589
+ className="scroll-container"
1590
+ >
1591
+ {slides.map((slideVideos, slideIndex) => (
1592
+ <div
1593
+ key={slideIndex}
1594
+ style={{
1595
+ width: scrollType === 'horizontal' ? '100%' : '100%',
1596
+ height: scrollType === 'vertical' ? '100%' : '100%',
1597
+ minWidth: scrollType === 'horizontal' ? '100%' : '100%',
1598
+ minHeight: scrollType === 'vertical' ? '100%' : '100%',
1599
+ flexShrink: 0,
1600
+ scrollSnapAlign: 'start',
1601
+ display: 'grid',
1602
+ gridTemplateColumns: getGridColumns(),
1603
+ gridTemplateRows: getGridRows(),
1604
+ gap: '3px',
1702
1605
  overflow: 'hidden',
1703
- display: 'flex',
1704
- alignItems: 'center',
1705
- justifyContent: 'center',
1606
+ alignContent: 'stretch',
1706
1607
  boxSizing: 'border-box',
1707
1608
  }}
1708
1609
  >
1709
- {renderVideoPlayer(video, isDynamic, 'media-content', {
1710
- width: '100%',
1711
- height: '100%',
1712
- objectFit: objectFit as any
1713
- })}
1610
+ {slideVideos.map((video, index) => (
1611
+ <div
1612
+ key={index}
1613
+ className="media-box"
1614
+ style={{
1615
+ width: '100%',
1616
+ height: '100%',
1617
+ minHeight: 0,
1618
+ overflow: 'hidden',
1619
+ display: 'flex',
1620
+ alignItems: 'center',
1621
+ justifyContent: 'center',
1622
+ boxSizing: 'border-box',
1623
+ }}
1624
+ >
1625
+ {renderVideoPlayer(video, isDynamic, 'media-content', {
1626
+ width: '100%',
1627
+ height: '100%',
1628
+ objectFit: objectFit as any
1629
+ })}
1630
+ </div>
1631
+ ))}
1714
1632
  </div>
1715
1633
  ))}
1716
1634
  </div>
1717
-
1718
- {/* Carousel Navigation for grid slides */}
1719
- {slides.length > 1 && (
1720
- <>
1721
- <button
1722
- onClick={prevCarouselItem}
1723
- disabled={carouselIndex === 0}
1724
- style={{
1725
- position: 'absolute',
1726
- left: '10px',
1727
- top: '50%',
1728
- transform: 'translateY(-50%)',
1729
- background: 'rgba(0,0,0,0.5)',
1730
- color: 'white',
1731
- border: 'none',
1732
- borderRadius: '50%',
1733
- width: '40px',
1734
- height: '40px',
1735
- cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
1736
- opacity: carouselIndex === 0 ? 0.5 : 1,
1737
- display: 'flex',
1738
- alignItems: 'center',
1739
- justifyContent: 'center',
1740
- fontSize: '18px',
1741
- zIndex: 10,
1742
- }}
1743
- >
1744
-
1745
- </button>
1746
- <button
1747
- onClick={nextCarouselItem}
1748
- disabled={carouselIndex >= slides.length - 1}
1749
- style={{
1750
- position: 'absolute',
1751
- right: '10px',
1752
- top: '50%',
1753
- transform: 'translateY(-50%)',
1754
- background: 'rgba(0,0,0,0.5)',
1755
- color: 'white',
1756
- border: 'none',
1757
- borderRadius: '50%',
1758
- width: '40px',
1759
- height: '40px',
1760
- cursor: carouselIndex >= slides.length - 1 ? 'not-allowed' : 'pointer',
1761
- opacity: carouselIndex >= slides.length - 1 ? 0.5 : 1,
1762
- display: 'flex',
1763
- alignItems: 'center',
1764
- justifyContent: 'center',
1765
- fontSize: '18px',
1766
- zIndex: 10,
1767
- }}
1768
- >
1769
-
1770
- </button>
1635
+ );
1636
+ }
1637
+
1638
+ // For 1x1 layout with scroll, show one item at a time (carousel-like)
1639
+ if (layout === '1x1' && scrollType) {
1640
+ const scrollDirection = scrollType === 'horizontal' ? 'row' : 'column';
1641
+ return (
1642
+ <div
1643
+ style={{
1644
+ width: '100%',
1645
+ height: '100%',
1646
+ overflow: scrollType === 'horizontal' ? 'auto' : 'auto',
1647
+ display: 'flex',
1648
+ flexDirection: scrollDirection as any,
1649
+ scrollSnapType: scrollType === 'horizontal' ? 'x mandatory' : 'y mandatory',
1650
+ gap: 0,
1651
+ padding: 0,
1652
+ }}
1653
+ className="scroll-container"
1654
+ >
1655
+ {videos.map((video, index) => (
1771
1656
  <div
1657
+ key={index}
1658
+ className="media-box"
1772
1659
  style={{
1773
- position: 'absolute',
1774
- bottom: '10px',
1775
- left: '50%',
1776
- transform: 'translateX(-50%)',
1777
- display: 'flex',
1778
- gap: '8px',
1779
- zIndex: 10,
1660
+ width: scrollType === 'horizontal' ? '100%' : '100%',
1661
+ height: scrollType === 'vertical' ? '100%' : '100%',
1662
+ minWidth: scrollType === 'horizontal' ? '100%' : '100%',
1663
+ minHeight: scrollType === 'vertical' ? '100%' : '100%',
1664
+ flexShrink: 0,
1665
+ scrollSnapAlign: 'start',
1780
1666
  }}
1781
1667
  >
1782
- {slides.map((_, index) => (
1783
- <button
1784
- key={index}
1785
- onClick={() => goToCarouselItem(index)}
1786
- style={{
1787
- width: index === carouselIndex ? '12px' : '8px',
1788
- height: index === carouselIndex ? '12px' : '8px',
1789
- borderRadius: '50%',
1790
- border: 'none',
1791
- background: index === carouselIndex ? 'white' : 'rgba(255,255,255,0.5)',
1792
- cursor: 'pointer',
1793
- transition: 'all 0.3s ease',
1794
- }}
1795
- />
1796
- ))}
1668
+ {renderVideoPlayer(video, isDynamic, 'media-content', {
1669
+ width: '100%',
1670
+ height: '100%',
1671
+ objectFit: objectFit as any
1672
+ })}
1797
1673
  </div>
1798
- </>
1799
- )}
1800
- </div>
1801
- );
1802
- }
1803
-
1804
- // Fallback for 1x1 or other cases
1805
- const maxVideos = getMaxVideos();
1806
- const displayVideos = videos.slice(0, maxVideos);
1807
-
1808
- const groupVideoContentStyle = {
1809
- display: 'grid',
1810
- gridTemplateColumns: getGridColumns(),
1811
- gridTemplateRows: getGridRows(),
1812
- gap: '3px',
1813
- height: '100%',
1814
- width: '100%',
1815
- overflow: 'hidden'
1816
- };
1817
-
1818
- return (
1819
- <div style={groupVideoContentStyle}>
1820
- {displayVideos.map((video, index) => (
1821
- <div
1822
- key={index}
1823
- className="media-box"
1824
- style={{
1825
- width: '100%',
1826
- height: '100%',
1827
- minHeight: 0,
1828
- minWidth: 0,
1829
- display: 'flex',
1830
- alignItems: 'stretch'
1831
- }}
1832
- >
1833
- {renderVideoPlayer(video, isDynamic, 'media-content', {
1834
- width: '100%',
1835
- height: '100%',
1836
- objectFit: objectFit as any
1837
- })}
1838
- </div>
1839
- ))}
1840
- </div>
1841
- );
1842
-
1674
+ ))}
1675
+ </div>
1676
+ );
1843
1677
  }
1844
- break;
1845
-
1846
- case 'GROUPPRODUCT':
1847
- if (tab.tabContentGroupProduct) {
1848
- // Prioritize showItems, fallback to dynamic/static arrays
1849
- const products = tab.tabContentGroupProduct.showItems && tab.tabContentGroupProduct.showItems.length > 0
1850
- ? tab.tabContentGroupProduct.showItems
1851
- : (tab.tabContentGroupProduct.type === 'DYNAMIC'
1852
- ? tab.tabContentGroupProduct.dynamic?.list
1853
- : tab.tabContentGroupProduct.staticProducts);
1854
-
1855
- if (!products || products.length === 0) {
1856
- return <div>No products available</div>;
1857
- }
1858
-
1859
- // 1x1 layout: use scrollbar if scroll type is set, otherwise carousel
1860
- if (layout === '1x1' && scrollType) {
1861
- // Use scrollable container with scrollbar - each item takes full width/height
1862
- const scrollDirection = scrollType === 'horizontal' ? 'row' : 'column';
1863
-
1864
- return (
1865
- <div
1866
- style={{
1867
- width: '100%',
1868
- height: '100%',
1869
- overflow: scrollType === 'horizontal' ? 'auto' : 'auto',
1870
- display: 'flex',
1871
- flexDirection: scrollDirection as any,
1872
- scrollSnapType: scrollType === 'horizontal' ? 'x mandatory' : 'y mandatory',
1873
- gap: 0,
1874
- padding: 0,
1875
- }}
1876
- className="scroll-container"
1877
- >
1878
- {products.map((product: any, index: number) => (
1678
+
1679
+ // Grid layout without scroll - show one grid per slide with carousel navigation
1680
+ if (layout !== '1x1') {
1681
+ // Group items into slides based on layout
1682
+ const itemsPerSlide = layout === '2x2' ? 4 : 2; // 4 for 2x2, 2 for 1x2/2x1
1683
+ const slides: any[][] = [];
1684
+ for (let i = 0; i < videos.length; i += itemsPerSlide) {
1685
+ slides.push(videos.slice(i, i + itemsPerSlide));
1686
+ }
1687
+
1688
+ const currentSlide = slides[carouselIndex] || slides[0] || [];
1689
+
1690
+ const groupVideoContentStyle: React.CSSProperties = {
1691
+ display: 'grid',
1692
+ gridTemplateColumns: getGridColumns(),
1693
+ gridTemplateRows: getGridRows(),
1694
+ gap: '3px',
1695
+ height: '100%',
1696
+ width: '100%',
1697
+ overflow: 'hidden',
1698
+ alignContent: 'stretch',
1699
+ boxSizing: 'border-box',
1700
+ };
1701
+
1702
+ return (
1703
+ <div style={{ position: 'relative', width: '100%', height: '100%', overflow: 'hidden' }}>
1704
+ <div style={groupVideoContentStyle}>
1705
+ {currentSlide.map((video, index) => (
1879
1706
  <div
1880
1707
  key={index}
1708
+ className="media-box"
1881
1709
  style={{
1882
- width: scrollType === 'horizontal' ? '100%' : '100%',
1883
- height: scrollType === 'vertical' ? '100%' : '100%',
1884
- minWidth: scrollType === 'horizontal' ? '100%' : '100%',
1885
- minHeight: scrollType === 'vertical' ? '100%' : '100%',
1886
- flexShrink: 0,
1887
- scrollSnapAlign: 'start',
1710
+ width: '100%',
1711
+ height: '100%',
1712
+ minHeight: 0,
1713
+ minWidth: 0,
1714
+ overflow: 'hidden',
1715
+ display: 'flex',
1716
+ alignItems: 'center',
1717
+ justifyContent: 'center',
1718
+ boxSizing: 'border-box',
1888
1719
  }}
1889
1720
  >
1890
- <ProductCard product={product} layout={layout} />
1721
+ {renderVideoPlayer(video, isDynamic, 'media-content', {
1722
+ width: '100%',
1723
+ height: '100%',
1724
+ objectFit: objectFit as any
1725
+ })}
1891
1726
  </div>
1892
1727
  ))}
1893
1728
  </div>
1894
- );
1895
- }
1896
-
1897
- // 1x1 layout: Carousel view (fallback when scroll is not set)
1898
- if (layout === '1x1') {
1899
- const currentProduct = products[carouselIndex] || products[0];
1900
-
1901
- return (
1902
- <div
1729
+
1730
+ {/* Carousel Navigation for grid slides */}
1731
+ {slides.length > 1 && (
1732
+ <>
1733
+ <button
1734
+ onClick={prevCarouselItem}
1735
+ disabled={carouselIndex === 0}
1736
+ style={{
1737
+ position: 'absolute',
1738
+ left: '10px',
1739
+ top: '50%',
1740
+ transform: 'translateY(-50%)',
1741
+ background: 'rgba(0,0,0,0.5)',
1742
+ color: 'white',
1743
+ border: 'none',
1744
+ borderRadius: '50%',
1745
+ width: '40px',
1746
+ height: '40px',
1747
+ cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
1748
+ opacity: carouselIndex === 0 ? 0.5 : 1,
1749
+ display: 'flex',
1750
+ alignItems: 'center',
1751
+ justifyContent: 'center',
1752
+ fontSize: '18px',
1753
+ zIndex: 10,
1754
+ }}
1755
+ >
1756
+
1757
+ </button>
1758
+ <button
1759
+ onClick={nextCarouselItem}
1760
+ disabled={carouselIndex >= slides.length - 1}
1761
+ style={{
1762
+ position: 'absolute',
1763
+ right: '10px',
1764
+ top: '50%',
1765
+ transform: 'translateY(-50%)',
1766
+ background: 'rgba(0,0,0,0.5)',
1767
+ color: 'white',
1768
+ border: 'none',
1769
+ borderRadius: '50%',
1770
+ width: '40px',
1771
+ height: '40px',
1772
+ cursor: carouselIndex >= slides.length - 1 ? 'not-allowed' : 'pointer',
1773
+ opacity: carouselIndex >= slides.length - 1 ? 0.5 : 1,
1774
+ display: 'flex',
1775
+ alignItems: 'center',
1776
+ justifyContent: 'center',
1777
+ fontSize: '18px',
1778
+ zIndex: 10,
1779
+ }}
1780
+ >
1781
+
1782
+ </button>
1783
+ <div
1784
+ style={{
1785
+ position: 'absolute',
1786
+ bottom: '10px',
1787
+ left: '50%',
1788
+ transform: 'translateX(-50%)',
1789
+ display: 'flex',
1790
+ gap: '8px',
1791
+ zIndex: 10,
1792
+ }}
1793
+ >
1794
+ {slides.map((_, index) => (
1795
+ <button
1796
+ key={index}
1797
+ onClick={() => goToCarouselItem(index)}
1798
+ style={{
1799
+ width: index === carouselIndex ? '12px' : '8px',
1800
+ height: index === carouselIndex ? '12px' : '8px',
1801
+ borderRadius: '50%',
1802
+ border: 'none',
1803
+ background: index === carouselIndex ? 'white' : 'rgba(255,255,255,0.5)',
1804
+ cursor: 'pointer',
1805
+ transition: 'all 0.3s ease',
1806
+ }}
1807
+ />
1808
+ ))}
1809
+ </div>
1810
+ </>
1811
+ )}
1812
+ </div>
1813
+ );
1814
+ }
1815
+
1816
+ // Fallback for 1x1 or other cases
1817
+ const maxVideos = getMaxVideos();
1818
+ const displayVideos = videos.slice(0, maxVideos);
1819
+
1820
+ const groupVideoContentStyle = {
1821
+ display: 'grid',
1822
+ gridTemplateColumns: getGridColumns(),
1823
+ gridTemplateRows: getGridRows(),
1824
+ gap: '3px',
1825
+ height: '100%',
1826
+ width: '100%',
1827
+ overflow: 'hidden'
1828
+ };
1829
+
1830
+ return (
1831
+ <div style={groupVideoContentStyle}>
1832
+ {displayVideos.map((video, index) => (
1833
+ <div
1834
+ key={index}
1835
+ className="media-box"
1836
+ style={{
1837
+ width: '100%',
1838
+ height: '100%',
1839
+ minHeight: 0,
1840
+ minWidth: 0,
1841
+ display: 'flex',
1842
+ alignItems: 'stretch'
1843
+ }}
1844
+ >
1845
+ {renderVideoPlayer(video, isDynamic, 'media-content', {
1846
+ width: '100%',
1847
+ height: '100%',
1848
+ objectFit: objectFit as any
1849
+ })}
1850
+ </div>
1851
+ ))}
1852
+ </div>
1853
+ );
1854
+
1855
+ }
1856
+ break;
1857
+
1858
+ case 'GROUPPRODUCT':
1859
+ if (tab.tabContentGroupProduct) {
1860
+ // Prioritize showItems, fallback to dynamic/static arrays
1861
+ const products = tab.tabContentGroupProduct.showItems && tab.tabContentGroupProduct.showItems.length > 0
1862
+ ? tab.tabContentGroupProduct.showItems
1863
+ : (tab.tabContentGroupProduct.type === 'DYNAMIC'
1864
+ ? tab.tabContentGroupProduct.dynamic?.list
1865
+ : tab.tabContentGroupProduct.staticProducts);
1866
+
1867
+ if (!products || products.length === 0) {
1868
+ return <div>No products available</div>;
1869
+ }
1870
+
1871
+ // 1x1 layout: use scrollbar if scroll type is set, otherwise carousel
1872
+ if (layout === '1x1' && scrollType) {
1873
+ // Use scrollable container with scrollbar - each item takes full width/height
1874
+ const scrollDirection = scrollType === 'horizontal' ? 'row' : 'column';
1875
+
1876
+ return (
1877
+ <div
1878
+ style={{
1879
+ width: '100%',
1880
+ height: '100%',
1881
+ overflow: scrollType === 'horizontal' ? 'auto' : 'auto',
1882
+ display: 'flex',
1883
+ flexDirection: scrollDirection as any,
1884
+ scrollSnapType: scrollType === 'horizontal' ? 'x mandatory' : 'y mandatory',
1885
+ gap: 0,
1886
+ padding: 0,
1887
+ }}
1888
+ className="scroll-container"
1889
+ >
1890
+ {products.map((product: any, index: number) => (
1891
+ <div
1892
+ key={index}
1893
+ style={{
1894
+ width: scrollType === 'horizontal' ? '100%' : '100%',
1895
+ height: scrollType === 'vertical' ? '100%' : '100%',
1896
+ minWidth: scrollType === 'horizontal' ? '100%' : '100%',
1897
+ minHeight: scrollType === 'vertical' ? '100%' : '100%',
1898
+ flexShrink: 0,
1899
+ scrollSnapAlign: 'start',
1900
+ }}
1901
+ >
1902
+ <ProductCard product={product} layout={layout} />
1903
+ </div>
1904
+ ))}
1905
+ </div>
1906
+ );
1907
+ }
1908
+
1909
+ // 1x1 layout: Carousel view (fallback when scroll is not set)
1910
+ if (layout === '1x1') {
1911
+ const currentProduct = products[carouselIndex] || products[0];
1912
+
1913
+ return (
1914
+ <div
1903
1915
  style={{
1904
- position: 'relative',
1905
- width: '100%',
1906
- height: '100%', // Full height of tab content
1907
- overflow: 'hidden',
1908
- display: 'flex',
1909
- alignItems: 'stretch',
1910
- justifyContent: 'center',
1916
+ position: 'relative',
1917
+ width: '100%',
1918
+ height: '100%', // Full height of tab content
1919
+ overflow: 'hidden',
1920
+ display: 'flex',
1921
+ alignItems: 'stretch',
1922
+ justifyContent: 'center',
1911
1923
  }}
1912
- >
1924
+ >
1913
1925
  {currentProduct && (
1914
- <div style={{ width: '100%', height: '100%', display: 'flex' }}>
1915
- <ProductCard product={currentProduct} layout={layout} />
1916
- </div>
1926
+ <div style={{ width: '100%', height: '100%', display: 'flex' }}>
1927
+ <ProductCard product={currentProduct} layout={layout} />
1928
+ </div>
1917
1929
  )}
1918
-
1930
+
1919
1931
  {/* Carousel Navigation */}
1920
1932
  {products.length > 1 && (
1921
- <>
1922
- {/* Prev Button */}
1923
- <button
1924
- onClick={prevCarouselItem}
1925
- disabled={carouselIndex === 0}
1926
- style={{
1927
- position: 'absolute',
1928
- left: '10px',
1929
- top: '50%',
1930
- transform: 'translateY(-50%)',
1931
- background: 'rgba(0,0,0,0.5)',
1932
- color: 'white',
1933
- border: 'none',
1934
- borderRadius: '50%',
1935
- width: '40px',
1936
- height: '40px',
1937
- cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
1938
- opacity: carouselIndex === 0 ? 0.5 : 1,
1939
- display: 'flex',
1940
- alignItems: 'center',
1941
- justifyContent: 'center',
1942
- fontSize: '18px',
1943
- zIndex: 10,
1944
- }}
1945
- >
1946
-
1947
- </button>
1948
-
1949
- {/* Next Button */}
1950
- <button
1951
- onClick={nextCarouselItem}
1952
- disabled={carouselIndex >= products.length - 1}
1953
- style={{
1954
- position: 'absolute',
1955
- right: '10px',
1956
- top: '50%',
1957
- transform: 'translateY(-50%)',
1958
- background: 'rgba(0,0,0,0.5)',
1959
- color: 'white',
1960
- border: 'none',
1961
- borderRadius: '50%',
1962
- width: '40px',
1963
- height: '40px',
1964
- cursor:
1965
- carouselIndex >= products.length - 1
1966
- ? 'not-allowed'
1967
- : 'pointer',
1968
- opacity: carouselIndex >= products.length - 1 ? 0.5 : 1,
1969
- display: 'flex',
1970
- alignItems: 'center',
1971
- justifyContent: 'center',
1972
- fontSize: '18px',
1973
- zIndex: 10,
1974
- }}
1975
- >
1976
-
1977
- </button>
1978
-
1979
- {/* Dots */}
1980
- <div
1981
- style={{
1982
- position: 'absolute',
1983
- bottom: '10px',
1984
- left: '50%',
1985
- transform: 'translateX(-50%)',
1986
- display: 'flex',
1987
- gap: '8px',
1988
- zIndex: 10,
1989
- }}
1990
- >
1991
- {products.map((_: any, index: number) => (
1933
+ <>
1934
+ {/* Prev Button */}
1992
1935
  <button
1993
- key={index}
1994
- onClick={() => goToCarouselItem(index)}
1995
- style={{
1996
- width: index === carouselIndex ? '12px' : '8px',
1997
- height: index === carouselIndex ? '12px' : '8px',
1998
- borderRadius: '50%',
1999
- border: 'none',
2000
- background:
2001
- index === carouselIndex
2002
- ? 'white'
2003
- : 'rgba(255,255,255,0.5)',
2004
- cursor: 'pointer',
2005
- transition: 'all 0.3s ease',
2006
- }}
2007
- />
2008
- ))}
2009
- </div>
2010
- </>
2011
- )}
2012
- </div>
2013
- );
2014
- }
2015
-
2016
- // Other layouts: Grid view with limited products
2017
- const getMaxProducts = () => {
2018
- switch (layout) {
2019
- case '1x2': return 2;
2020
- case '2x1': return 2;
2021
- case '2x2': return 4;
2022
- default: return products.length;
2023
- }
2024
- };
2025
-
2026
- // For grid layouts (1x2, 2x1, 2x2), show one grid per slide
2027
- if (layout !== '1x1' && scrollType) {
2028
- // Group items into slides based on layout
2029
- const itemsPerSlide = layout === '2x2' ? 4 : 2; // 4 for 2x2, 2 for 1x2/2x1
2030
- const slides: any[][] = [];
2031
- for (let i = 0; i < products.length; i += itemsPerSlide) {
2032
- slides.push(products.slice(i, i + itemsPerSlide));
2033
- }
2034
-
2035
- const scrollDirection = scrollType === 'horizontal' ? 'row' : 'column';
2036
-
2037
- return (
2038
- <div
2039
- style={{
2040
- width: '100%',
2041
- height: '100%',
2042
- overflow: scrollType === 'horizontal' ? 'auto' : 'auto',
2043
- display: 'flex',
2044
- flexDirection: scrollDirection as any,
2045
- scrollSnapType: scrollType === 'horizontal' ? 'x mandatory' : 'y mandatory',
2046
- gap: 0,
2047
- padding: 0,
2048
- }}
2049
- className="scroll-container"
2050
- >
2051
- {slides.map((slideProducts, slideIndex) => (
1936
+ onClick={prevCarouselItem}
1937
+ disabled={carouselIndex === 0}
1938
+ style={{
1939
+ position: 'absolute',
1940
+ left: '10px',
1941
+ top: '50%',
1942
+ transform: 'translateY(-50%)',
1943
+ background: 'rgba(0,0,0,0.5)',
1944
+ color: 'white',
1945
+ border: 'none',
1946
+ borderRadius: '50%',
1947
+ width: '40px',
1948
+ height: '40px',
1949
+ cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
1950
+ opacity: carouselIndex === 0 ? 0.5 : 1,
1951
+ display: 'flex',
1952
+ alignItems: 'center',
1953
+ justifyContent: 'center',
1954
+ fontSize: '18px',
1955
+ zIndex: 10,
1956
+ }}
1957
+ >
1958
+
1959
+ </button>
1960
+
1961
+ {/* Next Button */}
1962
+ <button
1963
+ onClick={nextCarouselItem}
1964
+ disabled={carouselIndex >= products.length - 1}
1965
+ style={{
1966
+ position: 'absolute',
1967
+ right: '10px',
1968
+ top: '50%',
1969
+ transform: 'translateY(-50%)',
1970
+ background: 'rgba(0,0,0,0.5)',
1971
+ color: 'white',
1972
+ border: 'none',
1973
+ borderRadius: '50%',
1974
+ width: '40px',
1975
+ height: '40px',
1976
+ cursor:
1977
+ carouselIndex >= products.length - 1
1978
+ ? 'not-allowed'
1979
+ : 'pointer',
1980
+ opacity: carouselIndex >= products.length - 1 ? 0.5 : 1,
1981
+ display: 'flex',
1982
+ alignItems: 'center',
1983
+ justifyContent: 'center',
1984
+ fontSize: '18px',
1985
+ zIndex: 10,
1986
+ }}
1987
+ >
1988
+
1989
+ </button>
1990
+
1991
+ {/* Dots */}
2052
1992
  <div
2053
- key={slideIndex}
2054
1993
  style={{
2055
- width: scrollType === 'horizontal' ? '100%' : '100%',
2056
- height: scrollType === 'vertical' ? '100%' : '100%',
2057
- minWidth: scrollType === 'horizontal' ? '100%' : '100%',
2058
- minHeight: scrollType === 'vertical' ? '100%' : '100%',
2059
- flexShrink: 0,
2060
- scrollSnapAlign: 'start',
2061
- display: 'grid',
2062
- gridTemplateColumns: getGridColumns(),
2063
- gridTemplateRows: getGridRows(),
2064
- gap: '12px',
2065
- padding: '12px',
2066
- overflow: 'hidden',
2067
- alignContent: 'stretch'
1994
+ position: 'absolute',
1995
+ bottom: '10px',
1996
+ left: '50%',
1997
+ transform: 'translateX(-50%)',
1998
+ display: 'flex',
1999
+ gap: '8px',
2000
+ zIndex: 10,
2068
2001
  }}
2069
2002
  >
2070
- {slideProducts.map((product: any, index: number) => (
2071
- <div
2003
+ {products.map((_: any, index: number) => (
2004
+ <button
2072
2005
  key={index}
2006
+ onClick={() => goToCarouselItem(index)}
2073
2007
  style={{
2074
- width: '100%',
2075
- height: '100%',
2076
- maxHeight: '100%',
2077
- overflow: 'hidden',
2078
- display: 'flex',
2079
- alignItems: 'stretch',
2080
- justifyContent: 'center',
2081
- minHeight: 0,
2082
- minWidth: 0
2008
+ width: index === carouselIndex ? '12px' : '8px',
2009
+ height: index === carouselIndex ? '12px' : '8px',
2010
+ borderRadius: '50%',
2011
+ border: 'none',
2012
+ background:
2013
+ index === carouselIndex
2014
+ ? 'white'
2015
+ : 'rgba(255,255,255,0.5)',
2016
+ cursor: 'pointer',
2017
+ transition: 'all 0.3s ease',
2083
2018
  }}
2084
- >
2085
- <ProductCard product={product} layout={layout} />
2086
- </div>
2019
+ />
2087
2020
  ))}
2088
2021
  </div>
2089
- ))}
2090
- </div>
2091
- );
2092
- }
2093
-
2094
- // Grid layout without scroll - show one grid per slide with carousel navigation
2095
- if (layout !== '1x1') {
2096
- // Group items into slides based on layout
2097
- const itemsPerSlide = layout === '2x2' ? 4 : 2; // 4 for 2x2, 2 for 1x2/2x1
2098
- const slides: any[][] = [];
2099
- for (let i = 0; i < products.length; i += itemsPerSlide) {
2100
- slides.push(products.slice(i, i + itemsPerSlide));
2101
- }
2102
-
2103
- const currentSlide = slides[carouselIndex] || slides[0] || [];
2104
-
2105
- const groupProductContentStyle = {
2106
- display: 'grid',
2107
- gridTemplateColumns: getGridColumns(),
2108
- gridTemplateRows: getGridRows(),
2109
- gap: '12px',
2110
- height: '100%',
2111
- width: '100%',
2112
- overflow: 'hidden',
2113
- padding: '12px',
2114
- alignItems: 'stretch',
2115
- alignContent: 'stretch'
2116
- };
2117
-
2118
- return (
2119
- <div style={{ position: 'relative', width: '100%', height: '100%' }}>
2120
- <div style={groupProductContentStyle}>
2121
- {currentSlide.map((product: any, index: number) => (
2022
+ </>
2023
+ )}
2024
+ </div>
2025
+ );
2026
+ }
2027
+
2028
+ // Other layouts: Grid view with limited products
2029
+ const getMaxProducts = () => {
2030
+ switch (layout) {
2031
+ case '1x2': return 2;
2032
+ case '2x1': return 2;
2033
+ case '2x2': return 4;
2034
+ default: return products.length;
2035
+ }
2036
+ };
2037
+
2038
+ // For grid layouts (1x2, 2x1, 2x2), show one grid per slide
2039
+ if (layout !== '1x1' && scrollType) {
2040
+ // Group items into slides based on layout
2041
+ const itemsPerSlide = layout === '2x2' ? 4 : 2; // 4 for 2x2, 2 for 1x2/2x1
2042
+ const slides: any[][] = [];
2043
+ for (let i = 0; i < products.length; i += itemsPerSlide) {
2044
+ slides.push(products.slice(i, i + itemsPerSlide));
2045
+ }
2046
+
2047
+ const scrollDirection = scrollType === 'horizontal' ? 'row' : 'column';
2048
+
2049
+ return (
2050
+ <div
2051
+ style={{
2052
+ width: '100%',
2053
+ height: '100%',
2054
+ overflow: scrollType === 'horizontal' ? 'auto' : 'auto',
2055
+ display: 'flex',
2056
+ flexDirection: scrollDirection as any,
2057
+ scrollSnapType: scrollType === 'horizontal' ? 'x mandatory' : 'y mandatory',
2058
+ gap: 0,
2059
+ padding: 0,
2060
+ }}
2061
+ className="scroll-container"
2062
+ >
2063
+ {slides.map((slideProducts, slideIndex) => (
2064
+ <div
2065
+ key={slideIndex}
2066
+ style={{
2067
+ width: scrollType === 'horizontal' ? '100%' : '100%',
2068
+ height: scrollType === 'vertical' ? '100%' : '100%',
2069
+ minWidth: scrollType === 'horizontal' ? '100%' : '100%',
2070
+ minHeight: scrollType === 'vertical' ? '100%' : '100%',
2071
+ flexShrink: 0,
2072
+ scrollSnapAlign: 'start',
2073
+ display: 'grid',
2074
+ gridTemplateColumns: getGridColumns(),
2075
+ gridTemplateRows: getGridRows(),
2076
+ gap: '12px',
2077
+ padding: '12px',
2078
+ overflow: 'hidden',
2079
+ alignContent: 'stretch'
2080
+ }}
2081
+ >
2082
+ {slideProducts.map((product: any, index: number) => (
2122
2083
  <div
2123
2084
  key={index}
2124
2085
  style={{
@@ -2137,98 +2098,23 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
2137
2098
  </div>
2138
2099
  ))}
2139
2100
  </div>
2140
-
2141
- {/* Carousel Navigation for grid slides */}
2142
- {slides.length > 1 && (
2143
- <>
2144
- <button
2145
- onClick={prevCarouselItem}
2146
- disabled={carouselIndex === 0}
2147
- style={{
2148
- position: 'absolute',
2149
- left: '10px',
2150
- top: '50%',
2151
- transform: 'translateY(-50%)',
2152
- background: 'rgba(0,0,0,0.5)',
2153
- color: 'white',
2154
- border: 'none',
2155
- borderRadius: '50%',
2156
- width: '40px',
2157
- height: '40px',
2158
- cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
2159
- opacity: carouselIndex === 0 ? 0.5 : 1,
2160
- display: 'flex',
2161
- alignItems: 'center',
2162
- justifyContent: 'center',
2163
- fontSize: '18px',
2164
- zIndex: 10,
2165
- }}
2166
- >
2167
-
2168
- </button>
2169
- <button
2170
- onClick={nextCarouselItem}
2171
- disabled={carouselIndex >= slides.length - 1}
2172
- style={{
2173
- position: 'absolute',
2174
- right: '10px',
2175
- top: '50%',
2176
- transform: 'translateY(-50%)',
2177
- background: 'rgba(0,0,0,0.5)',
2178
- color: 'white',
2179
- border: 'none',
2180
- borderRadius: '50%',
2181
- width: '40px',
2182
- height: '40px',
2183
- cursor: carouselIndex >= slides.length - 1 ? 'not-allowed' : 'pointer',
2184
- opacity: carouselIndex >= slides.length - 1 ? 0.5 : 1,
2185
- display: 'flex',
2186
- alignItems: 'center',
2187
- justifyContent: 'center',
2188
- fontSize: '18px',
2189
- zIndex: 10,
2190
- }}
2191
- >
2192
-
2193
- </button>
2194
- <div
2195
- style={{
2196
- position: 'absolute',
2197
- bottom: '10px',
2198
- left: '50%',
2199
- transform: 'translateX(-50%)',
2200
- display: 'flex',
2201
- gap: '8px',
2202
- zIndex: 10,
2203
- }}
2204
- >
2205
- {slides.map((_, index) => (
2206
- <button
2207
- key={index}
2208
- onClick={() => goToCarouselItem(index)}
2209
- style={{
2210
- width: index === carouselIndex ? '12px' : '8px',
2211
- height: index === carouselIndex ? '12px' : '8px',
2212
- borderRadius: '50%',
2213
- border: 'none',
2214
- background: index === carouselIndex ? 'white' : 'rgba(255,255,255,0.5)',
2215
- cursor: 'pointer',
2216
- transition: 'all 0.3s ease',
2217
- }}
2218
- />
2219
- ))}
2220
- </div>
2221
- </>
2222
- )}
2223
- </div>
2224
- );
2225
- }
2226
-
2227
- // Fallback for 1x1 or other cases
2228
- const maxProducts = getMaxProducts();
2229
- const displayProducts = products.slice(0, maxProducts);
2230
-
2231
- const groupProductContentStyle = {
2101
+ ))}
2102
+ </div>
2103
+ );
2104
+ }
2105
+
2106
+ // Grid layout without scroll - show one grid per slide with carousel navigation
2107
+ if (layout !== '1x1') {
2108
+ // Group items into slides based on layout
2109
+ const itemsPerSlide = layout === '2x2' ? 4 : 2; // 4 for 2x2, 2 for 1x2/2x1
2110
+ const slides: any[][] = [];
2111
+ for (let i = 0; i < products.length; i += itemsPerSlide) {
2112
+ slides.push(products.slice(i, i + itemsPerSlide));
2113
+ }
2114
+
2115
+ const currentSlide = slides[carouselIndex] || slides[0] || [];
2116
+
2117
+ const groupProductContentStyle = {
2232
2118
  display: 'grid',
2233
2119
  gridTemplateColumns: getGridColumns(),
2234
2120
  gridTemplateRows: getGridRows(),
@@ -2238,46 +2124,172 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
2238
2124
  overflow: 'hidden',
2239
2125
  padding: '12px',
2240
2126
  alignItems: 'stretch',
2241
- };
2242
-
2243
- return (
2244
- <div style={groupProductContentStyle}>
2245
- {displayProducts.map((product: any, index: number) => (
2127
+ alignContent: 'stretch'
2128
+ };
2129
+
2130
+ return (
2131
+ <div style={{ position: 'relative', width: '100%', height: '100%' }}>
2132
+ <div style={groupProductContentStyle}>
2133
+ {currentSlide.map((product: any, index: number) => (
2134
+ <div
2135
+ key={index}
2136
+ style={{
2137
+ width: '100%',
2138
+ height: '100%',
2139
+ maxHeight: '100%',
2140
+ overflow: 'hidden',
2141
+ display: 'flex',
2142
+ alignItems: 'stretch',
2143
+ justifyContent: 'center',
2144
+ minHeight: 0,
2145
+ minWidth: 0
2146
+ }}
2147
+ >
2148
+ <ProductCard product={product} layout={layout} />
2149
+ </div>
2150
+ ))}
2151
+ </div>
2152
+
2153
+ {/* Carousel Navigation for grid slides */}
2154
+ {slides.length > 1 && (
2155
+ <>
2156
+ <button
2157
+ onClick={prevCarouselItem}
2158
+ disabled={carouselIndex === 0}
2159
+ style={{
2160
+ position: 'absolute',
2161
+ left: '10px',
2162
+ top: '50%',
2163
+ transform: 'translateY(-50%)',
2164
+ background: 'rgba(0,0,0,0.5)',
2165
+ color: 'white',
2166
+ border: 'none',
2167
+ borderRadius: '50%',
2168
+ width: '40px',
2169
+ height: '40px',
2170
+ cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
2171
+ opacity: carouselIndex === 0 ? 0.5 : 1,
2172
+ display: 'flex',
2173
+ alignItems: 'center',
2174
+ justifyContent: 'center',
2175
+ fontSize: '18px',
2176
+ zIndex: 10,
2177
+ }}
2178
+ >
2179
+
2180
+ </button>
2181
+ <button
2182
+ onClick={nextCarouselItem}
2183
+ disabled={carouselIndex >= slides.length - 1}
2184
+ style={{
2185
+ position: 'absolute',
2186
+ right: '10px',
2187
+ top: '50%',
2188
+ transform: 'translateY(-50%)',
2189
+ background: 'rgba(0,0,0,0.5)',
2190
+ color: 'white',
2191
+ border: 'none',
2192
+ borderRadius: '50%',
2193
+ width: '40px',
2194
+ height: '40px',
2195
+ cursor: carouselIndex >= slides.length - 1 ? 'not-allowed' : 'pointer',
2196
+ opacity: carouselIndex >= slides.length - 1 ? 0.5 : 1,
2197
+ display: 'flex',
2198
+ alignItems: 'center',
2199
+ justifyContent: 'center',
2200
+ fontSize: '18px',
2201
+ zIndex: 10,
2202
+ }}
2203
+ >
2204
+
2205
+ </button>
2206
+ <div
2207
+ style={{
2208
+ position: 'absolute',
2209
+ bottom: '10px',
2210
+ left: '50%',
2211
+ transform: 'translateX(-50%)',
2212
+ display: 'flex',
2213
+ gap: '8px',
2214
+ zIndex: 10,
2215
+ }}
2216
+ >
2217
+ {slides.map((_, index) => (
2218
+ <button
2219
+ key={index}
2220
+ onClick={() => goToCarouselItem(index)}
2221
+ style={{
2222
+ width: index === carouselIndex ? '12px' : '8px',
2223
+ height: index === carouselIndex ? '12px' : '8px',
2224
+ borderRadius: '50%',
2225
+ border: 'none',
2226
+ background: index === carouselIndex ? 'white' : 'rgba(255,255,255,0.5)',
2227
+ cursor: 'pointer',
2228
+ transition: 'all 0.3s ease',
2229
+ }}
2230
+ />
2231
+ ))}
2232
+ </div>
2233
+ </>
2234
+ )}
2235
+ </div>
2236
+ );
2237
+ }
2238
+
2239
+ // Fallback for 1x1 or other cases
2240
+ const maxProducts = getMaxProducts();
2241
+ const displayProducts = products.slice(0, maxProducts);
2242
+
2243
+ const groupProductContentStyle = {
2244
+ display: 'grid',
2245
+ gridTemplateColumns: getGridColumns(),
2246
+ gridTemplateRows: getGridRows(),
2247
+ gap: '12px',
2248
+ height: '100%',
2249
+ width: '100%',
2250
+ overflow: 'hidden',
2251
+ padding: '12px',
2252
+ alignItems: 'stretch',
2253
+ };
2254
+
2255
+ return (
2256
+ <div style={groupProductContentStyle}>
2257
+ {displayProducts.map((product: any, index: number) => (
2246
2258
  <div
2247
- key={index}
2248
- style={{
2249
- width: '100%',
2250
- height: '100%',
2251
- display: 'flex',
2252
- alignItems: 'stretch',
2253
- justifyContent: 'center',
2254
- minHeight: 0,
2255
- minWidth: 0
2256
- }}
2259
+ key={index}
2260
+ style={{
2261
+ width: '100%',
2262
+ height: '100%',
2263
+ display: 'flex',
2264
+ alignItems: 'stretch',
2265
+ justifyContent: 'center',
2266
+ minHeight: 0,
2267
+ minWidth: 0
2268
+ }}
2257
2269
  >
2258
- <ProductCard product={product} layout={layout} />
2259
- </div>
2260
- ))}
2270
+ <ProductCard product={product} layout={layout} />
2271
+ </div>
2272
+ ))}
2273
+ </div>
2274
+ );
2275
+ }
2276
+ break;
2277
+
2278
+ default:
2279
+ return (
2280
+ <div style={{
2281
+ ...contentStyle,
2282
+ alignItems: 'center',
2283
+ justifyContent: 'center',
2284
+ backgroundColor: '#f8f9fa',
2285
+ borderRadius: '8px'
2286
+ }}>
2287
+ <p style={{ color: '#6c757d', textAlign: 'center' }}>No content available</p>
2261
2288
  </div>
2262
2289
  );
2263
- }
2264
- break;
2265
-
2266
- default:
2267
- return (
2268
- <div style={{
2269
- ...contentStyle,
2270
- alignItems: 'center',
2271
- justifyContent: 'center',
2272
- backgroundColor: '#f8f9fa',
2273
- borderRadius: '8px'
2274
- }}>
2275
- <p style={{ color: '#6c757d', textAlign: 'center' }}>No content available</p>
2276
- </div>
2277
- );
2278
- }
2290
+ }
2279
2291
 
2280
- return null;
2292
+ return null;
2281
2293
  } catch (error: any) {
2282
2294
  console.error('Error rendering tab content:', error);
2283
2295
  return (
@@ -2313,107 +2325,107 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
2313
2325
  }
2314
2326
 
2315
2327
  return (
2316
- <div style={{
2317
- width: '100%',
2318
- height: `${props.height}px`,
2319
- backgroundColor: '#ffffff',
2320
- overflow: 'hidden',
2321
- boxShadow: '0 4px 20px rgba(0,0,0,0.08)',
2322
- border: '1px solid #e5e7eb',
2323
- display: 'flex',
2324
- flexDirection: isVertical ? 'row' : 'column'
2325
- }}>
2326
- {/* Header Section */}
2327
- <div style={{
2328
- display: 'flex',
2329
- flexDirection: isVertical ? 'column' : 'row',
2330
- alignItems: isVertical ? 'stretch' : 'center',
2331
- justifyContent: isVertical ? 'space-between' : 'space-between',
2332
- padding: isVertical ? '20px 16px' : '20px 32px',
2333
- backgroundColor: '#ffffff',
2334
- borderBottom: isVertical ? 'none' : '1px solid #e5e7eb',
2335
- borderRight: isVertical ? '1px solid #e5e7eb' : 'none',
2336
- width: isVertical ? '200px' : '100%',
2337
- minWidth: isVertical ? '180px' : 'auto',
2338
- flexShrink: 0
2339
- }}>
2340
- {/* Title */}
2341
- {props.showTitle && (
2342
- <h1 style={{
2343
- ...getTitleStyle(),
2344
- marginBottom: isVertical ? '16px' : '0',
2345
- textAlign: isVertical ? 'left' : getTitleStyle().textAlign,
2346
- writingMode: isVertical ? 'horizontal-tb' : 'initial',
2347
- flexShrink: 0
2348
- }}>
2349
- {props?.title?.titleText}
2350
- </h1>
2351
- )}
2352
-
2353
- {/* Tab Headers */}
2354
2328
  <div style={{
2329
+ width: '100%',
2330
+ height: `${props.height}px`,
2331
+ backgroundColor: '#ffffff',
2332
+ overflow: 'hidden',
2333
+ boxShadow: '0 4px 20px rgba(0,0,0,0.08)',
2334
+ border: '1px solid #e5e7eb',
2355
2335
  display: 'flex',
2356
- flexDirection: isVertical ? 'column' : 'row',
2357
- alignItems: isVertical ? 'stretch' : 'center',
2358
- gap: isVertical ? '8px' : '0',
2359
- width: isVertical ? '100%' : 'auto',
2360
- marginLeft: isVertical ? '0' : 'auto'
2336
+ flexDirection: isVertical ? 'row' : 'column'
2361
2337
  }}>
2362
- {tabs.map((tab, index) => (
2363
- <React.Fragment key={index}>
2364
- <button
2365
- style={{
2366
- ...getTabHeaderStyle(index),
2367
- width: isVertical ? '100%' : 'auto',
2368
- textAlign: isVertical ? 'left' : 'center',
2369
- justifyContent: isVertical ? 'flex-start' : 'center',
2370
- marginBottom: isVertical ? '0' : '0'
2371
- }}
2372
- onClick={() => handleTabChange(index)}
2373
- onMouseEnter={() => setHoveredTab(index)}
2374
- onMouseLeave={() => setHoveredTab(null)}
2375
- >
2376
- {tab.tabHeaderType === 'text' ? (
2377
- tab.tabHeaderText
2378
- ) : tab.tabHeaderImage.url ? (
2379
- <img
2380
- src={getImageUrl(tab.tabHeaderImage.url)}
2381
- alt={tab.tabHeaderImage.alt}
2382
- style={{ height: '20px', width: 'auto' }}
2383
- />
2384
- ) : (
2385
- tab.tabHeaderText
2386
- )}
2387
- </button>
2388
-
2389
- {/* Add separator - only for horizontal orientation */}
2390
- {!isVertical && index < tabs.length - 1 && (
2391
- <span style={{ color: '#9ca3af', margin: '0 4px' }}>|</span>
2392
- )}
2393
- </React.Fragment>
2394
- ))}
2395
- </div>
2338
+ {/* Header Section */}
2339
+ <div style={{
2340
+ display: 'flex',
2341
+ flexDirection: isVertical ? 'column' : 'row',
2342
+ alignItems: isVertical ? 'stretch' : 'center',
2343
+ justifyContent: isVertical ? 'space-between' : 'space-between',
2344
+ padding: isVertical ? '20px 16px' : '20px 32px',
2345
+ backgroundColor: '#ffffff',
2346
+ borderBottom: isVertical ? 'none' : '1px solid #e5e7eb',
2347
+ borderRight: isVertical ? '1px solid #e5e7eb' : 'none',
2348
+ width: isVertical ? '200px' : '100%',
2349
+ minWidth: isVertical ? '180px' : 'auto',
2350
+ flexShrink: 0
2351
+ }}>
2352
+ {/* Title */}
2353
+ {props.showTitle && (
2354
+ <h1 style={{
2355
+ ...getTitleStyle(),
2356
+ marginBottom: isVertical ? '16px' : '0',
2357
+ textAlign: isVertical ? 'left' : getTitleStyle().textAlign,
2358
+ writingMode: isVertical ? 'horizontal-tb' : 'initial',
2359
+ flexShrink: 0
2360
+ }}>
2361
+ {props?.title?.titleText}
2362
+ </h1>
2363
+ )}
2364
+
2365
+ {/* Tab Headers */}
2366
+ <div style={{
2367
+ display: 'flex',
2368
+ flexDirection: isVertical ? 'column' : 'row',
2369
+ alignItems: isVertical ? 'stretch' : 'center',
2370
+ gap: isVertical ? '8px' : '0',
2371
+ width: isVertical ? '100%' : 'auto',
2372
+ marginLeft: isVertical ? '0' : 'auto'
2373
+ }}>
2374
+ {tabs.map((tab, index) => (
2375
+ <React.Fragment key={index}>
2376
+ <button
2377
+ style={{
2378
+ ...getTabHeaderStyle(index),
2379
+ width: isVertical ? '100%' : 'auto',
2380
+ textAlign: isVertical ? 'left' : 'center',
2381
+ justifyContent: isVertical ? 'flex-start' : 'center',
2382
+ marginBottom: isVertical ? '0' : '0'
2383
+ }}
2384
+ onClick={() => handleTabChange(index)}
2385
+ onMouseEnter={() => setHoveredTab(index)}
2386
+ onMouseLeave={() => setHoveredTab(null)}
2387
+ >
2388
+ {tab.tabHeaderType === 'text' ? (
2389
+ tab.tabHeaderText
2390
+ ) : tab.tabHeaderImage.url ? (
2391
+ <img
2392
+ src={getImageUrl(tab.tabHeaderImage.url)}
2393
+ alt={tab.tabHeaderImage.alt}
2394
+ style={{ height: '20px', width: 'auto' }}
2395
+ />
2396
+ ) : (
2397
+ tab.tabHeaderText
2398
+ )}
2399
+ </button>
2396
2400
 
2397
- </div>
2401
+ {/* Add separator - only for horizontal orientation */}
2402
+ {!isVertical && index < tabs.length - 1 && (
2403
+ <span style={{ color: '#9ca3af', margin: '0 4px' }}>|</span>
2404
+ )}
2405
+ </React.Fragment>
2406
+ ))}
2407
+ </div>
2398
2408
 
2399
- {/* Tab Content */}
2400
- <div style={{
2401
- position: 'relative',
2402
- flex: 1,
2403
- overflow: 'hidden',
2404
- minHeight: 0, // Allows flex child to shrink below content size
2405
- width: isVertical ? 'calc(100% - 200px)' : '100%'
2406
- }}>
2407
- {tabs.length > 0 && tabs[activeTab] && (
2409
+ </div>
2410
+
2411
+ {/* Tab Content */}
2408
2412
  <div style={{
2409
- width: '100%',
2410
- height: '100%'
2413
+ position: 'relative',
2414
+ flex: 1,
2415
+ overflow: 'hidden',
2416
+ minHeight: 0, // Allows flex child to shrink below content size
2417
+ width: isVertical ? 'calc(100% - 200px)' : '100%'
2411
2418
  }}>
2412
- {renderTabContent(tabs[activeTab])}
2419
+ {tabs.length > 0 && tabs[activeTab] && (
2420
+ <div style={{
2421
+ width: '100%',
2422
+ height: '100%'
2423
+ }}>
2424
+ {renderTabContent(tabs[activeTab])}
2425
+ </div>
2426
+ )}
2413
2427
  </div>
2414
- )}
2415
- </div>
2416
- </div>
2428
+ </div>
2417
2429
  );
2418
2430
  } catch (error: any) {
2419
2431
  console.error('Error rendering TabComponent:', error);