tee3apps-cms-sdk-react 0.0.13 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tee3apps-cms-sdk-react",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
4
4
  "description": "Uses JSON to dynamically generate and render UI pages in a website",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -326,6 +326,33 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
326
326
  setCarouselIndex(0);
327
327
  };
328
328
 
329
+ // Helper function to get sliding window indices for grid layouts
330
+ const getSlidingWindowIndices = (totalItems: number, itemsPerView: number, currentIndex: number): number[] => {
331
+ if (totalItems <= itemsPerView) {
332
+ // If total items <= items per view, show all items
333
+ return Array.from({ length: totalItems }, (_, i) => i);
334
+ }
335
+
336
+ // Calculate max possible index for sliding window
337
+ // For sliding window, we can slide until the last item is shown
338
+ // Max index is when we show the last itemsPerView items
339
+ const maxIndex = totalItems - itemsPerView;
340
+ const clampedIndex = Math.min(Math.max(0, currentIndex), maxIndex);
341
+
342
+ // Sliding window: start from clampedIndex, show itemsPerView items
343
+ return Array.from({ length: itemsPerView }, (_, i) => clampedIndex + i);
344
+ };
345
+
346
+ // Helper function to calculate max carousel index for grid layouts
347
+ const getMaxCarouselIndex = (totalItems: number, itemsPerView: number): number => {
348
+ if (totalItems <= itemsPerView) {
349
+ return 0; // No carousel needed
350
+ }
351
+ // For sliding window, max index is when we show the last itemsPerView items
352
+ // This allows us to slide through all items with overlapping windows
353
+ return Math.max(0, totalItems - itemsPerView);
354
+ };
355
+
329
356
  const getCurrentLayout = (tab: TabItem): string => {
330
357
  switch (deviceMode) {
331
358
  case 'mobileweb': return tab.mode.mobileweb.layout;
@@ -599,7 +626,7 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
599
626
  );
600
627
  }
601
628
 
602
- // Other layouts: grid display with limited images
629
+ // Other layouts: grid display with sliding window carousel
603
630
  const getMaxImages = () => {
604
631
  switch (layout) {
605
632
  case '1x2': return 2;
@@ -609,8 +636,11 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
609
636
  }
610
637
  };
611
638
 
612
- const maxImages = getMaxImages();
613
- const displayImages = images.slice(0, maxImages);
639
+ const itemsPerView = getMaxImages();
640
+ const maxCarouselIndex = getMaxCarouselIndex(images.length, itemsPerView);
641
+ const displayIndices = getSlidingWindowIndices(images.length, itemsPerView, carouselIndex);
642
+ const displayImages = displayIndices.map(idx => images[idx]);
643
+ const needsCarousel = images.length > itemsPerView;
614
644
 
615
645
  // Create dynamic content style for GROUPIMAGE
616
646
  const groupImageContentStyle = {
@@ -623,46 +653,137 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
623
653
  };
624
654
 
