@flamingo-stack/openframe-frontend-core 0.0.177 → 0.0.178-snapshot.20260514174355

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 (69) hide show
  1. package/dist/{chunk-C6ZMI4UB.cjs → chunk-4TM2SBMX.cjs} +884 -1513
  2. package/dist/chunk-4TM2SBMX.cjs.map +1 -0
  3. package/dist/{chunk-WX7PT5C7.cjs → chunk-ALW3D72O.cjs} +61 -2
  4. package/dist/chunk-ALW3D72O.cjs.map +1 -0
  5. package/dist/{chunk-KB2N44BY.js → chunk-FMWHOUFE.js} +61 -2
  6. package/dist/chunk-FMWHOUFE.js.map +1 -0
  7. package/dist/{chunk-6LDN3CIY.js → chunk-ZMQP3UZJ.js} +3701 -4330
  8. package/dist/chunk-ZMQP3UZJ.js.map +1 -0
  9. package/dist/components/features/entity-video-section.d.ts +54 -0
  10. package/dist/components/features/entity-video-section.d.ts.map +1 -0
  11. package/dist/components/features/index.cjs +17 -3
  12. package/dist/components/features/index.cjs.map +1 -1
  13. package/dist/components/features/index.d.ts +4 -2
  14. package/dist/components/features/index.d.ts.map +1 -1
  15. package/dist/components/features/index.js +22 -8
  16. package/dist/components/features/video-bites-display.d.ts +38 -0
  17. package/dist/components/features/video-bites-display.d.ts.map +1 -0
  18. package/dist/components/features/video-ratio-tabs.d.ts +62 -0
  19. package/dist/components/features/video-ratio-tabs.d.ts.map +1 -0
  20. package/dist/components/features/video.d.ts +94 -0
  21. package/dist/components/features/video.d.ts.map +1 -0
  22. package/dist/components/index.cjs +17 -3
  23. package/dist/components/index.cjs.map +1 -1
  24. package/dist/components/index.js +22 -8
  25. package/dist/components/media-carousel.d.ts.map +1 -1
  26. package/dist/components/navigation/index.cjs +3 -3
  27. package/dist/components/navigation/index.js +2 -2
  28. package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
  29. package/dist/components/ui/index.cjs +3 -3
  30. package/dist/components/ui/index.js +2 -2
  31. package/dist/hooks/index.cjs +4 -2
  32. package/dist/hooks/index.cjs.map +1 -1
  33. package/dist/hooks/index.d.ts +1 -0
  34. package/dist/hooks/index.d.ts.map +1 -1
  35. package/dist/hooks/index.js +3 -1
  36. package/dist/hooks/use-near-viewport.d.ts +42 -0
  37. package/dist/hooks/use-near-viewport.d.ts.map +1 -0
  38. package/dist/index.cjs +19 -3
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.js +24 -8
  41. package/package.json +2 -2
  42. package/src/components/features/entity-video-section.tsx +175 -0
  43. package/src/components/features/index.ts +9 -2
  44. package/src/components/features/video-bites-display.tsx +216 -0
  45. package/src/components/features/video-ratio-tabs.tsx +174 -0
  46. package/src/components/features/video.tsx +474 -0
  47. package/src/components/media-carousel.tsx +43 -236
  48. package/src/components/shared/product-release/release-detail-page.tsx +26 -19
  49. package/src/hooks/index.ts +3 -0
  50. package/src/hooks/use-near-viewport.ts +118 -0
  51. package/dist/chunk-6LDN3CIY.js.map +0 -1
  52. package/dist/chunk-C6ZMI4UB.cjs.map +0 -1
  53. package/dist/chunk-KB2N44BY.js.map +0 -1
  54. package/dist/chunk-WX7PT5C7.cjs.map +0 -1
  55. package/dist/components/features/video-player.d.ts +0 -47
  56. package/dist/components/features/video-player.d.ts.map +0 -1
  57. package/dist/components/features/youtube-embed.d.ts +0 -17
  58. package/dist/components/features/youtube-embed.d.ts.map +0 -1
  59. package/dist/utils/lite-youtube-embed-stub.d.ts +0 -8
  60. package/dist/utils/lite-youtube-embed-stub.d.ts.map +0 -1
  61. package/dist/utils/lite-youtube-embed.d.ts +0 -9
  62. package/dist/utils/lite-youtube-embed.d.ts.map +0 -1
  63. package/src/components/features/.video-player.md +0 -44
  64. package/src/components/features/.youtube-embed.md +0 -40
  65. package/src/components/features/__tests__/video-player.test.tsx +0 -142
  66. package/src/components/features/video-player.tsx +0 -1030
  67. package/src/components/features/youtube-embed.tsx +0 -275
  68. package/src/utils/lite-youtube-embed-stub.tsx +0 -21
  69. package/src/utils/lite-youtube-embed.tsx +0 -46
