@uniweb/kit 0.7.9 → 0.7.11
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 +4 -7
- package/src/components/Image/Image.jsx +120 -121
- package/src/components/Media/Media.jsx +181 -158
- package/src/styled/Visual/index.jsx +15 -15
package/package.json
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/kit",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.11",
|
|
4
4
|
"description": "Standard component library for Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/index.js",
|
|
8
|
-
"./theme-tokens.css":
|
|
9
|
-
"style": "./src/theme-tokens.css",
|
|
10
|
-
"default": "./src/theme-tokens.css"
|
|
11
|
-
}
|
|
8
|
+
"./theme-tokens.css": "./src/theme-tokens.css"
|
|
12
9
|
},
|
|
13
10
|
"files": [
|
|
14
11
|
"src",
|
|
@@ -37,11 +34,11 @@
|
|
|
37
34
|
"node": ">=20.19"
|
|
38
35
|
},
|
|
39
36
|
"dependencies": {
|
|
37
|
+
"@uniweb/core": "latest",
|
|
40
38
|
"clsx": "^2.1.0",
|
|
41
39
|
"fuse.js": "^7.0.0",
|
|
42
40
|
"shiki": "^3.0.0",
|
|
43
|
-
"tailwind-merge": "^2.6.0"
|
|
44
|
-
"@uniweb/core": "0.5.9"
|
|
41
|
+
"tailwind-merge": "^2.6.0"
|
|
45
42
|
},
|
|
46
43
|
"peerDependencies": {
|
|
47
44
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -19,13 +19,13 @@ import { cn } from "../../utils/index.js";
|
|
|
19
19
|
* Size presets for images
|
|
20
20
|
*/
|
|
21
21
|
const SIZE_CLASSES = {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
xs: "w-8 h-8",
|
|
23
|
+
sm: "w-12 h-12",
|
|
24
|
+
md: "w-16 h-16",
|
|
25
|
+
lg: "w-24 h-24",
|
|
26
|
+
xl: "w-32 h-32",
|
|
27
|
+
"2xl": "w-48 h-48",
|
|
28
|
+
full: "w-full h-full",
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -34,18 +34,18 @@ const SIZE_CLASSES = {
|
|
|
34
34
|
* @returns {string} CSS filter value
|
|
35
35
|
*/
|
|
36
36
|
function buildFilterStyle(filter) {
|
|
37
|
-
|
|
37
|
+
if (!filter || typeof filter !== "object") return undefined;
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
const filters = [];
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
if (filter.blur) filters.push(`blur(${filter.blur}px)`);
|
|
42
|
+
if (filter.brightness) filters.push(`brightness(${filter.brightness}%)`);
|
|
43
|
+
if (filter.contrast) filters.push(`contrast(${filter.contrast}%)`);
|
|
44
|
+
if (filter.grayscale) filters.push(`grayscale(${filter.grayscale}%)`);
|
|
45
|
+
if (filter.saturate) filters.push(`saturate(${filter.saturate}%)`);
|
|
46
|
+
if (filter.sepia) filters.push(`sepia(${filter.sepia}%)`);
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
return filters.length > 0 ? filters.join(" ") : undefined;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
@@ -85,116 +85,115 @@ function buildFilterStyle(filter) {
|
|
|
85
85
|
* <Image src="/logo.png" href="/about" alt="Company logo" />
|
|
86
86
|
*/
|
|
87
87
|
export function Image({
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
88
|
+
profile,
|
|
89
|
+
type,
|
|
90
|
+
size,
|
|
91
|
+
value,
|
|
92
|
+
src,
|
|
93
|
+
url,
|
|
94
|
+
alt = "",
|
|
95
|
+
href,
|
|
96
|
+
rounded,
|
|
97
|
+
filter,
|
|
98
|
+
loading = "lazy",
|
|
99
|
+
className,
|
|
100
|
+
ariaHidden,
|
|
101
|
+
onError,
|
|
102
|
+
onLoad,
|
|
103
|
+
...props
|
|
104
104
|
}) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
105
|
+
const [hasError, setHasError] = useState(false);
|
|
106
|
+
|
|
107
|
+
// Determine the image source
|
|
108
|
+
let resolvedSrc = src || url || "";
|
|
109
|
+
let resolvedAlt = alt;
|
|
110
|
+
|
|
111
|
+
// Handle profile-based images
|
|
112
|
+
if (profile && type) {
|
|
113
|
+
if (type === "avatar" || type === "banner") {
|
|
114
|
+
// Use profile methods if available
|
|
115
|
+
if (typeof profile.getImageInfo === "function") {
|
|
116
|
+
const imageInfo = profile.getImageInfo(type, size);
|
|
117
|
+
resolvedSrc = imageInfo?.url || resolvedSrc;
|
|
118
|
+
resolvedAlt = imageInfo?.alt || resolvedAlt;
|
|
119
|
+
}
|
|
120
|
+
} else if (value && typeof profile.getAssetInfo === "function") {
|
|
121
|
+
const assetInfo = profile.getAssetInfo(value, true, alt);
|
|
122
|
+
resolvedSrc = assetInfo?.src || resolvedSrc;
|
|
123
|
+
resolvedAlt = assetInfo?.alt || resolvedAlt;
|
|
124
|
+
}
|
|
125
125
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
|
|
127
|
+
// Handle value as direct source
|
|
128
|
+
if (!resolvedSrc && value) {
|
|
129
|
+
if (typeof value === "string") {
|
|
130
|
+
resolvedSrc = value;
|
|
131
|
+
} else if (value.url || value.src) {
|
|
132
|
+
resolvedSrc = value.url || value.src;
|
|
133
|
+
resolvedAlt = value.alt || resolvedAlt;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Build classes
|
|
138
|
+
const sizeClass = size && SIZE_CLASSES[size];
|
|
139
|
+
const roundedClass =
|
|
140
|
+
rounded === true
|
|
141
|
+
? "rounded-full"
|
|
142
|
+
: typeof rounded === "string"
|
|
143
|
+
? rounded
|
|
144
|
+
: "";
|
|
145
|
+
|
|
146
|
+
const imageClasses = cn("object-cover", sizeClass, roundedClass, className);
|
|
147
|
+
|
|
148
|
+
// Build filter style
|
|
149
|
+
const filterStyle = buildFilterStyle(filter);
|
|
150
|
+
|
|
151
|
+
// Handle error
|
|
152
|
+
const handleError = useCallback(
|
|
153
|
+
(e) => {
|
|
154
|
+
setHasError(true);
|
|
155
|
+
onError?.(e);
|
|
156
|
+
},
|
|
157
|
+
[onError],
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Handle load
|
|
161
|
+
const handleLoad = useCallback(
|
|
162
|
+
(e) => {
|
|
163
|
+
onLoad?.(e);
|
|
164
|
+
},
|
|
165
|
+
[onLoad],
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// Don't render if no source or error
|
|
169
|
+
if (!resolvedSrc || hasError) {
|
|
170
|
+
return null;
|
|
135
171
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// Build filter style
|
|
150
|
-
const filterStyle = buildFilterStyle(filter);
|
|
151
|
-
|
|
152
|
-
// Handle error
|
|
153
|
-
const handleError = useCallback(
|
|
154
|
-
(e) => {
|
|
155
|
-
setHasError(true);
|
|
156
|
-
onError?.(e);
|
|
157
|
-
},
|
|
158
|
-
[onError]
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
// Handle load
|
|
162
|
-
const handleLoad = useCallback(
|
|
163
|
-
(e) => {
|
|
164
|
-
onLoad?.(e);
|
|
165
|
-
},
|
|
166
|
-
[onLoad]
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
// Don't render if no source or error
|
|
170
|
-
if (!resolvedSrc || hasError) {
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const imageElement = (
|
|
175
|
-
<img
|
|
176
|
-
src={resolvedSrc}
|
|
177
|
-
alt={resolvedAlt}
|
|
178
|
-
loading={loading}
|
|
179
|
-
className={imageClasses}
|
|
180
|
-
style={filterStyle ? { filter: filterStyle } : undefined}
|
|
181
|
-
onError={handleError}
|
|
182
|
-
onLoad={handleLoad}
|
|
183
|
-
aria-hidden={ariaHidden}
|
|
184
|
-
{...props}
|
|
185
|
-
/>
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
// Wrap in link if href provided
|
|
189
|
-
if (href) {
|
|
190
|
-
return (
|
|
191
|
-
<Link to={href} className="inline-block">
|
|
192
|
-
{imageElement}
|
|
193
|
-
</Link>
|
|
172
|
+
|
|
173
|
+
const imageElement = (
|
|
174
|
+
<img
|
|
175
|
+
src={resolvedSrc}
|
|
176
|
+
alt={resolvedAlt}
|
|
177
|
+
loading={loading}
|
|
178
|
+
className={imageClasses}
|
|
179
|
+
style={filterStyle ? { filter: filterStyle } : undefined}
|
|
180
|
+
onError={handleError}
|
|
181
|
+
onLoad={handleLoad}
|
|
182
|
+
aria-hidden={ariaHidden}
|
|
183
|
+
{...props}
|
|
184
|
+
/>
|
|
194
185
|
);
|
|
195
|
-
}
|
|
196
186
|
|
|
197
|
-
|
|
187
|
+
// Wrap in link if href provided
|
|
188
|
+
if (href) {
|
|
189
|
+
return (
|
|
190
|
+
<Link to={href} className="inline-block">
|
|
191
|
+
{imageElement}
|
|
192
|
+
</Link>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return imageElement;
|
|
198
197
|
}
|
|
199
198
|
|
|
200
199
|
export default Image;
|
|
@@ -8,114 +8,136 @@
|
|
|
8
8
|
* @module @uniweb/kit/Media
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import React, { useState, useEffect, useRef
|
|
12
|
-
import { cn } from
|
|
13
|
-
import { detectMediaType } from
|
|
11
|
+
import React, { useState, useEffect, useRef } from "react";
|
|
12
|
+
import { cn } from "../../utils/index.js";
|
|
13
|
+
import { detectMediaType } from "../../utils/index.js";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Extract YouTube video ID from URL
|
|
17
17
|
*/
|
|
18
18
|
function getYouTubeId(url) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
if (!url) return null;
|
|
20
|
+
const match = url.match(
|
|
21
|
+
/(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([^&?/]+)/,
|
|
22
|
+
);
|
|
23
|
+
return match?.[1] || null;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* Extract Vimeo video ID from URL
|
|
26
28
|
*/
|
|
27
29
|
function getVimeoId(url) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
if (!url) return null;
|
|
31
|
+
const match = url.match(/vimeo\.com\/(?:video\/)?(\d+)/);
|
|
32
|
+
return match?.[1] || null;
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* YouTube Player Component
|
|
35
37
|
*/
|
|
36
38
|
function YouTubePlayer({ videoId, autoplay, muted, loop, className }) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
39
|
+
const params = new URLSearchParams({
|
|
40
|
+
enablejsapi: "1",
|
|
41
|
+
autoplay: autoplay ? "1" : "0",
|
|
42
|
+
mute: muted ? "1" : "0",
|
|
43
|
+
loop: loop ? "1" : "0",
|
|
44
|
+
playlist: loop ? videoId : "",
|
|
45
|
+
rel: "0",
|
|
46
|
+
modestbranding: "1",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<iframe
|
|
51
|
+
src={`https://www.youtube.com/embed/${videoId}?${params}`}
|
|
52
|
+
className={className}
|
|
53
|
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
54
|
+
allowFullScreen
|
|
55
|
+
title="YouTube video"
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
/**
|
|
59
61
|
* Vimeo Player Component
|
|
60
62
|
*/
|
|
61
63
|
function VimeoPlayer({ videoId, autoplay, muted, loop, className }) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
64
|
+
const params = new URLSearchParams({
|
|
65
|
+
autoplay: autoplay ? "1" : "0",
|
|
66
|
+
muted: muted ? "1" : "0",
|
|
67
|
+
loop: loop ? "1" : "0",
|
|
68
|
+
dnt: "1",
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<iframe
|
|
73
|
+
src={`https://player.vimeo.com/video/${videoId}?${params}`}
|
|
74
|
+
className={className}
|
|
75
|
+
allow="autoplay; fullscreen; picture-in-picture"
|
|
76
|
+
allowFullScreen
|
|
77
|
+
title="Vimeo video"
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
/**
|
|
81
83
|
* Local/Direct Video Player Component
|
|
82
84
|
*/
|
|
83
|
-
function LocalVideo({
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
85
|
+
function LocalVideo({
|
|
86
|
+
src,
|
|
87
|
+
autoplay,
|
|
88
|
+
muted,
|
|
89
|
+
loop,
|
|
90
|
+
controls,
|
|
91
|
+
poster,
|
|
92
|
+
onProgress,
|
|
93
|
+
className,
|
|
94
|
+
}) {
|
|
95
|
+
const videoRef = useRef(null);
|
|
96
|
+
const [milestones, setMilestones] = useState({
|
|
97
|
+
25: false,
|
|
98
|
+
50: false,
|
|
99
|
+
75: false,
|
|
100
|
+
95: false,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
const video = videoRef.current;
|
|
105
|
+
if (!video || !onProgress) return;
|
|
106
|
+
|
|
107
|
+
const handleTimeUpdate = () => {
|
|
108
|
+
const percent = (video.currentTime / video.duration) * 100;
|
|
109
|
+
|
|
110
|
+
Object.entries({ 25: 25, 50: 50, 75: 75, 95: 95 }).forEach(
|
|
111
|
+
([key, threshold]) => {
|
|
112
|
+
if (percent >= threshold && !milestones[key]) {
|
|
113
|
+
setMilestones((prev) => ({ ...prev, [key]: true }));
|
|
114
|
+
onProgress({
|
|
115
|
+
milestone: key,
|
|
116
|
+
percent,
|
|
117
|
+
currentTime: video.currentTime,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
video.addEventListener("timeupdate", handleTimeUpdate);
|
|
125
|
+
return () => video.removeEventListener("timeupdate", handleTimeUpdate);
|
|
126
|
+
}, [milestones, onProgress]);
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<video
|
|
130
|
+
ref={videoRef}
|
|
131
|
+
src={src}
|
|
132
|
+
autoPlay={autoplay}
|
|
133
|
+
muted={muted}
|
|
134
|
+
loop={loop}
|
|
135
|
+
controls={controls}
|
|
136
|
+
poster={poster}
|
|
137
|
+
playsInline
|
|
138
|
+
className={className}
|
|
139
|
+
/>
|
|
140
|
+
);
|
|
119
141
|
}
|
|
120
142
|
|
|
121
143
|
/**
|
|
@@ -141,84 +163,85 @@ function LocalVideo({ src, autoplay, muted, loop, controls, poster, onProgress,
|
|
|
141
163
|
* <Media src="/videos/intro.mp4" controls className="w-full" />
|
|
142
164
|
*/
|
|
143
165
|
export function Media({
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
166
|
+
src,
|
|
167
|
+
media,
|
|
168
|
+
poster,
|
|
169
|
+
autoplay = false,
|
|
170
|
+
muted = false,
|
|
171
|
+
loop = false,
|
|
172
|
+
controls = true,
|
|
173
|
+
aspectRatio = "16/9",
|
|
174
|
+
className,
|
|
175
|
+
videoClassName,
|
|
176
|
+
onProgress,
|
|
177
|
+
...props
|
|
156
178
|
}) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
179
|
+
// Normalize source
|
|
180
|
+
const videoSrc =
|
|
181
|
+
typeof src === "string" ? src : src?.src || media?.src || "";
|
|
182
|
+
|
|
183
|
+
// Detect video type
|
|
184
|
+
const mediaType = detectMediaType(videoSrc);
|
|
185
|
+
|
|
186
|
+
// Render video player
|
|
187
|
+
const videoContent = (() => {
|
|
188
|
+
const playerClass = cn("w-full h-full", videoClassName);
|
|
189
|
+
|
|
190
|
+
switch (mediaType) {
|
|
191
|
+
case "youtube": {
|
|
192
|
+
const videoId = getYouTubeId(videoSrc);
|
|
193
|
+
if (!videoId) return null;
|
|
194
|
+
return (
|
|
195
|
+
<YouTubePlayer
|
|
196
|
+
videoId={videoId}
|
|
197
|
+
autoplay={autoplay}
|
|
198
|
+
muted={muted}
|
|
199
|
+
loop={loop}
|
|
200
|
+
className={playerClass}
|
|
201
|
+
/>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
case "vimeo": {
|
|
206
|
+
const videoId = getVimeoId(videoSrc);
|
|
207
|
+
if (!videoId) return null;
|
|
208
|
+
return (
|
|
209
|
+
<VimeoPlayer
|
|
210
|
+
videoId={videoId}
|
|
211
|
+
autoplay={autoplay}
|
|
212
|
+
muted={muted}
|
|
213
|
+
loop={loop}
|
|
214
|
+
className={playerClass}
|
|
215
|
+
/>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
case "video":
|
|
220
|
+
default:
|
|
221
|
+
return (
|
|
222
|
+
<LocalVideo
|
|
223
|
+
src={videoSrc}
|
|
224
|
+
autoplay={autoplay}
|
|
225
|
+
muted={muted}
|
|
226
|
+
loop={loop}
|
|
227
|
+
controls={controls}
|
|
228
|
+
poster={poster}
|
|
229
|
+
onProgress={onProgress}
|
|
230
|
+
className={playerClass}
|
|
231
|
+
/>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
})();
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<div
|
|
238
|
+
className={cn("relative overflow-hidden", className)}
|
|
239
|
+
style={{ aspectRatio }}
|
|
240
|
+
{...props}
|
|
241
|
+
>
|
|
242
|
+
{videoContent}
|
|
243
|
+
</div>
|
|
244
|
+
);
|
|
222
245
|
}
|
|
223
246
|
|
|
224
|
-
export default Media
|
|
247
|
+
export default Media;
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* @module @uniweb/kit/styled/Visual
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import React from
|
|
11
|
-
import { Media } from
|
|
12
|
-
import { Image } from
|
|
13
|
-
import { getChildBlockRenderer } from
|
|
10
|
+
import React from "react";
|
|
11
|
+
import { Media } from "../../components/Media/Media.jsx";
|
|
12
|
+
import { Image } from "../../components/Image/index.js";
|
|
13
|
+
import { getChildBlockRenderer } from "../../utils/index.js";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Renders the first non-empty visual from the given candidates.
|
|
@@ -35,18 +35,18 @@ import { getChildBlockRenderer } from '../../utils/index.js'
|
|
|
35
35
|
* <Visual image={content.images[1]} className="aspect-video" />
|
|
36
36
|
*/
|
|
37
37
|
export function Visual({ inset, video, image, className, fallback = null }) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
if (inset) {
|
|
39
|
+
const Renderer = getChildBlockRenderer();
|
|
40
|
+
return <Renderer blocks={[inset]} as="div" extra={{ className }} />;
|
|
41
|
+
}
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
if (video) {
|
|
44
|
+
return <Media {...video} className={className} />;
|
|
45
|
+
}
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
if (image) {
|
|
48
|
+
return <Image {...image} className={className} />;
|
|
49
|
+
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
return fallback;
|
|
52
52
|
}
|