625
655
  return (
626
- <div style={groupImageContentStyle} className='media-box'>
627
- {displayImages.map((image, index) => {
628
- const linkUrl = buildLinkForSlide(image);
629
- const imageElement = (
630
- <div key={index} className="media-box">
631
- <img
632
- src={getImageUrl(
633
- tab.tabContentGroupImage.type === 'DYNAMIC'
634
- ? image.image.url
635
- : image.attr?.url
636
- )}
637
- alt={
638
- tab.tabContentGroupImage.type === 'DYNAMIC'
639
- ? image.image.alt
640
- : image.attr?.alt
656
+ <div style={{ position: 'relative', width: '100%' }}>
657
+ <div style={groupImageContentStyle} className='media-box'>
658
+ {displayImages.map((image, displayIndex) => {
659
+ const originalIndex = displayIndices[displayIndex];
660
+ const linkUrl = buildLinkForSlide(image);
661
+ const imageElement = (
662
+ <div key={originalIndex} className="media-box">
663
+ <img
664
+ src={getImageUrl(
665
+ tab.tabContentGroupImage.type === 'DYNAMIC'
666
+ ? image.image.url
667
+ : image.attr?.url
668
+ )}
669
+ alt={
670
+ tab.tabContentGroupImage.type === 'DYNAMIC'
671
+ ? image.image.alt
672
+ : image.attr?.alt
673
+ }
674
+ className="media-content"
675
+ />
676
+ </div>
677
+ );
678
+
679
+ return linkUrl ? (
680
+ <a
681
+ key={originalIndex}
682
+ href={linkUrl}
683
+ target={
684
+ image.link_type === 'EXTERNALLINK'
685
+ ? image.external_link?.target || '_blank'
686
+ : '_self'
641
687
  }
642
- className="media-content"
643
- />
644
- </div>
645
- );
646
-
647
- return linkUrl ? (
648
- <a
649
- key={index}
650
- href={linkUrl}
651
- target={
652
- image.link_type === 'EXTERNALLINK'
653
- ? image.external_link?.target || '_blank'
654
- : '_self'
655
- }
656
- className='media-box'
688
+ className='media-box'
689
+ >
690
+ {imageElement}
691
+ </a>
692
+ ) : (
693
+ <div key={originalIndex} style={{ width: '100%', height: '100%' }}>
694
+ {imageElement}
695
+ </div>
696
+ );
697
+ })}
698
+ </div>
699
+
700
+ {/* Carousel Navigation for Grid Layouts */}
701
+ {needsCarousel && (
702
+ <>
703
+ {/* Previous Button */}
704
+ <button
705
+ onClick={prevCarouselItem}
706
+ disabled={carouselIndex === 0}
707
+ style={{
708
+ position: 'absolute',
709
+ left: '10px',
710
+ top: '50%',
711
+ transform: 'translateY(-50%)',
712
+ background: 'rgba(0,0,0,0.5)',
713
+ color: 'white',
714
+ border: 'none',
715
+ borderRadius: '50%',
716
+ width: '40px',
717
+ height: '40px',
718
+ cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
719
+ opacity: carouselIndex === 0 ? 0.5 : 1,
720
+ display: 'flex',
721
+ alignItems: 'center',
722
+ justifyContent: 'center',
723
+ fontSize: '18px',
724
+ zIndex: 10,
725
+ }}
657
726
  >
658
- {imageElement}
659
- </a>
660
- ) : (
661
- <div key={index} style={{ width: '100%', height: '100%' }}>
662
- {imageElement}
727
+
728
+ </button>
729
+
730
+ {/* Next Button */}
731
+ <button
732
+ onClick={nextCarouselItem}
733
+ disabled={carouselIndex >= maxCarouselIndex}
734
+ style={{
735
+ position: 'absolute',
736
+ right: '10px',
737
+ top: '50%',
738
+ transform: 'translateY(-50%)',
739
+ background: 'rgba(0,0,0,0.5)',
740
+ color: 'white',
741
+ border: 'none',
742
+ borderRadius: '50%',
743
+ width: '40px',
744
+ height: '40px',
745
+ cursor: carouselIndex >= maxCarouselIndex ? 'not-allowed' : 'pointer',
746
+ opacity: carouselIndex >= maxCarouselIndex ? 0.5 : 1,
747
+ display: 'flex',
748
+ alignItems: 'center',
749
+ justifyContent: 'center',
750
+ fontSize: '18px',
751
+ zIndex: 10,
752
+ }}
753
+ >
754
+
755
+ </button>
756
+
757
+ {/* Dots Indicator */}
758
+ <div
759
+ style={{
760
+ position: 'absolute',
761
+ bottom: '10px',
762
+ left: '50%',
763
+ transform: 'translateX(-50%)',
764
+ display: 'flex',
765
+ gap: '8px',
766
+ zIndex: 10,
767
+ }}
768
+ >
769
+ {Array.from({ length: maxCarouselIndex + 1 }, (_, index) => (
770
+ <button
771
+ key={index}
772
+ onClick={() => goToCarouselItem(index)}
773
+ style={{
774
+ width: index === carouselIndex ? '12px' : '8px',
775
+ height: index === carouselIndex ? '12px' : '8px',
776
+ borderRadius: '50%',
777
+ border: 'none',
778
+ background: index === carouselIndex ? 'white' : 'rgba(255,255,255,0.5)',
779
+ cursor: 'pointer',
780
+ transition: 'all 0.3s ease',
781
+ }}
782
+ />
783
+ ))}
663
784
  </div>
664
- );
665
- })}
785
+ </>
786
+ )}
666
787
  </div>
667
788
  );
668
789
 
@@ -827,7 +948,7 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
827
948
  );
828
949
  }
829
950
 