@@ -1,275 +0,0 @@
1
- "use client";
2
-
3
- import React, { useState, useEffect } from 'react';
4
- import ReactPlayer from 'react-player/youtube';
5
-
6
- // Simple SVG icon components
7
- const Play = ({ size = 16, className }: { size?: number; className?: string }) => (
8
- <svg width={size} height={size} fill="currentColor" viewBox="0 0 24 24" className={className}>
9
- <polygon points="5,3 19,12 5,21"/>
10
- </svg>
11
- );
12
-
13
- const Loader = ({ size = 16, className }: { size?: number; className?: string }) => (
14
- <svg width={size} height={size} fill="none" stroke="currentColor" viewBox="0 0 24 24" className={className}>
15
- <line x1="12" y1="2" x2="12" y2="6"/>
16
- <line x1="12" y1="18" x2="12" y2="22"/>
17
- <line x1="4.93" y1="4.93" x2="7.76" y2="7.76"/>
18
- <line x1="16.24" y1="16.24" x2="19.07" y2="19.07"/>
19
- <line x1="2" y1="12" x2="6" y2="12"/>
20
- <line x1="18" y1="12" x2="22" y2="12"/>
21
- <line x1="4.93" y1="19.07" x2="7.76" y2="16.24"/>
22
- <line x1="16.24" y1="7.76" x2="19.07" y2="4.93"/>
23
- </svg>
24
- );
25
-
26
- interface YouTubeEmbedProps {
27
- videoId: string;
28
- title?: string;
29
- className?: string;
30
- showTitle?: boolean;
31
- showMeta?: boolean;
32
- minimalControls?: boolean;
33
- }
34
-
35
- export const YouTubeEmbed: React.FC<YouTubeEmbedProps> = ({
36
- videoId,
37
- title = "YouTube Video",
38
- className = "",
39
- showTitle = true,
40
- showMeta = true,
41
- minimalControls = false
42
- }) => {
43
- const [isLoading, setIsLoading] = useState(true);
44
- const [hasError, setHasError] = useState(false);
45
- const [isPlaying, setIsPlaying] = useState(false);
46
- const [useIframe, setUseIframe] = useState(false);
47
- const [mounted, setMounted] = useState(false);
48
-
49
- useEffect(() => {
50
- setMounted(true);
51
-
52
- // Always use iframe to avoid CSP issues with ReactPlayer scripts
53
- setUseIframe(true);
54
- setIsLoading(false);
55
- }, []);
56
-
57
- const handleReady = () => {
58
- setIsLoading(false);
59
- };
60
-
61
- const handleError = () => {
62
- setIsLoading(false);
63
- setHasError(true);
64
- // Fallback to iframe on error
65
- setUseIframe(true);
66
- };
67
-
68
- const handlePlay = () => {
69
- setIsPlaying(true);
70
- };
71
-
72
- const handlePause = () => {
73
- setIsPlaying(false);
74
- };
75
-
76
- const videoUrl = `https://www.youtube.com/watch?v=${videoId}`;
77
-
78
- // Build embed URL with conditional parameters
79
- const embedParams = new URLSearchParams({
80
- rel: '0',
81
- modestbranding: '1',
82
- playsinline: '1'
83
- });
84
-
85
- if (minimalControls) {
86
- embedParams.set('controls', '0'); // Hide all controls
87
- embedParams.set('showinfo', '0'); // Hide video info
88
- embedParams.set('fs', '0'); // Disable fullscreen
89
- embedParams.set('iv_load_policy', '3'); // Hide annotations
90
- embedParams.set('cc_load_policy', '0'); // Hide captions
91
- embedParams.set('disablekb', '1'); // Disable keyboard controls
92
- embedParams.set('rel', '0'); // Hide related videos (already set above, but ensure it's enforced)
93
- }
94
-
95
- const embedUrl = `https://www.youtube.com/embed/${videoId}?${embedParams.toString()}`;
96
-
97
- // Don't render until mounted to prevent hydration mismatch
98
- if (!mounted) {
99
- return (
100
- <div className={`youtube-embed-container my-6 ${className}`}>
101
- <div className="video-wrapper relative w-full" style={{ paddingBottom: '56.25%' }}>
102
- <div className="loading-overlay absolute inset-0 bg-ods-card border border-ods-border rounded-lg flex items-center justify-center">
103
- <div className="loading-content flex flex-col items-center gap-3">
104
- <Loader className="animate-spin text-ods-accent" size={32} />
105
- <span className="font-sans text-sm text-ods-text-secondary">Loading video...</span>
106
- </div>
107
- </div>
108
- </div>
109
- </div>
110
- );
111
- }
112
-
113
- if (hasError) {
114
- return (
115
- <div className={`youtube-embed-error ${className}`}>
116
- <div className="error-state bg-ods-card border border-ods-error rounded-lg p-6 text-center my-6">
117
- <div className="error-icon flex justify-center mb-4">
118
- <svg width="48" height="48" fill="currentColor" viewBox="0 0 24 24" className="text-ods-error">
119
- <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
120
- </svg>
121
- </div>
122
- <div className="error-title font-sans font-semibold text-lg text-ods-error mb-2">
123
- Video Unavailable
124
- </div>
125
- <div className="error-description font-sans text-sm text-ods-text-secondary mb-4">
126
- Unable to load YouTube video. The video may be private or deleted.
127
- </div>
128
- <a
129
- href={videoUrl}
130
- target="_blank"
131
- rel="noopener noreferrer"
132
- className="error-retry-button bg-ods-error hover:bg-ods-error-hover text-ods-text-on-error border-none rounded px-4 py-2 font-sans font-medium text-sm cursor-pointer transition-colors duration-200"
133
- >
134
- Watch on YouTube
135
- </a>
136
- </div>
137
- </div>
138
- );
139
- }
140
-
141
- return (
142
- <div className={`youtube-embed-container my-6 ${className}`}>
143
- {/* Video Title */}
144
- {title && showTitle && (
145
- <div className="video-title font-sans text-lg font-medium text-ods-text-primary mb-3">
146
- {title}
147
- </div>
148
- )}
149
-
150
- {/* Video Container with 16:9 Aspect Ratio */}
151
- <div className="video-wrapper relative w-full" style={{ paddingBottom: '56.25%' }}>
152
- {/* Loading State */}
153
- {isLoading && (
154
- <div className="loading-overlay absolute inset-0 bg-ods-card border border-ods-border rounded-lg flex items-center justify-center">
155
- <div className="loading-content flex flex-col items-center gap-3">
156
- <Loader className="animate-spin text-ods-accent" size={32} />
157
- <span className="font-sans text-sm text-ods-text-secondary">Loading video...</span>
158
- </div>
159
- </div>
160
- )}
161
-
162
- {/* Video Player */}
163
- <div className="video-player absolute inset-0 rounded-lg overflow-hidden border border-ods-border bg-ods-bg-inverse">
164
- {useIframe ? (
165
- // Iframe fallback for mobile and error cases
166
- <iframe
167
- src={embedUrl}
168
- title={title}
169
- className="w-full h-full border-0"
170
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
171
- allowFullScreen
172
- style={{ border: 'none' }}
173
- />
174
- ) : (
175
- <ReactPlayer
176
- url={videoUrl}
177
- width="100%"
178
- height="100%"
179
- onReady={handleReady}
180
- onError={handleError}
181
- onPlay={handlePlay}
182
- onPause={handlePause}
183
- config={{
184
- youtube: {
185
- playerVars: {
186
- autoplay: 0,
187
- controls: 1,
188
- rel: 0,
189
- showinfo: 0,
190
- modestbranding: 1,
191
- iv_load_policy: 3,
192
- cc_load_policy: 0,
193
- playsinline: 1
194
- }
195
- }
196
- } as any}
197
- light={false} // Show video immediately
198
- playing={false} // Don't autoplay
199
- />
200
- )}
201
- </div>
202
-
203
- {/* Custom Play Button Overlay (optional enhancement) - only for ReactPlayer */}
204
- {!useIframe && !isPlaying && !isLoading && (
205
- <div className="play-overlay absolute inset-0 flex items-center justify-center bg-ods-bg-inverse bg-opacity-20 rounded-lg transition-opacity duration-300 hover:bg-opacity-30">
206
- <button
207
- onClick={() => setIsPlaying(true)}
208
- className="play-button bg-ods-accent hover:bg-ods-accent-hover text-ods-text-on-accent w-16 h-16 rounded-full flex items-center justify-center transition-all duration-200 transform hover:scale-110 shadow-lg"
209
- aria-label="Play video"
210
- >
211
- <Play size={24} className="ml-1" />
212
- </button>
213
- </div>
214
- )}
215
- </div>
216
-
217
- {/* Video Meta Information */}
218
- {showMeta && (
219
- <div className="video-meta flex items-center justify-between mt-3 text-sm text-ods-text-secondary">
220
- <div className="video-platform font-sans">
221
- YouTube
222
- </div>
223
- <a
224
- href={videoUrl}
225
- target="_blank"
226
- rel="noopener noreferrer"
227
- className="video-link font-sans text-ods-accent hover:text-ods-accent-hover transition-colors duration-200"
228
- >
229
- Watch on YouTube →
230
- </a>
231
- </div>
232
- )}
233
- </div>
234
- );
235
- };
236
-
237
- // Utility function to extract YouTube video ID from various URL formats
238
- export const extractYouTubeId = (url: string): string | null => {
239
- const patterns = [
240
- /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
241
- /youtube\.com\/v\/([^&\n?#]+)/,
242
- /youtube\.com\/watch\?.*v=([^&\n?#]+)/
243
- ];
244
-
245
- for (const pattern of patterns) {
246
- const match = url.match(pattern);
247
- if (match) {
248
- return match[1];
249
- }
250
- }
251
-
252
- return null;
253
- };
254
-
255
- // Component for parsing YouTube URLs in markdown
256
- export const YouTubeLinkParser: React.FC<{ href: string; children: React.ReactNode }> = ({ href, children }) => {
257
- const videoId = extractYouTubeId(href);
258
-
259
- // If it's a YouTube URL, render the embed instead of a link
260
- if (videoId) {
261
- return <YouTubeEmbed videoId={videoId} title={typeof children === 'string' ? children : undefined} />;
262
- }
263
-
264
- // Otherwise, render as a normal link
265
- return (
266
- <a
267
- href={href}
268
- target="_blank"
269
- rel="noopener noreferrer"
270
- className="text-ods-accent hover:text-ods-accent-hover transition-colors duration-200"
271
- >
272
- {children}
273
- </a>
274
- );
275
- };
@@ -1,21 +0,0 @@
1
- import React from 'react';
2
-
3
- export interface LiteYouTubeEmbedProps {
4
- id: string;
5
- title?: string;
6
- poster?: string;
7
- className?: string;
8
- }
9
-
10
- export function LiteYouTubeEmbed({ id, title, poster, className }: LiteYouTubeEmbedProps) {
11
- return (
12
- <div className={className}>
13
- <iframe
14
- src={`https://www.youtube.com/embed/${id}`}
15
- title={title}
16
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
17
- allowFullScreen
18
- />
19
- </div>
20
- );
21
- }
@@ -1,46 +0,0 @@
1
- import React from 'react';
2
-
3
- interface LiteYouTubeEmbedProps {
4
- id: string;
5
- title?: string;
6
- thumbnail?: string;
7
- className?: string;
8
- }
9
-
10
- export function LiteYouTubeEmbed({ id, title = 'YouTube video', thumbnail, className }: LiteYouTubeEmbedProps) {
11
- const [isLoaded, setIsLoaded] = React.useState(false);
12
-
13
- const handleLoad = () => {
14
- setIsLoaded(true);
15
- };
16
-
17
- if (isLoaded) {
18
- return (
19
- <iframe
20
- src={`https://www.youtube.com/embed/${id}`}
21
- title={title}
22
- className={className}
23
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
24
- allowFullScreen
25
- />
26
- );
27
- }
28
-
29
- return (
30
- <div
31
- className={`cursor-pointer relative ${className}`}
32
- onClick={handleLoad}
33
- >
34
- <img
35
- src={thumbnail || `https://img.youtube.com/vi/${id}/maxresdefault.jpg`}
36
- alt={title}
37
- className="w-full h-full object-cover"
38
- />
39
- <div className="absolute inset-0 flex items-center justify-center">
40
- <div className="w-16 h-16 bg-red-600 rounded-full flex items-center justify-center">
41
- <div className="w-0 h-0 border-l-8 border-l-white border-t-4 border-t-transparent border-b-4 border-b-transparent ml-1" />
42
- </div>
43
- </div>
44
- </div>
45
- );
46
- }