@uniweb/kit 0.1.5 → 0.1.6
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 +3 -2
- package/src/components/Asset/Asset.jsx +31 -81
- package/src/components/Media/Media.jsx +27 -125
- package/src/components/SocialIcon/index.jsx +146 -0
- package/src/hooks/index.js +6 -0
- package/src/hooks/useAccordion.js +143 -0
- package/src/hooks/useActiveRoute.js +97 -0
- package/src/hooks/useGridLayout.js +71 -0
- package/src/hooks/useMobileMenu.js +58 -0
- package/src/hooks/useScrolled.js +48 -0
- package/src/hooks/useTheme.js +205 -0
- package/src/index.js +29 -10
- package/src/styled/Asset/Asset.jsx +161 -0
- package/src/styled/Asset/index.js +1 -0
- package/src/{components → styled}/Disclaimer/Disclaimer.jsx +1 -1
- package/src/styled/Media/Media.jsx +322 -0
- package/src/styled/Media/index.js +1 -0
- package/src/{components → styled}/Section/Render.jsx +4 -4
- package/src/{components → styled}/Section/index.js +6 -0
- package/src/{components → styled}/Section/renderers/Alert.jsx +1 -1
- package/src/{components → styled}/Section/renderers/Details.jsx +1 -1
- package/src/{components → styled}/Section/renderers/Table.jsx +1 -1
- package/src/{components → styled}/Section/renderers/index.js +1 -1
- package/src/styled/SidebarLayout/SidebarLayout.jsx +310 -0
- package/src/styled/SidebarLayout/index.js +1 -0
- package/src/styled/index.js +40 -0
- /package/src/{components → styled}/Disclaimer/index.js +0 -0
- /package/src/{components → styled}/Section/Section.jsx +0 -0
- /package/src/{components → styled}/Section/renderers/Code.jsx +0 -0
- /package/src/{components → styled}/Section/renderers/Divider.jsx +0 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asset Component
|
|
3
|
+
*
|
|
4
|
+
* File asset preview with download functionality.
|
|
5
|
+
*
|
|
6
|
+
* @module @uniweb/kit/Asset
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React, { useState, useCallback, forwardRef, useImperativeHandle } from 'react'
|
|
10
|
+
import { cn } from '../../utils/index.js'
|
|
11
|
+
import { FileLogo } from '../FileLogo/index.js'
|
|
12
|
+
import { Image } from '../Image/index.js'
|
|
13
|
+
import { useWebsite } from '../../hooks/useWebsite.js'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if file is an image
|
|
17
|
+
* @param {string} filename
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
*/
|
|
20
|
+
function isImageFile(filename) {
|
|
21
|
+
if (!filename) return false
|
|
22
|
+
const ext = filename.toLowerCase().split('.').pop()
|
|
23
|
+
return ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'].includes(ext)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Asset - File preview with download
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} props
|
|
30
|
+
* @param {string|Object} props.value - Asset identifier or object
|
|
31
|
+
* @param {Object} [props.profile] - Profile object for asset resolution
|
|
32
|
+
* @param {boolean} [props.withDownload=true] - Show download button
|
|
33
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* <Asset value="document.pdf" profile={profile} />
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* <Asset value={{ src: "/files/report.pdf", filename: "report.pdf" }} />
|
|
40
|
+
*/
|
|
41
|
+
export const Asset = forwardRef(function Asset(
|
|
42
|
+
{ value, profile, withDownload = true, className, ...props },
|
|
43
|
+
ref
|
|
44
|
+
) {
|
|
45
|
+
const { localize } = useWebsite()
|
|
46
|
+
const [imageError, setImageError] = useState(false)
|
|
47
|
+
const [isHovered, setIsHovered] = useState(false)
|
|
48
|
+
|
|
49
|
+
// Resolve asset info
|
|
50
|
+
let src = ''
|
|
51
|
+
let filename = ''
|
|
52
|
+
let alt = ''
|
|
53
|
+
|
|
54
|
+
if (typeof value === 'string') {
|
|
55
|
+
src = value
|
|
56
|
+
filename = value.split('/').pop() || value
|
|
57
|
+
} else if (value && typeof value === 'object') {
|
|
58
|
+
src = value.src || value.url || ''
|
|
59
|
+
filename = value.filename || value.name || src.split('/').pop() || ''
|
|
60
|
+
alt = value.alt || filename
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Use profile to resolve asset if available
|
|
64
|
+
if (profile && typeof profile.getAssetInfo === 'function') {
|
|
65
|
+
const assetInfo = profile.getAssetInfo(value, true, filename)
|
|
66
|
+
src = assetInfo?.src || src
|
|
67
|
+
filename = assetInfo?.filename || filename
|
|
68
|
+
alt = assetInfo?.alt || alt
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const isImage = isImageFile(filename)
|
|
72
|
+
|
|
73
|
+
// Handle download
|
|
74
|
+
const handleDownload = useCallback(async () => {
|
|
75
|
+
if (!src) return
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
// Try to trigger download
|
|
79
|
+
const downloadUrl = src.includes('?') ? `${src}&download=true` : `${src}?download=true`
|
|
80
|
+
const response = await fetch(downloadUrl)
|
|
81
|
+
const blob = await response.blob()
|
|
82
|
+
|
|
83
|
+
const link = document.createElement('a')
|
|
84
|
+
link.href = URL.createObjectURL(blob)
|
|
85
|
+
link.download = filename
|
|
86
|
+
document.body.appendChild(link)
|
|
87
|
+
link.click()
|
|
88
|
+
document.body.removeChild(link)
|
|
89
|
+
URL.revokeObjectURL(link.href)
|
|
90
|
+
} catch (error) {
|
|
91
|
+
// Fallback: open in new tab
|
|
92
|
+
window.open(src, '_blank')
|
|
93
|
+
}
|
|
94
|
+
}, [src, filename])
|
|
95
|
+
|
|
96
|
+
// Expose download method via ref
|
|
97
|
+
useImperativeHandle(ref, () => ({
|
|
98
|
+
triggerDownload: handleDownload
|
|
99
|
+
}), [handleDownload])
|
|
100
|
+
|
|
101
|
+
// Handle image error
|
|
102
|
+
const handleImageError = useCallback(() => {
|
|
103
|
+
setImageError(true)
|
|
104
|
+
}, [])
|
|
105
|
+
|
|
106
|
+
if (!src) return null
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div
|
|
110
|
+
className={cn(
|
|
111
|
+
'relative inline-block rounded-lg overflow-hidden border border-gray-200',
|
|
112
|
+
'transition-shadow hover:shadow-md',
|
|
113
|
+
className
|
|
114
|
+
)}
|
|
115
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
116
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
117
|
+
{...props}
|
|
118
|
+
>
|
|
119
|
+
{/* Preview */}
|
|
120
|
+
<div className="w-32 h-32 flex items-center justify-center bg-gray-50">
|
|
121
|
+
{isImage && !imageError ? (
|
|
122
|
+
<Image
|
|
123
|
+
src={src}
|
|
124
|
+
alt={alt}
|
|
125
|
+
className="w-full h-full object-cover"
|
|
126
|
+
onError={handleImageError}
|
|
127
|
+
/>
|
|
128
|
+
) : (
|
|
129
|
+
<FileLogo filename={filename} size="48" className="text-gray-400" />
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
{/* Filename */}
|
|
134
|
+
<div className="px-2 py-1 text-xs text-gray-600 truncate max-w-[128px]" title={filename}>
|
|
135
|
+
{filename}
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
{/* Download overlay */}
|
|
139
|
+
{withDownload && (
|
|
140
|
+
<button
|
|
141
|
+
onClick={handleDownload}
|
|
142
|
+
className={cn(
|
|
143
|
+
'absolute inset-0 flex items-center justify-center',
|
|
144
|
+
'bg-black/50 text-white transition-opacity',
|
|
145
|
+
isHovered ? 'opacity-100' : 'opacity-0'
|
|
146
|
+
)}
|
|
147
|
+
aria-label={localize({
|
|
148
|
+
en: 'Download file',
|
|
149
|
+
fr: 'Télécharger le fichier'
|
|
150
|
+
})}
|
|
151
|
+
>
|
|
152
|
+
<svg className="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
153
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
|
154
|
+
</svg>
|
|
155
|
+
</button>
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
export default Asset
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Asset, default } from './Asset.jsx'
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import React, { useState, useCallback, useEffect } from 'react'
|
|
10
10
|
import { cn } from '../../utils/index.js'
|
|
11
11
|
import { useWebsite } from '../../hooks/useWebsite.js'
|
|
12
|
-
import { SafeHtml } from '
|
|
12
|
+
import { SafeHtml } from '../../components/SafeHtml/index.js'
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Disclaimer Modal
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Component
|
|
3
|
+
*
|
|
4
|
+
* Video player supporting:
|
|
5
|
+
* - YouTube embeds
|
|
6
|
+
* - Vimeo embeds
|
|
7
|
+
* - Local/direct video files
|
|
8
|
+
* - Thumbnail facades
|
|
9
|
+
* - Playback tracking
|
|
10
|
+
*
|
|
11
|
+
* @module @uniweb/kit/Media
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import React, { useState, useEffect, useRef, useCallback } from 'react'
|
|
15
|
+
import { cn } from '../../utils/index.js'
|
|
16
|
+
import { detectMediaType } from '../../utils/index.js'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Extract YouTube video ID from URL
|
|
20
|
+
* @param {string} url
|
|
21
|
+
* @returns {string|null}
|
|
22
|
+
*/
|
|
23
|
+
function getYouTubeId(url) {
|
|
24
|
+
if (!url) return null
|
|
25
|
+
const match = url.match(/(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([^&?/]+)/)
|
|
26
|
+
return match?.[1] || null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Extract Vimeo video ID from URL
|
|
31
|
+
* @param {string} url
|
|
32
|
+
* @returns {string|null}
|
|
33
|
+
*/
|
|
34
|
+
function getVimeoId(url) {
|
|
35
|
+
if (!url) return null
|
|
36
|
+
const match = url.match(/vimeo\.com\/(?:video\/)?(\d+)/)
|
|
37
|
+
return match?.[1] || null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get thumbnail URL for a video
|
|
42
|
+
* @param {string} src - Video URL
|
|
43
|
+
* @param {string} type - Media type
|
|
44
|
+
* @returns {string|null}
|
|
45
|
+
*/
|
|
46
|
+
function getVideoThumbnail(src, type) {
|
|
47
|
+
if (type === 'youtube') {
|
|
48
|
+
const id = getYouTubeId(src)
|
|
49
|
+
return id ? `https://img.youtube.com/vi/${id}/maxresdefault.jpg` : null
|
|
50
|
+
}
|
|
51
|
+
// Vimeo requires API call, return null for now
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* YouTube Player Component
|
|
57
|
+
*/
|
|
58
|
+
function YouTubePlayer({ videoId, autoplay, muted, loop, onReady, onStateChange, className }) {
|
|
59
|
+
const iframeRef = useRef(null)
|
|
60
|
+
|
|
61
|
+
const params = new URLSearchParams({
|
|
62
|
+
enablejsapi: '1',
|
|
63
|
+
autoplay: autoplay ? '1' : '0',
|
|
64
|
+
mute: muted ? '1' : '0',
|
|
65
|
+
loop: loop ? '1' : '0',
|
|
66
|
+
playlist: loop ? videoId : '',
|
|
67
|
+
rel: '0',
|
|
68
|
+
modestbranding: '1'
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<iframe
|
|
73
|
+
ref={iframeRef}
|
|
74
|
+
src={`https://www.youtube.com/embed/${videoId}?${params}`}
|
|
75
|
+
className={cn('w-full h-full', className)}
|
|
76
|
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
77
|
+
allowFullScreen
|
|
78
|
+
title="YouTube video"
|
|
79
|
+
/>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Vimeo Player Component
|
|
85
|
+
*/
|
|
86
|
+
function VimeoPlayer({ videoId, autoplay, muted, loop, className }) {
|
|
87
|
+
const params = new URLSearchParams({
|
|
88
|
+
autoplay: autoplay ? '1' : '0',
|
|
89
|
+
muted: muted ? '1' : '0',
|
|
90
|
+
loop: loop ? '1' : '0',
|
|
91
|
+
dnt: '1'
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<iframe
|
|
96
|
+
src={`https://player.vimeo.com/video/${videoId}?${params}`}
|
|
97
|
+
className={cn('w-full h-full', className)}
|
|
98
|
+
allow="autoplay; fullscreen; picture-in-picture"
|
|
99
|
+
allowFullScreen
|
|
100
|
+
title="Vimeo video"
|
|
101
|
+
/>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Local/Direct Video Player Component
|
|
107
|
+
*/
|
|
108
|
+
function LocalVideo({ src, autoplay, muted, loop, controls, poster, onProgress, className }) {
|
|
109
|
+
const videoRef = useRef(null)
|
|
110
|
+
const [milestones, setMilestones] = useState({ 25: false, 50: false, 75: false, 95: false })
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
const video = videoRef.current
|
|
114
|
+
if (!video || !onProgress) return
|
|
115
|
+
|
|
116
|
+
const handleTimeUpdate = () => {
|
|
117
|
+
const percent = (video.currentTime / video.duration) * 100
|
|
118
|
+
|
|
119
|
+
Object.entries({ 25: 25, 50: 50, 75: 75, 95: 95 }).forEach(([key, threshold]) => {
|
|
120
|
+
if (percent >= threshold && !milestones[key]) {
|
|
121
|
+
setMilestones((prev) => ({ ...prev, [key]: true }))
|
|
122
|
+
onProgress({ milestone: key, percent, currentTime: video.currentTime })
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
video.addEventListener('timeupdate', handleTimeUpdate)
|
|
128
|
+
return () => video.removeEventListener('timeupdate', handleTimeUpdate)
|
|
129
|
+
}, [milestones, onProgress])
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<video
|
|
133
|
+
ref={videoRef}
|
|
134
|
+
src={src}
|
|
135
|
+
autoPlay={autoplay}
|
|
136
|
+
muted={muted}
|
|
137
|
+
loop={loop}
|
|
138
|
+
controls={controls}
|
|
139
|
+
poster={poster}
|
|
140
|
+
playsInline
|
|
141
|
+
className={cn('w-full h-full object-cover', className)}
|
|
142
|
+
/>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Play Button Overlay
|
|
148
|
+
*/
|
|
149
|
+
function PlayButton({ onClick, className }) {
|
|
150
|
+
return (
|
|
151
|
+
<button
|
|
152
|
+
onClick={onClick}
|
|
153
|
+
className={cn(
|
|
154
|
+
'absolute inset-0 flex items-center justify-center',
|
|
155
|
+
'bg-black/30 hover:bg-black/40 transition-colors',
|
|
156
|
+
'group cursor-pointer',
|
|
157
|
+
className
|
|
158
|
+
)}
|
|
159
|
+
aria-label="Play video"
|
|
160
|
+
>
|
|
161
|
+
<div className="w-16 h-16 rounded-full bg-white/90 group-hover:bg-white flex items-center justify-center transition-colors">
|
|
162
|
+
<svg className="w-8 h-8 text-gray-900 ml-1" fill="currentColor" viewBox="0 0 24 24">
|
|
163
|
+
<path d="M8 5v14l11-7z" />
|
|
164
|
+
</svg>
|
|
165
|
+
</div>
|
|
166
|
+
</button>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Media - Video player component
|
|
172
|
+
*
|
|
173
|
+
* @param {Object} props
|
|
174
|
+
* @param {string|Object} props.src - Video URL or media object
|
|
175
|
+
* @param {Object} [props.media] - Media object with src/caption
|
|
176
|
+
* @param {string} [props.thumbnail] - Thumbnail URL
|
|
177
|
+
* @param {boolean} [props.autoplay=false] - Auto-play video
|
|
178
|
+
* @param {boolean} [props.muted=false] - Mute video
|
|
179
|
+
* @param {boolean} [props.loop=false] - Loop video
|
|
180
|
+
* @param {boolean} [props.controls=true] - Show controls
|
|
181
|
+
* @param {boolean} [props.facade=false] - Show thumbnail with play button
|
|
182
|
+
* @param {string} [props.aspectRatio='16/9'] - Aspect ratio
|
|
183
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
184
|
+
* @param {Function} [props.onProgress] - Progress callback for tracking
|
|
185
|
+
* @param {Object} [props.block] - Block object for event tracking
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* // YouTube video
|
|
189
|
+
* <Media src="https://youtube.com/watch?v=abc123" />
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* // With thumbnail facade
|
|
193
|
+
* <Media
|
|
194
|
+
* src="https://youtube.com/watch?v=abc123"
|
|
195
|
+
* thumbnail="/images/video-poster.jpg"
|
|
196
|
+
* facade
|
|
197
|
+
* />
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* // Local video
|
|
201
|
+
* <Media src="/videos/intro.mp4" controls autoplay={false} />
|
|
202
|
+
*/
|
|
203
|
+
export function Media({
|
|
204
|
+
src,
|
|
205
|
+
media,
|
|
206
|
+
thumbnail,
|
|
207
|
+
autoplay = false,
|
|
208
|
+
muted = false,
|
|
209
|
+
loop = false,
|
|
210
|
+
controls = true,
|
|
211
|
+
facade = false,
|
|
212
|
+
aspectRatio = '16/9',
|
|
213
|
+
className,
|
|
214
|
+
onProgress,
|
|
215
|
+
block,
|
|
216
|
+
...props
|
|
217
|
+
}) {
|
|
218
|
+
const [showVideo, setShowVideo] = useState(!facade)
|
|
219
|
+
|
|
220
|
+
// Normalize source
|
|
221
|
+
const videoSrc = typeof src === 'string' ? src : (src?.src || media?.src || '')
|
|
222
|
+
const caption = media?.caption || src?.caption || ''
|
|
223
|
+
|
|
224
|
+
// Detect video type
|
|
225
|
+
const mediaType = detectMediaType(videoSrc)
|
|
226
|
+
|
|
227
|
+
// Get thumbnail
|
|
228
|
+
const thumbnailSrc = thumbnail || getVideoThumbnail(videoSrc, mediaType)
|
|
229
|
+
|
|
230
|
+
// Handle play click (for facade mode)
|
|
231
|
+
const handlePlay = useCallback(() => {
|
|
232
|
+
setShowVideo(true)
|
|
233
|
+
}, [])
|
|
234
|
+
|
|
235
|
+
// Handle progress tracking
|
|
236
|
+
const handleProgress = useCallback((data) => {
|
|
237
|
+
onProgress?.(data)
|
|
238
|
+
|
|
239
|
+
// Track via block if available
|
|
240
|
+
if (block?.trackEvent && typeof window !== 'undefined' && window.uniweb?.analytics?.initialized) {
|
|
241
|
+
block.trackEvent(`video_milestone_${data.milestone}`, {
|
|
242
|
+
milestone: `${data.milestone}%`,
|
|
243
|
+
src: videoSrc
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
}, [onProgress, block, videoSrc])
|
|
247
|
+
|
|
248
|
+
// Render facade (thumbnail with play button)
|
|
249
|
+
if (facade && !showVideo && thumbnailSrc) {
|
|
250
|
+
return (
|
|
251
|
+
<div
|
|
252
|
+
className={cn('relative overflow-hidden', className)}
|
|
253
|
+
style={{ aspectRatio }}
|
|
254
|
+
{...props}
|
|
255
|
+
>
|
|
256
|
+
<img
|
|
257
|
+
src={thumbnailSrc}
|
|
258
|
+
alt={caption || 'Video thumbnail'}
|
|
259
|
+
className="w-full h-full object-cover"
|
|
260
|
+
/>
|
|
261
|
+
<PlayButton onClick={handlePlay} />
|
|
262
|
+
</div>
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Render video player
|
|
267
|
+
const videoContent = (() => {
|
|
268
|
+
switch (mediaType) {
|
|
269
|
+
case 'youtube': {
|
|
270
|
+
const videoId = getYouTubeId(videoSrc)
|
|
271
|
+
if (!videoId) return null
|
|
272
|
+
return (
|
|
273
|
+
<YouTubePlayer
|
|
274
|
+
videoId={videoId}
|
|
275
|
+
autoplay={autoplay || (facade && showVideo)}
|
|
276
|
+
muted={muted}
|
|
277
|
+
loop={loop}
|
|
278
|
+
/>
|
|
279
|
+
)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
case 'vimeo': {
|
|
283
|
+
const videoId = getVimeoId(videoSrc)
|
|
284
|
+
if (!videoId) return null
|
|
285
|
+
return (
|
|
286
|
+
<VimeoPlayer
|
|
287
|
+
videoId={videoId}
|
|
288
|
+
autoplay={autoplay || (facade && showVideo)}
|
|
289
|
+
muted={muted}
|
|
290
|
+
loop={loop}
|
|
291
|
+
/>
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
case 'video':
|
|
296
|
+
default:
|
|
297
|
+
return (
|
|
298
|
+
<LocalVideo
|
|
299
|
+
src={videoSrc}
|
|
300
|
+
autoplay={autoplay || (facade && showVideo)}
|
|
301
|
+
muted={muted}
|
|
302
|
+
loop={loop}
|
|
303
|
+
controls={controls}
|
|
304
|
+
poster={thumbnailSrc}
|
|
305
|
+
onProgress={handleProgress}
|
|
306
|
+
/>
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
})()
|
|
310
|
+
|
|
311
|
+
return (
|
|
312
|
+
<div
|
|
313
|
+
className={cn('relative overflow-hidden bg-black', className)}
|
|
314
|
+
style={{ aspectRatio }}
|
|
315
|
+
{...props}
|
|
316
|
+
>
|
|
317
|
+
{videoContent}
|
|
318
|
+
</div>
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export default Media
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Media, default } from './Media.jsx'
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
import React from 'react'
|
|
11
11
|
import { cn } from '../../utils/index.js'
|
|
12
|
-
import { SafeHtml } from '
|
|
13
|
-
import { Image } from '
|
|
14
|
-
import { Media } from '
|
|
15
|
-
import { Link } from '
|
|
12
|
+
import { SafeHtml } from '../../components/SafeHtml/index.js'
|
|
13
|
+
import { Image } from '../../components/Image/index.js'
|
|
14
|
+
import { Media } from '../../components/Media/index.js'
|
|
15
|
+
import { Link } from '../../components/Link/index.js'
|
|
16
16
|
import { Code } from './renderers/Code.jsx'
|
|
17
17
|
import { Alert } from './renderers/Alert.jsx'
|
|
18
18
|
import { Table } from './renderers/Table.jsx'
|