830
- // Other layouts: grid display with limited videos
951
+ // Other layouts: grid display with sliding window carousel
831
952
  const getMaxVideos = () => {
832
953
  switch (layout) {
833
954
  case '1x2': return 2;
@@ -837,8 +958,11 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
837
958
  }
838
959
  };
839
960
 
840
- const maxVideos = getMaxVideos();
841
- const displayVideos = videos.slice(0, maxVideos);
961
+ const itemsPerView = getMaxVideos();
962
+ const maxCarouselIndex = getMaxCarouselIndex(videos.length, itemsPerView);
963
+ const displayIndices = getSlidingWindowIndices(videos.length, itemsPerView, carouselIndex);
964
+ const displayVideos = displayIndices.map(idx => videos[idx]);
965
+ const needsCarousel = videos.length > itemsPerView;
842
966
 
843
967
  // Create dynamic content style for GROUPVIDEO
844
968
  const groupVideoContentStyle = {
@@ -851,49 +975,140 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
851
975
  };
852
976
 
853
977
  return (
854
- <div style={groupVideoContentStyle} className='media-box'>
855
- {displayVideos.map((video, index) => {
856
- const linkUrl = buildLinkForSlide(video);
857
-
858
- const videoElement = (
859
- <div className="media-box">
860
- <video
861
- src={getImageUrl(
862
- tab.tabContentGroupVideo.type === 'DYNAMIC'
863
- ? video.video?.url
864
- : video.attr?.url
865
- )}
866
- title={
867
- tab.tabContentGroupVideo.type === 'DYNAMIC'
868
- ? video.video?.alt || video.name
869
- : video.attr?.alt
870
- }
871
- className="media-content"
872
- controls
873
- style={{ width: '100%', height: 'auto', objectFit: 'cover' }}
874
- />
875
- </div>
876
- );
978
+ <div style={{ position: 'relative', width: '100%' }}>
979
+ <div style={groupVideoContentStyle} className='media-box'>
980
+ {displayVideos.map((video, displayIndex) => {
981
+ const originalIndex = displayIndices[displayIndex];
982
+ const linkUrl = buildLinkForSlide(video);
983
+
984
+ const videoElement = (
985
+ <div key={originalIndex} className="media-box">
986
+ <video
987
+ src={getImageUrl(
988
+ tab.tabContentGroupVideo.type === 'DYNAMIC'
989
+ ? video.video?.url
990
+ : video.attr?.url
991
+ )}
992
+ title={
993
+ tab.tabContentGroupVideo.type === 'DYNAMIC'
994
+ ? video.video?.alt || video.name
995
+ : video.attr?.alt
996
+ }
997
+ className="media-content"
998
+ controls
999
+ style={{ width: '100%', height: 'auto', objectFit: 'cover' }}
1000
+ />
1001
+ </div>
1002
+ );
877
1003
 
878
- return linkUrl ? (
879
- <a
880
- key={index}
881
- href={linkUrl}
882
- target={
883
- video.link_type === 'EXTERNALLINK'
884
- ? video.external_link?.target || '_blank'
885
- : '_self'
886
- }
887
- className='media-box'
1004
+ return linkUrl ? (
1005
+ <a
1006
+ key={originalIndex}
1007
+ href={linkUrl}
1008
+ target={
1009
+ video.link_type === 'EXTERNALLINK'
1010
+ ? video.external_link?.target || '_blank'
1011
+ : '_self'
1012
+ }
1013
+ className='media-box'
1014
+ >
1015
+ {videoElement}
1016
+ </a>
1017
+ ) : (
1018
+ <div key={originalIndex} style={{ width: '100%', height: '100%' }}>
1019
+ {videoElement}
1020
+ </div>
1021
+ );
1022
+ })}
1023
+ </div>
1024
+
1025
+ {/* Carousel Navigation for Grid Layouts */}
1026
+ {needsCarousel && (
1027
+ <>
1028
+ {/* Previous Button */}
1029
+ <button
1030
+ onClick={prevCarouselItem}
1031
+ disabled={carouselIndex === 0}
1032
+ style={{
1033
+ position: 'absolute',
1034
+ left: '10px',
1035
+ top: '50%',
1036
+ transform: 'translateY(-50%)',
1037
+ background: 'rgba(0,0,0,0.5)',
1038
+ color: 'white',
1039
+ border: 'none',
1040
+ borderRadius: '50%',
1041
+ width: '40px',
1042
+ height: '40px',
1043
+ cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
1044
+ opacity: carouselIndex === 0 ? 0.5 : 1,
1045
+ display: 'flex',
1046
+ alignItems: 'center',
1047
+ justifyContent: 'center',
1048
+ fontSize: '18px',
1049
+ zIndex: 10,
1050
+ }}
888
1051
  >
889
- {videoElement}
890
- </a>
891
- ) : (
892
- <div key={index} style={{ width: '100%', height: '100%' }}>
893
- {videoElement}
1052
+
1053
+ </button>
1054
+
1055
+ {/* Next Button */}
1056
+ <button
1057
+ onClick={nextCarouselItem}
1058
+ disabled={carouselIndex >= maxCarouselIndex}
1059
+ style={{
1060
+ position: 'absolute',
1061
+ right: '10px',
1062
+ top: '50%',
1063
+ transform: 'translateY(-50%)',
1064
+ background: 'rgba(0,0,0,0.5)',
1065
+ color: 'white',
1066
+ border: 'none',
1067
+ borderRadius: '50%',
1068
+ width: '40px',
1069
+ height: '40px',
1070
+ cursor: carouselIndex >= maxCarouselIndex ? 'not-allowed' : 'pointer',
1071
+ opacity: carouselIndex >= maxCarouselIndex ? 0.5 : 1,
1072
+ display: 'flex',
1073
+ alignItems: 'center',
1074
+ justifyContent: 'center',
1075
+ fontSize: '18px',
1076
+ zIndex: 10,
1077
+ }}
1078
+ >
1079
+
1080
+ </button>
1081
+
1082
+ {/* Dots Indicator */}
1083
+ <div
1084
+ style={{
1085
+ position: 'absolute',
1086
+ bottom: '10px',
1087
+ left: '50%',
1088
+ transform: 'translateX(-50%)',
1089
+ display: 'flex',
1090
+ gap: '8px',
1091
+ zIndex: 10,
1092
+ }}
1093
+ >
1094
+ {Array.from({ length: maxCarouselIndex + 1 }, (_, index) => (
1095
+ <button
1096
+ key={index}
1097
+ onClick={() => goToCarouselItem(index)}
1098
+ style={{
1099
+ width: index === carouselIndex ? '12px' : '8px',
1100
+ height: index === carouselIndex ? '12px' : '8px',
1101
+ borderRadius: '50%',
1102
+ border: 'none',
1103
+ background: index === carouselIndex ? 'white' : 'rgba(255,255,255,0.5)',
1104
+ cursor: 'pointer',
1105
+ transition: 'all 0.3s ease',
1106
+ }}
1107
+ />
1108
+ ))}
894
1109
  </div>
895
- );
896
- })}
1110
+ </>
1111
+ )}
897
1112
  </div>
898
1113
  );
