tee3apps-cms-sdk-react 0.0.20 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/index.js +3 -3
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +3 -3
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/src/Page.tsx +2 -2
  7. package/src/PageComponents/BoxComponent.tsx +123 -25
  8. package/src/PageComponents/Visual-Components/CarouselComponent.tsx +300 -167
  9. package/src/PageComponents/Visual-Components/GroupBrandComponent.tsx +396 -390
  10. package/src/PageComponents/Visual-Components/GroupCategoryComponent.tsx +21 -13
  11. package/src/PageComponents/Visual-Components/GroupImageList.tsx +642 -668
  12. package/src/PageComponents/Visual-Components/GroupProductComponent.tsx +46 -20
  13. package/src/PageComponents/Visual-Components/GroupVideoList.tsx +693 -589
  14. package/src/PageComponents/Visual-Components/ImageComponent.tsx +74 -18
  15. package/src/PageComponents/Visual-Components/LottieComponent.tsx +14 -8
  16. package/src/PageComponents/Visual-Components/NavigationComponent.tsx +74 -27
  17. package/src/PageComponents/Visual-Components/RichTextComponent.tsx +1 -1
  18. package/src/PageComponents/Visual-Components/TabComponent.tsx +1624 -876
  19. package/src/PageComponents/Visual-Components/TextComponent.tsx +24 -10
  20. package/src/PageComponents/Visual-Components/VideoComponent.tsx +33 -11
  21. package/src/PageComponents/Visual-Components/tab.css +645 -611
  22. package/src/index.css +126 -81
  23. package/src/Components/BoxRenderer.tsx +0 -108
  24. package/src/Components/ComponentRenderer.tsx +0 -29
  25. package/src/Components/ImageComponent.tsx +0 -68
  26. package/src/Components/RowComponent.tsx +0 -66
  27. package/src/Components/TextComponent.tsx +0 -47