899
1114
 
@@ -1023,7 +1238,7 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
1023
1238
  );
1024
1239
  }
1025
1240
 
1026
- // Other layouts: Grid view with limited products
1241
+ // Other layouts: Grid view with sliding window carousel
1027
1242
  const getMaxProducts = () => {
1028
1243
  switch (layout) {
1029
1244
  case '1x2': return 2;
@@ -1033,8 +1248,11 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
1033
1248
  }
1034
1249
  };
1035
1250
 
1036
- const maxProducts = getMaxProducts();
1037
- const displayProducts = products.slice(0, maxProducts);
1251
+ const itemsPerView = getMaxProducts();
1252
+ const maxCarouselIndex = getMaxCarouselIndex(products.length, itemsPerView);
1253
+ const displayIndices = getSlidingWindowIndices(products.length, itemsPerView, carouselIndex);
1254
+ const displayProducts = displayIndices.map(idx => products[idx]);
1255
+ const needsCarousel = products.length > itemsPerView;
1038
1256
 
1039
1257
  // Create dynamic content style for GROUPPRODUCT
1040
1258
  const groupProductContentStyle = {
@@ -1049,26 +1267,119 @@ const TabComponent: React.FC<TabComponentMainProps> = ({ props, deviceMode = 'we
1049
1267
  };
1050
1268
 
1051
1269
  return (
1052
- <div style={groupProductContentStyle}>
1053
- {displayProducts.map((product, index) => (
1054
- <div
1055
- key={index}
1056
- style={{
1057
- width: '100%',
1058
- height: '100%',
1059
- display: 'flex',
1060
- alignItems: 'stretch',
1061
- justifyContent: 'center',
1062
- minHeight: '200px',
1063
- }}
1064
- >
1065
- <ProductCard product={product} layout={layout} />
1270
+ <div style={{ position: 'relative', width: '100%' }}>
1271
+ <div style={groupProductContentStyle}>
1272
+ {displayProducts.map((product, displayIndex) => {
1273
+ const originalIndex = displayIndices[displayIndex];
1274
+ return (
1275
+ <div
1276
+ key={originalIndex}
1277
+ style={{
1278
+ width: '100%',
1279
+ height: '100%',
1280
+ display: 'flex',
1281
+ alignItems: 'stretch',
1282
+ justifyContent: 'center',
1283
+ minHeight: '200px',
1284
+ }}
1285
+ >
1286
+ <ProductCard product={product} layout={layout} />
1287
+ </div>
1288
+ );
1289
+ })}
1290
+ </div>
1291
+
1292
+ {/* Carousel Navigation for Grid Layouts */}
1293
+ {needsCarousel && (
1294
+ <>
1295
+ {/* Previous Button */}
1296
+ <button
1297
+ onClick={prevCarouselItem}
1298
+ disabled={carouselIndex === 0}
1299
+ style={{
1300
+ position: 'absolute',
1301
+ left: '10px',
1302
+ top: '50%',
1303
+ transform: 'translateY(-50%)',
1304
+ background: 'rgba(0,0,0,0.5)',
1305
+ color: 'white',
1306
+ border: 'none',
1307
+ borderRadius: '50%',
1308
+ width: '40px',
1309
+ height: '40px',
1310
+ cursor: carouselIndex === 0 ? 'not-allowed' : 'pointer',
1311
+ opacity: carouselIndex === 0 ? 0.5 : 1,
1312
+ display: 'flex',
1313
+ alignItems: 'center',
1314
+ justifyContent: 'center',
1315
+ fontSize: '18px',
1316
+ zIndex: 10,
1317
+ }}
1318
+ >
1319
+
1320
+ </button>
1321
+
1322
+ {/* Next Button */}
1323
+ <button
1324
+ onClick={nextCarouselItem}
1325
+ disabled={carouselIndex >= maxCarouselIndex}
1326
+ style={{
1327
+ position: 'absolute',
1328
+ right: '10px',
1329
+ top: '50%',
1330
+ transform: 'translateY(-50%)',
1331
+ background: 'rgba(0,0,0,0.5)',
1332
+ color: 'white',
1333
+ border: 'none',
1334
+ borderRadius: '50%',
1335
+ width: '40px',
1336
+ height: '40px',
1337
+ cursor: carouselIndex >= maxCarouselIndex ? 'not-allowed' : 'pointer',
1338
+ opacity: carouselIndex >= maxCarouselIndex ? 0.5 : 1,
1339
+ display: 'flex',
1340
+ alignItems: 'center',
1341
+ justifyContent: 'center',
1342
+ fontSize: '18px',
1343
+ zIndex: 10,
1344
+ }}
1345
+ >
1346
+
1347
+ </button>
1348
+
1349
+ {/* Dots Indicator */}
1350
+ <div
1351
+ style={{
1352
+ position: 'absolute',
1353
+ bottom: '10px',
1354
+ left: '50%',
1355
+ transform: 'translateX(-50%)',
1356
+ display: 'flex',
1357
+ gap: '8px',
1358
+ zIndex: 10,
1359
+ }}
1360
+ >
1361
+ {Array.from({ length: maxCarouselIndex + 1 }, (_, index) => (
1362
+ <button
1363
+ key={index}
1364
+ onClick={() => goToCarouselItem(index)}
1365
+ style={{
1366
+ width: index === carouselIndex ? '12px' : '8px',
1367
+ height: index === carouselIndex ? '12px' : '8px',
1368
+ borderRadius: '50%',
1369
+ border: 'none',
1370
+ background: index === carouselIndex ? 'white' : 'rgba(255,255,255,0.5)',
1371
+ cursor: 'pointer',
1372
+ transition: 'all 0.3s ease',
1373
+ }}
1374
+ />
1375
+ ))}
1376
+ </div>
1377
+ </>
1378
+ )}
1066
1379
  </div>
1067
- ))}
1068
- </div>
1069
- );
1070
- }
1071
- break;
1380
+ );
1381
+ }
1382
+ break;
1072
1383
 
1073
1384
  default:
1074
1385
  return (