@@ -1,590 +1,694 @@
1
- import React, { useState } from 'react';
2
- import { Linodeurl } from '../../const';
3
-
4
- interface VideoItem {
5
- id: number;
6
- attr: {
7
- playerType: 'Video' | 'Youtube' | 'Vimeo' | 'Dailymotion';
8
- controls?: boolean;
9
- loop?: boolean;
10
- alt?: string;
11
- url: string;
12
- autoplay?: boolean;
13
- muted?: boolean;
14
- };
15
- title: string;
16
- subTitle: string;
17
- }
18
-
19
- interface GroupVideoStatic {
20
- _id: string;
21
- name: string;
22
- code: string;
23
- }
24
-
25
- interface GroupVideoDynamic {
26
- code: string;
27
- name: { all: string };
28
- list: VideoItem[];
29
- isActive?: boolean;
30
- }
31
-
32
- interface GroupVideoData {
33
- static: GroupVideoStatic;
34
- dynamic: GroupVideoDynamic;
35
- groupvideodata: VideoItem[];
36
- groupimagedata: any[];
37
- }
38
-
39
- interface HeaderTextStyle {
40
- fontSize: number;
41
- fontColor: string;
42
- isBold: boolean;
43
- isItalic: boolean;
44
- isUnderline: boolean;
45
- }
46
-
47
- interface DeviceBooleanProps {
48
- web: boolean;
49
- mobileweb: boolean;
50
- mobileapp: boolean;
51
- tablet: boolean;
52
- }
53
-
54
- interface DeviceLayoutProps {
55
- web: string;
56
- mobileweb: string;
57
- mobileapp: string;
58
- tablet: string;
59
- }
60
-
61
- export interface GroupVideoListProps {
62
- nameData: string;
63
- type: string;
64
- groupvideo: GroupVideoData;
65
- headerText: string;
66
- activeStatus: boolean;
67
- headerImage: string;
68
- headerTextStyle: HeaderTextStyle;
69
- layout: DeviceLayoutProps;
70
- showHeader: DeviceBooleanProps;
71
- isHorizontalScroll: DeviceBooleanProps;
72
- headerBackground: string;
73
- carouselBackground: string;
74
- cardcolor: string;
75
- showSubtitle?: boolean;
76
- showTitle?: boolean;
77
- titleStyle?: {
78
- fontSize: number;
79
- fontColor: string;
80
- isBold: boolean;
81
- isItalic: boolean;
82
- isUnderline: boolean;
83
- };
84
- subtitleStyle?: {
85
- fontSize: number;
86
- fontColor: string;
87
- isBold: boolean;
88
- isItalic: boolean;
89
- isUnderline: boolean;
90
- };
91
- }
92
-
93
- interface GroupVideoListMainProps {
94
- props: GroupVideoListProps;
95
- deviceMode?: string;
96
- boxHeight?: string;
97
- }
98
-
99
- // Helper functions for extracting video IDs
100
- const extractYouTubeId = (url: string): string | null => {
101
- try {
102
- const parsed = new URL(url);
103
- if (parsed.hostname.includes('youtu.be')) {
104
- return parsed.pathname.replace('/', '') || null;
105
- }
106
- if (parsed.hostname.includes('youtube.com')) {
107
- if (parsed.pathname.startsWith('/watch')) {
108
- return parsed.searchParams.get('v');
109
- }
110
- if (parsed.pathname.startsWith('/embed/')) {
111
- return parsed.pathname.split('/embed/')[1] || null;
112
- }
113
- }
114
- } catch {}
115
- return null;
116
- };
117
-
118
- const extractVimeoId = (url: string): string | null => {
119
- try {
120
- const parsed = new URL(url);
121
- if (parsed.hostname.includes('vimeo.com')) {
122
- const parts = parsed.pathname.split('/').filter(Boolean);
123
- if (parts[0] === 'video') return parts[1] || null;
124
- return parts[0] || null;
125
- }
126
- if (parsed.hostname.includes('player.vimeo.com')) {
127
- const parts = parsed.pathname.split('/').filter(Boolean);
128
- return parts[1] || null;
129
- }
130
- } catch {}
131
- return null;
132
- };
133
-
134
- const extractDailymotionId = (url: string): string | null => {
135
- try {
136
- const parsed = new URL(url);
137
- if (parsed.hostname.includes('dailymotion.com')) {
138
- const parts = parsed.pathname.split('/').filter(Boolean);
139
- if (parts[0] === 'video') return parts[1] || null;
140
- }
141
- if (parsed.hostname.includes('dai.ly')) {
142
- return parsed.pathname.replace('/', '') || null;
143
- }
144
- } catch {}
145
- return null;
146
- };
147
-
148
- // Video Component
149
- interface VideoComponentProps {
150
- videoData: {
151
- url: string;
152
- type: 'Youtube' | 'Vimeo' | 'Dailymotion' | 'Video';
153
- controls?: boolean;
154
- loop?: boolean;
155
- alt?: string;
156
- autoplay?: boolean;
157
- muted?: boolean;
158
- };
159
- borderRadius?: number;
160
- height?: string;
161
- }
162
-
163
- const VideoComponent: React.FC<VideoComponentProps> = ({
164
- videoData,
165
- borderRadius = 6,
166
- height = '100%'
167
- }) => {
168
- const renderPlayer = () => {
169
- const commonStyle: React.CSSProperties = {
170
- width: '100%',
171
- height: '100%',
172
- border: 0,
173
- borderRadius: `${borderRadius}px`,
174
- };
175
-
176
- if (videoData.type === 'Youtube') {
177
- const id = extractYouTubeId(videoData.url);
178
- const src = id ? `https://www.youtube.com/embed/${id}` : videoData.url;
179
- return (
180
- <iframe
181
- title={videoData.alt || 'YouTube video'}
182
- src={src}
183
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
184
- allowFullScreen
185
- style={commonStyle}
186
- />
187
- );
188
- }
189
-
190
- if (videoData.type === 'Vimeo') {
191
- const id = extractVimeoId(videoData.url);
192
- const src = id ? `https://player.vimeo.com/video/${id}` : videoData.url;
193
- return (
194
- <iframe
195
- title={videoData.alt || 'Vimeo video'}
196
- src={src}
197
- allow="autoplay; fullscreen; picture-in-picture"
198
- allowFullScreen
199
- style={commonStyle}
200
- />
201
- );
202
- }
203
-
204
- if (videoData.type === 'Dailymotion') {
205
- const id = extractDailymotionId(videoData.url);
206
- const src = id ? `https://www.dailymotion.com/embed/video/${id}` : videoData.url;
207
- return (
208
- <iframe
209
- title={videoData.alt || 'Dailymotion video'}
210
- src={src}
211
- allow="autoplay; fullscreen; picture-in-picture"
212
- allowFullScreen
213
- style={commonStyle}
214
- />
215
- );
216
- }
217
-
218
- // Default: normal video
219
- return (
220
- <video
221
- autoPlay={videoData.autoplay !== false}
222
- muted={videoData.muted !== false}
223
- playsInline
224
- controls={videoData.controls !== false}
225
- loop={videoData.loop === true}
226
- style={{ width: '100%', height: '100%', borderRadius: `${borderRadius}px` }}
227
- >
228
- <source src={`${Linodeurl}${videoData.url}`} />
229
- Your browser does not support the video tag.
230
- </video>
231
- );
232
- };
233
-
234
- return (
235
- <div
236
- style={{
237
- borderRadius: `${borderRadius}px`,
238
- height: height,
239
- overflow: 'hidden'
240
- }}
241
- >
242
- {renderPlayer()}
243
- </div>
244
- );
245
- };
246
-
247
- // Main GroupVideoList Component
248
- const GroupVideoList: React.FC<GroupVideoListMainProps> = ({ props, deviceMode = 'web', boxHeight }) => {
249
- const getCurrentBooleanProp = (prop?: DeviceBooleanProps) => {
250
- if (!prop) return false;
251
- switch (deviceMode) {
252
- case 'mobileweb': return !!prop.mobileweb;
253
- case 'mobileapp': return !!prop.mobileapp;
254
- case 'tablet': return !!prop.tablet;
255
- case 'web':
256
- default: return !!prop.web;
257
- }
258
- };
259
-
260
- const getCurrentLayout = () => {
261
- if (!props.layout) return 'NONE';
262
- switch (deviceMode) {
263
- case 'mobileweb': return props.layout.mobileweb || 'NONE';
264
- case 'mobileapp': return props.layout.mobileapp || 'NONE';
265
- case 'tablet': return props.layout.tablet || 'NONE';
266
- case 'web':
267
- default: return props.layout.web || 'NONE';
268
- }
269
- };
270
-
271
- const showHeader = getCurrentBooleanProp(props.showHeader);
272
- const isHorizontalScroll = getCurrentBooleanProp(props.isHorizontalScroll);
273
- const currentLayout = getCurrentLayout();
274
-
275
- const getVideos = (): VideoItem[] => {
276
- if (props.type === 'dynamic') {
277
- if (props.groupvideo?.dynamic?.list?.length) return props.groupvideo.dynamic.list;
278
- }
279
- if (props.groupvideo?.groupvideodata?.length) return props.groupvideo.groupvideodata;
280
- return [];
281
- };
282
-
283
- const videos = getVideos();
284
-
285
- // Carousel navigation buttons (layout NONE)
286
- const scrollLeft = () => {
287
- const scrollableContainer = document.querySelector('.scrollable-container');
288
- if (scrollableContainer) {
289
- (scrollableContainer as HTMLElement).scrollBy({ left: -200, behavior: 'smooth' });
290
- }
291
- };
292
-
293
- const scrollRight = () => {
294
- const scrollableContainer = document.querySelector('.scrollable-container');
295
- if (scrollableContainer) {
296
- (scrollableContainer as HTMLElement).scrollBy({ left: 200, behavior: 'smooth' });
297
- }
298
- };
299
-
300
- const VideoCard = ({ item, large = false }: { item: VideoItem; large?: boolean }) => {
301
- const [isHovered, setIsHovered] = useState(false);
302
-
303
- return (
304
- <div
305
- className="videoCard"
306
- style={{
307
- height: 'auto',
308
- borderRadius: '8px',
309
- overflow: 'hidden',
310
- border: '1px solid #eee',
311
- backgroundColor: props.cardcolor || '#fff',
312
- display: 'flex',
313
- flexDirection: 'column',
314
- padding: large ? '16px' : '12px',
315
- boxShadow: isHovered ? '0 4px 8px rgba(0,0,0,0.15)' : '0 2px 4px rgba(0,0,0,0.1)',
316
- transition: 'all 0.2s ease',
317
- cursor: 'pointer',
318
- transform: isHovered ? 'translateY(-2px)' : 'translateY(0px)',
319
- flex: 1
320
- }}
321
- onMouseOver={() => setIsHovered(true)}
322
- onMouseOut={() => setIsHovered(false)}
323
- >
324
- <div style={{ width: '100%', marginBottom: large ? '12px' : '8px' }}>
325
- <VideoComponent
326
- videoData={{
327
- url: item.attr.url,
328
- type: item.attr.playerType,
329
- controls: item.attr.controls,
330
- loop: item.attr.loop,
331
- alt: item.attr.alt,
332
- autoplay: item.attr.autoplay !== false,
333
- muted: item.attr.muted !== false
334
- }}
335
- borderRadius={6}
336
- height={`${parseInt(boxHeight || '280') - 160}px`}
337
- />
338
- </div>
339
- {(props?.showTitle !== false) && item.title && (
340
- <div style={{
341
- textAlign: 'center',
342
- fontSize: props.titleStyle ? `${props.titleStyle.fontSize}px` : (large ? '16px' : '14px'),
343
- fontWeight: props.titleStyle?.isBold ? 'bold' : (large ? 600 : 500),
344
- color: props.titleStyle?.fontColor || '#333',
345
- fontStyle: props.titleStyle?.isItalic ? 'italic' : 'normal',
346
- textDecoration: props.titleStyle?.isUnderline ? 'underline' : 'none',
347
- lineHeight: '1.4',
348
- marginBottom: '4px'
349
- }}>
350
- {item.title}
351
- </div>
352
- )}
353
- {(props?.showSubtitle !== false) && item.subTitle && (
354
- <div style={{
355
- textAlign: 'center',
356
- fontSize: props.subtitleStyle ? `${props.subtitleStyle.fontSize}px` : (large ? '14px' : '12px'),
357
- fontWeight: props.subtitleStyle?.isBold ? 'bold' : 400,
358
- color: props.subtitleStyle?.fontColor || '#666',
359
- fontStyle: props.subtitleStyle?.isItalic ? 'italic' : 'normal',
360
- textDecoration: props.subtitleStyle?.isUnderline ? 'underline' : 'none',
361
- lineHeight: '1.3'
362
- }}>
363
- {item.subTitle}
364
- </div>
365
- )}
366
- </div>
367
- );
368
- };
369
-
370
- return (
371
- <div
372
- style={{
373
- border: '1px solid #e1e1e1',
374
- width: '100%',
375
- maxWidth: '100%',
376
- borderRadius: '0px',
377
- height: boxHeight || 'auto',
378
- minHeight: boxHeight ? boxHeight : '100px',
379
- position: 'relative',
380
- overflow: 'hidden',
381
- marginBottom: '20px',
382
- marginTop: '0px',
383
- display: 'flex',
384
- flexDirection: 'column'
385
- }}
386
- className='GroupVideoListComponent'
387
- >
388
- {showHeader && (
389
- <div
390
- className="groupVideoHeader"
391
- style={{
392
- backgroundColor: props.headerBackground,
393
- padding: '12px 16px',
394
- borderRadius: '0px',
395
- marginBottom: '4px',
396
- display: 'flex',
397
- alignItems: 'center',
398
- justifyContent: 'space-between',
399
- flexWrap: 'wrap',
400
- gap: '12px',
401
- }}
402
- >
403
- <p style={{
404
- color: props.headerTextStyle.fontColor,
405
- fontSize: `${props.headerTextStyle.fontSize}px`,
406
- fontWeight: props.headerTextStyle.isBold ? 'bold' : 'normal',
407
- fontStyle: props.headerTextStyle.isItalic ? 'italic' : 'normal',
408
- textDecoration: props.headerTextStyle.isUnderline ? 'underline' : 'none',
409
- margin: 0
410
- }}>
411
- {props.headerText || 'Video List'}
412
- </p>
413
-
414
- {currentLayout === 'NONE' && videos.length > 2 && (
415
- <div style={{ display: 'flex', gap: '8px' }}>
416
- <button
417
- onClick={scrollLeft}
418
- style={{
419
- width: '32px',
420
- height: '32px',
421
- borderRadius: '50%',
422
- border: '1px solid #ddd',
423
- backgroundColor: '#fff',
424
- cursor: 'pointer',
425
- display: 'flex',
426
- alignItems: 'center',
427
- justifyContent: 'center',
428
- fontSize: '16px',
429
- fontWeight: 'bold'
430
- }}
431
- onMouseOver={(e) => { e.currentTarget.style.backgroundColor = '#f8f9fa'; }}
432
- onMouseOut={(e) => { e.currentTarget.style.backgroundColor = '#fff'; }}
433
- >
434
-
435
- </button>
436
- <button
437
- onClick={scrollRight}
438
- style={{
439
- width: '32px',
440
- height: '32px',
441
- borderRadius: '50%',
442
- border: '1px solid #ddd',
443
- backgroundColor: '#fff',
444
- cursor: 'pointer',
445
- display: 'flex',
446
- alignItems: 'center',
447
- justifyContent: 'center',
448
- fontSize: '16px',
449
- fontWeight: 'bold'
450
- }}
451
- onMouseOver={(e) => { e.currentTarget.style.backgroundColor = '#f8f9fa'; }}
452
- onMouseOut={(e) => { e.currentTarget.style.backgroundColor = '#fff'; }}
453
- >
454
-
455
- </button>
456
- </div>
457
- )}
458
- </div>
459
- )}
460
-
461
- <div
462
- style={{
463
- display: currentLayout === 'NONE' ? 'flex' : 'block',
464
- overflowX: currentLayout === 'NONE' ? (isHorizontalScroll ? 'auto' : 'hidden') : 'visible',
465
- gap: currentLayout === 'NONE' ? '12px' : '0',
466
- padding: '12px',
467
- scrollBehavior: 'smooth',
468
- backgroundColor: props.carouselBackground || '#fff',
469
- scrollbarWidth: 'thin',
470
- borderRadius: '8px',
471
- position: 'relative',
472
- scrollbarColor: '#c1c1c1 transparent',
473
- flex: 1,
474
- overflow: 'hidden'
475
- }}
476
- className="groupVideoCarousel"
477
- >
478
- {/* Navigation buttons - positioned like carousel */}
479
- {!showHeader && currentLayout === 'NONE' && videos.length > 2 && (
480
- <>
481
- <button
482
- onClick={scrollLeft}
483
- style={{
484
- position: 'absolute',
485
- left: '10px',
486
- top: '50%',
487
- transform: 'translateY(-50%)',
488
- background: 'rgba(0,0,0,0.5)',
489
- color: 'white',
490
- border: 'none',
491
- borderRadius: '50%',
492
- width: '40px',
493
- height: '40px',
494
- cursor: 'pointer',
495
- display: 'flex',
496
- alignItems: 'center',
497
- justifyContent: 'center',
498
- fontSize: '18px',
499
- zIndex: 10,
500
- transition: 'all 0.2s ease'
501
- }}
502
- onMouseOver={(e) => {
503
- e.currentTarget.style.background = 'rgba(0,0,0,0.7)';
504
- e.currentTarget.style.transform = 'translateY(-50%) scale(1.1)';
505
- }}
506
- onMouseOut={(e) => {
507
- e.currentTarget.style.background = 'rgba(0,0,0,0.5)';
508
- e.currentTarget.style.transform = 'translateY(-50%) scale(1)';
509
- }}
510
- >
511
-
512
- </button>
513
- <button
514
- onClick={scrollRight}
515
- style={{
516
- position: 'absolute',
517
- right: '10px',
518
- top: '50%',
519
- transform: 'translateY(-50%)',
520
- background: 'rgba(0,0,0,0.5)',
521
- color: 'white',
522
- border: 'none',
523
- borderRadius: '50%',
524
- width: '40px',
525
- height: '40px',
526
- cursor: 'pointer',
527
- display: 'flex',
528
- alignItems: 'center',
529
- justifyContent: 'center',
530
- fontSize: '18px',
531
- zIndex: 10,
532
- transition: 'all 0.2s ease'
533
- }}
534
- onMouseOver={(e) => {
535
- e.currentTarget.style.background = 'rgba(0,0,0,0.7)';
536
- e.currentTarget.style.transform = 'translateY(-50%) scale(1.1)';
537
- }}
538
- onMouseOut={(e) => {
539
- e.currentTarget.style.background = 'rgba(0,0,0,0.5)';
540
- e.currentTarget.style.transform = 'translateY(-50%) scale(1)';
541
- }}
542
- >
543
-
544
- </button>
545
- </>
546
- )}
547
- {videos.length > 0 ? (
548
- currentLayout === 'NONE' ? (
549
- videos.map((video) => (
550
- <div key={video.id} style={{ minWidth: '200px',height: 'auto' }}>
551
- <VideoCard item={video} />
552
- </div>
553
- ))
554
- ) : currentLayout === 'SMALL' ? (
555
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '16px', padding: '8px' }}>
556
- {videos.map((video) => (
557
- <VideoCard key={video.id} item={video} />
558
- ))}
559
- </div>
560
- ) : currentLayout === 'MEDIUM' ? (
561
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '16px', padding: '8px' }}>
562
- {videos.map((video) => (
563
- <VideoCard key={video.id} item={video} />
564
- ))}
565
- </div>
566
- ) : currentLayout === 'MEDIUM_THREE' ? (
567
- <div style={{ display: 'flex', flexDirection: 'column', gap: '16px', padding: '8px' }}>
568
- {videos.length > 0 && (
569
- <VideoCard item={videos[0]} large />
570
- )}
571
- {videos.length > 1 && (
572
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '16px' }}>
573
- {videos.slice(1).map((video) => (
574
- <VideoCard key={video.id} item={video} />
575
- ))}
576
- </div>
577
- )}
578
- </div>
579
- ) : null
580
- ) : (
581
- <div style={{ textAlign: 'center', color: '#666', padding: '40px', width: '100%', border: '2px dashed #ddd', borderRadius: '8px', backgroundColor: '#f9f9f9' }}>
582
- <p>No videos available.</p>
583
- </div>
584
- )}
585
- </div>
586
- </div>
587
- );
588
- };
589
-
1
+ import React, { useState } from 'react';
2
+ import { Linodeurl } from '../../const';
3
+
4
+ interface VideoItem {
5
+ id: number;
6
+ attr: {
7
+ playerType: 'Video' | 'Youtube' | 'Vimeo' | 'Dailymotion';
8
+ controls?: boolean;
9
+ loop?: boolean;
10
+ alt?: string;
11
+ url: string;
12
+ autoplay?: boolean;
13
+ muted?: boolean;
14
+ };
15
+ title: string;
16
+ subTitle: string;
17
+ }
18
+
19
+ interface GroupVideoStatic {
20
+ _id: string;
21
+ name: string;
22
+ code: string;
23
+ }
24
+
25
+ interface GroupVideoDynamic {
26
+ code: string;
27
+ name: { all: string };
28
+ list: VideoItem[];
29
+ isActive?: boolean;
30
+ }
31
+
32
+ interface GroupVideoData {
33
+ static: GroupVideoStatic;
34
+ dynamic: GroupVideoDynamic;
35
+ groupvideodata: VideoItem[];
36
+ groupimagedata: any[];
37
+ }
38
+
39
+ interface HeaderTextStyle {
40
+ fontSize: number;
41
+ fontColor: string;
42
+ isBold: boolean;
43
+ isItalic: boolean;
44
+ isUnderline: boolean;
45
+ }
46
+
47
+ interface DeviceBooleanProps {
48
+ web: boolean;
49
+ mobileweb: boolean;
50
+ mobileapp: boolean;
51
+ tablet: boolean;
52
+ }
53
+
54
+ interface DeviceLayoutProps {
55
+ web: string;
56
+ mobileweb: string;
57
+ mobileapp: string;
58
+ tablet: string;
59
+ }
60
+
61
+ export interface GroupVideoListProps {
62
+ nameData: string;
63
+ type: string;
64
+ groupvideo: GroupVideoData;
65
+ headerText: string;
66
+ activeStatus: boolean;
67
+ headerImage: string;
68
+ headerTextStyle: HeaderTextStyle;
69
+ layout: DeviceLayoutProps;
70
+ showHeader: DeviceBooleanProps;
71
+ isHorizontalScroll: DeviceBooleanProps;
72
+ headerBackground: string;
73
+ carouselBackground: string;
74
+ cardcolor: string;
75
+ showSubtitle?: boolean | DeviceBooleanProps;
76
+ showTitle?: boolean | DeviceBooleanProps;
77
+ titleStyle?: {
78
+ fontSize: number;
79
+ fontColor: string;
80
+ isBold: boolean;
81
+ isItalic: boolean;
82
+ isUnderline: boolean;
83
+ };
84
+ subtitleStyle?: {
85
+ fontSize: number;
86
+ fontColor: string;
87
+ isBold: boolean;
88
+ isItalic: boolean;
89
+ isUnderline: boolean;
90
+ };
91
+ }
92
+
93
+ interface GroupVideoListMainProps {
94
+ props: GroupVideoListProps;
95
+ deviceMode?: string;
96
+ boxHeight?: string;
97
+ }
98
+
99
+ // Helper functions for extracting video IDs
100
+ const extractYouTubeId = (url: string): string | null => {
101
+ try {
102
+ const parsed = new URL(url);
103
+ if (parsed.hostname.includes('youtu.be')) {
104
+ return parsed.pathname.replace('/', '') || null;
105
+ }
106
+ if (parsed.hostname.includes('youtube.com')) {
107
+ if (parsed.pathname.startsWith('/watch')) {
108
+ return parsed.searchParams.get('v');
109
+ }
110
+ if (parsed.pathname.startsWith('/embed/')) {
111
+ return parsed.pathname.split('/embed/')[1] || null;
112
+ }
113
+ }
114
+ } catch {}
115
+ return null;
116
+ };
117
+
118
+ const extractVimeoId = (url: string): string | null => {
119
+ try {
120
+ const parsed = new URL(url);
121
+ if (parsed.hostname.includes('vimeo.com')) {
122
+ const parts = parsed.pathname.split('/').filter(Boolean);
123
+ if (parts[0] === 'video') return parts[1] || null;
124
+ return parts[0] || null;
125
+ }
126
+ if (parsed.hostname.includes('player.vimeo.com')) {
127
+ const parts = parsed.pathname.split('/').filter(Boolean);
128
+ return parts[1] || null;
129
+ }
130
+ } catch {}
131
+ return null;
132
+ };
133
+
134
+ const extractDailymotionId = (url: string): string | null => {
135
+ try {
136
+ const parsed = new URL(url);
137
+ if (parsed.hostname.includes('dailymotion.com')) {
138
+ const parts = parsed.pathname.split('/').filter(Boolean);
139
+ if (parts[0] === 'video') return parts[1] || null;
140
+ }
141
+ if (parsed.hostname.includes('dai.ly')) {
142
+ return parsed.pathname.replace('/', '') || null;
143
+ }
144
+ } catch {}
145
+ return null;
146
+ };
147
+
148
+ // Video Component
149
+ interface VideoComponentProps {
150
+ videoData: {
151
+ url: string;
152
+ type: 'Youtube' | 'Vimeo' | 'Dailymotion' | 'Video';
153
+ controls?: boolean;
154
+ loop?: boolean;
155
+ alt?: string;
156
+ autoplay?: boolean;
157
+ muted?: boolean;
158
+ };
159
+ borderRadius?: number;
160
+ height?: string;
161
+ }
162
+
163
+ const VideoComponent: React.FC<VideoComponentProps> = ({
164
+ videoData,
165
+ borderRadius = 6,
166
+ height = '100%'
167
+ }) => {
168
+ const renderPlayer = () => {
169
+ const commonStyle: React.CSSProperties = {
170
+ width: '100%',
171
+ height: '100%',
172
+ border: 0,
173
+ borderRadius: `${borderRadius}px`,
174
+ };
175
+
176
+ if (videoData.type === 'Youtube') {
177
+ const id = extractYouTubeId(videoData.url);
178
+ const src = id ? `https://www.youtube.com/embed/${id}` : videoData.url;
179
+ return (
180
+ <iframe
181
+ title={videoData.alt || 'YouTube video'}
182
+ src={src}
183
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
184
+ allowFullScreen
185
+ style={commonStyle}
186
+ />
187
+ );
188
+ }
189
+
190
+ if (videoData.type === 'Vimeo') {
191
+ const id = extractVimeoId(videoData.url);
192
+ const src = id ? `https://player.vimeo.com/video/${id}` : videoData.url;
193
+ return (
194
+ <iframe
195
+ title={videoData.alt || 'Vimeo video'}
196
+ src={src}
197
+ allow="autoplay; fullscreen; picture-in-picture"
198
+ allowFullScreen
199
+ style={commonStyle}
200
+ />
201
+ );
202
+ }
203
+
204
+ if (videoData.type === 'Dailymotion') {
205
+ const id = extractDailymotionId(videoData.url);
206
+ const src = id ? `https://www.dailymotion.com/embed/video/${id}` : videoData.url;
207
+ return (
208
+ <iframe
209
+ title={videoData.alt || 'Dailymotion video'}
210
+ src={src}
211
+ allow="autoplay; fullscreen; picture-in-picture"
212
+ allowFullScreen
213
+ style={commonStyle}
214
+ />
215
+ );
216
+ }
217
+
218
+ // Default: normal video
219
+ return (
220
+ <video
221
+ autoPlay={videoData.autoplay !== false}
222
+ muted={videoData.muted !== false}
223
+ playsInline
224
+ controls={videoData.controls !== false}
225
+ loop={videoData.loop === true}
226
+ style={{ width: '100%', height: '100%', borderRadius: `${borderRadius}px` }}
227
+ >
228
+ <source src={`${Linodeurl}${videoData.url}`} />
229
+ Your browser does not support the video tag.
230
+ </video>
231
+ );
232
+ };
233
+
234
+ return (
235
+ <div
236
+ style={{
237
+ borderRadius: `${borderRadius}px`,
238
+ height: height,
239
+ overflow: 'hidden'
240
+ }}
241
+ >
242
+ {renderPlayer()}
243
+ </div>
244
+ );
245
+ };
246
+
247
+ // Main GroupVideoList Component
248
+ const GroupVideoList: React.FC<GroupVideoListMainProps> = ({ props, deviceMode = 'web', boxHeight }) => {
249
+ const getCurrentBooleanProp = (prop?: DeviceBooleanProps) => {
250
+ if (!prop) return false;
251
+ switch (deviceMode) {
252
+ case 'mobileweb': return !!prop.mobileweb;
253
+ case 'mobileapp': return !!prop.mobileapp;
254
+ case 'tablet': return !!prop.tablet;
255
+ case 'web':
256
+ default: return !!prop.web;
257
+ }
258
+ };
259
+
260
+ const getCurrentLayout = () => {
261
+ if (!props.layout) return 'NONE';
262
+ switch (deviceMode) {
263
+ case 'mobileweb': return props.layout.mobileweb || 'NONE';
264
+ case 'mobileapp': return props.layout.mobileapp || 'NONE';
265
+ case 'tablet': return props.layout.tablet || 'NONE';
266
+ case 'web':
267
+ default: return props.layout.web || 'NONE';
268
+ }
269
+ };
270
+
271
+ const showHeader = getCurrentBooleanProp(props.showHeader);
272
+ const isHorizontalScroll = getCurrentBooleanProp(props.isHorizontalScroll);
273
+ const currentLayout = getCurrentLayout();
274
+
275
+ // Handle showTitle and showSubtitle - can be boolean or DeviceBooleanProps
276
+ const showTitle = typeof props.showTitle === 'boolean'
277
+ ? props.showTitle
278
+ : getCurrentBooleanProp(props.showTitle as DeviceBooleanProps | undefined);
279
+ const showSubtitle = typeof props.showSubtitle === 'boolean'
280
+ ? props.showSubtitle
281
+ : getCurrentBooleanProp(props.showSubtitle as DeviceBooleanProps | undefined);
282
+
283
+ const getVideos = (): VideoItem[] => {
284
+ if (props.type === 'dynamic') {
285
+ if (props.groupvideo?.dynamic?.list?.length) return props.groupvideo.dynamic.list;
286
+ }
287
+ if (props.groupvideo?.groupvideodata?.length) return props.groupvideo.groupvideodata;
288
+ return [];
289
+ };
290
+
291
+ const videos = getVideos();
292
+
293
+ // Calculate consistent dimensions for all video cards
294
+ const calculateCardDimensions = (large = false) => {
295
+ // Fixed heights for title and subtitle
296
+ const titleHeight = showTitle ? 24 : 0;
297
+ const subtitleHeight = showSubtitle ? 20 : 0;
298
+ const spacing = (showTitle ? 8 : 0) + (showSubtitle && showTitle ? 4 : (showSubtitle ? 8 : 0));
299
+ const cardPadding = large ? 32 : 24; // 16px top + 16px bottom or 12px top + 12px bottom
300
+
301
+ if (!boxHeight) {
302
+ // When no boxHeight, use a default video height
303
+ const defaultVideoHeight = 200;
304
+ const defaultCardHeight = defaultVideoHeight + cardPadding + titleHeight + subtitleHeight + spacing;
305
+ return {
306
+ cardHeight: `${defaultCardHeight}px`,
307
+ videoHeight: `${defaultVideoHeight}px`,
308
+ titleHeight: showTitle ? `${titleHeight}px` : '0px',
309
+ subtitleHeight: showSubtitle ? `${subtitleHeight}px` : '0px',
310
+ titleMarginBottom: showTitle && showSubtitle ? '4px' : '0px',
311
+ videoMarginBottom: showTitle || showSubtitle ? '8px' : '0px'
312
+ };
313
+ }
314
+
315
+ const totalHeight = parseInt(boxHeight);
316
+ const headerHeight = showHeader ? 60 : 0;
317
+ const containerPadding = 24; // 12px top + 12px bottom
318
+
319
+ // Calculate available height for video
320
+ const availableHeight = totalHeight - headerHeight - containerPadding - cardPadding - titleHeight - subtitleHeight - spacing;
321
+ const videoHeight = Math.max(availableHeight, 150);
322
+
323
+ // Card height should match the container height minus header and container padding
324
+ const cardHeight = totalHeight - headerHeight - containerPadding;
325
+
326
+ return {
327
+ cardHeight: `${cardHeight}px`,
328
+ videoHeight: `${videoHeight}px`,
329
+ titleHeight: showTitle ? `${titleHeight}px` : '0px',
330
+ subtitleHeight: showSubtitle ? `${subtitleHeight}px` : '0px',
331
+ titleMarginBottom: showTitle && showSubtitle ? '4px' : '0px',
332
+ videoMarginBottom: showTitle || showSubtitle ? '8px' : '0px'
333
+ };
334
+ };
335
+
336
+ // Carousel navigation buttons (layout NONE)
337
+ const scrollLeft = () => {
338
+ const scrollableContainer = document.querySelector('.groupVideoCarousel');
339
+ if (scrollableContainer) {
340
+ (scrollableContainer as HTMLElement).scrollBy({ left: -300, behavior: 'smooth' });
341
+ }
342
+ };
343
+
344
+ const scrollRight = () => {
345
+ const scrollableContainer = document.querySelector('.groupVideoCarousel');
346
+ if (scrollableContainer) {
347
+ (scrollableContainer as HTMLElement).scrollBy({ left: 300, behavior: 'smooth' });
348
+ }
349
+ };
350
+
351
+ const VideoCard = ({ item, large = false }: { item: VideoItem; large?: boolean }) => {
352
+ const [isHovered, setIsHovered] = useState(false);
353
+ const dimensions = calculateCardDimensions(large);
354
+
355
+ return (
356
+ <div
357
+ className="videoCard"
358
+ style={{
359
+ height: dimensions.cardHeight,
360
+ borderRadius: '8px',
361
+ overflow: 'hidden',
362
+ border: '1px solid #eee',
363
+ backgroundColor: props.cardcolor || '#fff',
364
+ display: 'flex',
365
+ flexDirection: 'column',
366
+ padding: large ? '16px' : '12px',
367
+ boxShadow: isHovered ? '0 4px 8px rgba(0,0,0,0.15)' : '0 2px 4px rgba(0,0,0,0.1)',
368
+ transition: 'all 0.2s ease',
369
+ cursor: 'pointer',
370
+ transform: isHovered ? 'translateY(-2px)' : 'translateY(0px)',
371
+ flex: currentLayout === 'NONE' ? '0 0 auto' : 1,
372
+ minWidth: currentLayout === 'NONE' ? '280px' : 'auto',
373
+ width: currentLayout === 'NONE' ? '280px' : '100%'
374
+ }}
375
+ onMouseOver={() => setIsHovered(true)}
376
+ onMouseOut={() => setIsHovered(false)}
377
+ >
378
+ <div
379
+ style={{
380
+ width: '100%',
381
+ height: dimensions.videoHeight,
382
+ minHeight: dimensions.videoHeight,
383
+ maxHeight: dimensions.videoHeight,
384
+ marginBottom: dimensions.videoMarginBottom,
385
+ flexShrink: 0,
386
+ overflow: 'hidden',
387
+ borderRadius: '6px'
388
+ }}
389
+ >
390
+ <VideoComponent
391
+ videoData={{
392
+ url: item.attr.url,
393
+ type: item.attr.playerType,
394
+ controls: item.attr.controls,
395
+ loop: item.attr.loop,
396
+ alt: item.attr.alt,
397
+ autoplay: item.attr.autoplay !== false,
398
+ muted: item.attr.muted !== false
399
+ }}
400
+ borderRadius={6}
401
+ height="100%"
402
+ />
403
+ </div>
404
+ {showTitle && (
405
+ <div style={{
406
+ textAlign: 'center',
407
+ fontSize: props.titleStyle ? `${props.titleStyle.fontSize}px` : (large ? '16px' : '14px'),
408
+ fontWeight: props.titleStyle?.isBold ? 'bold' : (large ? 600 : 500),
409
+ color: props.titleStyle?.fontColor || '#333',
410
+ fontStyle: props.titleStyle?.isItalic ? 'italic' : 'normal',
411
+ textDecoration: props.titleStyle?.isUnderline ? 'underline' : 'none',
412
+ lineHeight: '1.4',
413
+ height: dimensions.titleHeight,
414
+ minHeight: dimensions.titleHeight,
415
+ marginBottom: dimensions.titleMarginBottom,
416
+ overflow: 'hidden',
417
+ textOverflow: 'ellipsis',
418
+ whiteSpace: 'nowrap',
419
+ flexShrink: 0,
420
+ display: 'flex',
421
+ alignItems: 'center',
422
+ justifyContent: 'center'
423
+ }}>
424
+ {item.title || ''}
425
+ </div>
426
+ )}
427
+ {showSubtitle && (
428
+ <div style={{
429
+ textAlign: 'center',
430
+ fontSize: props.subtitleStyle ? `${props.subtitleStyle.fontSize}px` : (large ? '14px' : '12px'),
431
+ fontWeight: props.subtitleStyle?.isBold ? 'bold' : 400,
432
+ color: props.subtitleStyle?.fontColor || '#666',
433
+ fontStyle: props.subtitleStyle?.isItalic ? 'italic' : 'normal',
434
+ textDecoration: props.subtitleStyle?.isUnderline ? 'underline' : 'none',
435
+ lineHeight: '1.3',
436
+ height: dimensions.subtitleHeight,
437
+ minHeight: dimensions.subtitleHeight,
438
+ overflow: 'hidden',
439
+ textOverflow: 'ellipsis',
440
+ whiteSpace: 'nowrap',
441
+ flexShrink: 0,
442
+ display: 'flex',
443
+ alignItems: 'center',
444
+ justifyContent: 'center'
445
+ }}>
446
+ {item.subTitle || ''}
447
+ </div>
448
+ )}
449
+ </div>
450
+ );
451
+ };
452
+
453
+ return (
454
+ <>
455
+ <style>{`
456
+ .groupVideoCarousel::-webkit-scrollbar {
457
+ height: 8px;
458
+ width: 8px;
459
+ }
460
+ .groupVideoCarousel::-webkit-scrollbar-track {
461
+ background: transparent;
462
+ border-radius: 4px;
463
+ }
464
+ .groupVideoCarousel::-webkit-scrollbar-thumb {
465
+ background: #c1c1c1;
466
+ border-radius: 4px;
467
+ }
468
+ .groupVideoCarousel::-webkit-scrollbar-thumb:hover {
469
+ background: #a1a1a1;
470
+ }
471
+ `}</style>
472
+ <div
473
+ style={{
474
+ border: '1px solid #e1e1e1',
475
+ width: '100%',
476
+ maxWidth: '100%',
477
+ borderRadius: '0px',
478
+ height: boxHeight || 'auto',
479
+ minHeight: boxHeight ? boxHeight : '100px',
480
+ position: 'relative',
481
+ overflow: 'hidden',
482
+ marginBottom: '20px',
483
+ marginTop: '0px',
484
+ display: 'flex',
485
+ flexDirection: 'column'
486
+ }}
487
+ className='GroupVideoListComponent'
488
+ >
489
+ {showHeader && (
490
+ <div
491
+ className="groupVideoHeader"
492
+ style={{
493
+ backgroundColor: props.headerBackground,
494
+ padding: '12px 16px',
495
+ borderRadius: '0px',
496
+ marginBottom: '4px',
497
+ display: 'flex',
498
+ alignItems: 'center',
499
+ justifyContent: 'space-between',
500
+ flexWrap: 'wrap',
501
+ gap: '12px',
502
+ }}
503
+ >
504
+ <p style={{
505
+ color: props.headerTextStyle.fontColor,
506
+ fontSize: `${props.headerTextStyle.fontSize}px`,
507
+ fontWeight: props.headerTextStyle.isBold ? 'bold' : 'normal',
508
+ fontStyle: props.headerTextStyle.isItalic ? 'italic' : 'normal',
509
+ textDecoration: props.headerTextStyle.isUnderline ? 'underline' : 'none',
510
+ margin: 0
511
+ }}>
512
+ {props.headerText || 'Video List'}
513
+ </p>
514
+
515
+ {currentLayout === 'NONE' && isHorizontalScroll && videos.length > 1 && (
516
+ <div style={{ display: 'flex', gap: '8px' }}>
517
+ <button
518
+ onClick={scrollLeft}
519
+ style={{
520
+ width: '32px',
521
+ height: '32px',
522
+ borderRadius: '50%',
523
+ border: '1px solid #ddd',
524
+ backgroundColor: '#fff',
525
+ cursor: 'pointer',
526
+ display: 'flex',
527
+ alignItems: 'center',
528
+ justifyContent: 'center',
529
+ fontSize: '16px',
530
+ fontWeight: 'bold'
531
+ }}
532
+ onMouseOver={(e) => { e.currentTarget.style.backgroundColor = '#f8f9fa'; }}
533
+ onMouseOut={(e) => { e.currentTarget.style.backgroundColor = '#fff'; }}
534
+ >
535
+
536
+ </button>
537
+ <button
538
+ onClick={scrollRight}
539
+ style={{
540
+ width: '32px',
541
+ height: '32px',
542
+ borderRadius: '50%',
543
+ border: '1px solid #ddd',
544
+ backgroundColor: '#fff',
545
+ cursor: 'pointer',
546
+ display: 'flex',
547
+ alignItems: 'center',
548
+ justifyContent: 'center',
549
+ fontSize: '16px',
550
+ fontWeight: 'bold'
551
+ }}
552
+ onMouseOver={(e) => { e.currentTarget.style.backgroundColor = '#f8f9fa'; }}
553
+ onMouseOut={(e) => { e.currentTarget.style.backgroundColor = '#fff'; }}
554
+ >
555
+
556
+ </button>
557
+ </div>
558
+ )}
559
+ </div>
560
+ )}
561
+
562
+ <div
563
+ style={{
564
+ display: currentLayout === 'NONE' ? 'flex' : 'block',
565
+ overflowX: currentLayout === 'NONE' ? (isHorizontalScroll ? 'auto' : 'hidden') : 'visible',
566
+ overflowY: currentLayout === 'NONE' ? 'hidden' : (boxHeight ? 'auto' : 'visible'),
567
+ gap: currentLayout === 'NONE' ? '12px' : '0',
568
+ padding: '12px',
569
+ scrollBehavior: 'smooth',
570
+ backgroundColor: props.carouselBackground || '#fff',
571
+ scrollbarWidth: 'thin',
572
+ borderRadius: '8px',
573
+ position: 'relative',
574
+ scrollbarColor: '#c1c1c1 transparent',
575
+ flex: 1,
576
+ minHeight: 0,
577
+ height: boxHeight ? `calc(${boxHeight} - ${showHeader ? '60px' : '0px'})` : 'auto'
578
+ }}
579
+ className="groupVideoCarousel"
580
+ >
581
+ {/* Navigation buttons - positioned like carousel */}
582
+ {!showHeader && currentLayout === 'NONE' && isHorizontalScroll && videos.length > 1 && (
583
+ <>
584
+ <button
585
+ onClick={scrollLeft}
586
+ style={{
587
+ position: 'absolute',
588
+ left: '10px',
589
+ top: '50%',
590
+ transform: 'translateY(-50%)',
591
+ background: 'rgba(0,0,0,0.5)',
592
+ color: 'white',
593
+ border: 'none',
594
+ borderRadius: '50%',
595
+ width: '40px',
596
+ height: '40px',
597
+ cursor: 'pointer',
598
+ display: 'flex',
599
+ alignItems: 'center',
600
+ justifyContent: 'center',
601
+ fontSize: '18px',
602
+ zIndex: 10,
603
+ transition: 'all 0.2s ease'
604
+ }}
605
+ onMouseOver={(e) => {
606
+ e.currentTarget.style.background = 'rgba(0,0,0,0.7)';
607
+ e.currentTarget.style.transform = 'translateY(-50%) scale(1.1)';
608
+ }}
609
+ onMouseOut={(e) => {
610
+ e.currentTarget.style.background = 'rgba(0,0,0,0.5)';
611
+ e.currentTarget.style.transform = 'translateY(-50%) scale(1)';
612
+ }}
613
+ >
614
+
615
+ </button>
616
+ <button
617
+ onClick={scrollRight}
618
+ style={{
619
+ position: 'absolute',
620
+ right: '10px',
621
+ top: '50%',
622
+ transform: 'translateY(-50%)',
623
+ background: 'rgba(0,0,0,0.5)',
624
+ color: 'white',
625
+ border: 'none',
626
+ borderRadius: '50%',
627
+ width: '40px',
628
+ height: '40px',
629
+ cursor: 'pointer',
630
+ display: 'flex',
631
+ alignItems: 'center',
632
+ justifyContent: 'center',
633
+ fontSize: '18px',
634
+ zIndex: 10,
635
+ transition: 'all 0.2s ease'
636
+ }}
637
+ onMouseOver={(e) => {
638
+ e.currentTarget.style.background = 'rgba(0,0,0,0.7)';
639
+ e.currentTarget.style.transform = 'translateY(-50%) scale(1.1)';
640
+ }}
641
+ onMouseOut={(e) => {
642
+ e.currentTarget.style.background = 'rgba(0,0,0,0.5)';
643
+ e.currentTarget.style.transform = 'translateY(-50%) scale(1)';
644
+ }}
645
+ >
646
+
647
+ </button>
648
+ </>
649
+ )}
650
+ {videos.length > 0 ? (
651
+ currentLayout === 'NONE' ? (
652
+ videos.map((video) => (
653
+ <div key={video.id} style={{ minWidth: '280px', width: '280px', height: '100%', display: 'flex', alignItems: 'stretch' }}>
654
+ <VideoCard item={video} />
655
+ </div>
656
+ ))
657
+ ) : currentLayout === 'SMALL' ? (
658
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '16px', padding: '8px' }}>
659
+ {videos.map((video) => (
660
+ <VideoCard key={video.id} item={video} />
661
+ ))}
662
+ </div>
663
+ ) : currentLayout === 'MEDIUM' ? (
664
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '16px', padding: '8px' }}>
665
+ {videos.map((video) => (
666
+ <VideoCard key={video.id} item={video} />
667
+ ))}
668
+ </div>
669
+ ) : currentLayout === 'MEDIUM_THREE' ? (
670
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '16px', padding: '8px' }}>
671
+ {videos.length > 0 && (
672
+ <VideoCard item={videos[0]} large />
673
+ )}
674
+ {videos.length > 1 && (
675
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '16px' }}>
676
+ {videos.slice(1).map((video) => (
677
+ <VideoCard key={video.id} item={video} />
678
+ ))}
679
+ </div>
680
+ )}
681
+ </div>
682
+ ) : null
683
+ ) : (
684
+ <div style={{ textAlign: 'center', color: '#666', padding: '40px', width: '100%', border: '2px dashed #ddd', borderRadius: '8px', backgroundColor: '#f9f9f9' }}>
685
+ <p>No videos available.</p>
686
+ </div>
687
+ )}
688
+ </div>
689
+ </div>
690
+ </>
691
+ );
692
+ };
693
+
590
694
  export default GroupVideoList;