Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion assets/preview.less
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
}
}

&-img {
&-img,
&-video {
max-width: 100%;
max-height: 70%;
}
Expand Down
8 changes: 8 additions & 0 deletions docs/demo/previewvideo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: previewvideo
nav:
title: Demo
path: /demo
---

<code src="../examples/previewvideo.tsx"></code>
42 changes: 42 additions & 0 deletions docs/examples/previewvideo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Image from '@rc-component/image';
import * as React from 'react';
import '../../assets/index.less';
import { defaultIcons } from './common';

export default function PreviewVideo() {
return (
<div>
<Image
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/auto-orient,1/resize,p_10/quality,q_10"
preview={{
icons: defaultIcons,
type: 'video',
src: 'https://gw.alipayobjects.com/os/rmsportal/NTMlQdLIkPjOACXsdRrq.mp4',
}}
width={200}
/>

<br />
<h1>PreviewGroup</h1>
<Image.PreviewGroup preview={{ icons: defaultIcons }}>
<Image
key={1}
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
preview={{
src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
}}
width={200}
/>
<Image
key={2}
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
preview={{
type: 'video',
src: 'https://gw.alipayobjects.com/os/rmsportal/NTMlQdLIkPjOACXsdRrq.mp4',
}}
width={200}
/>
</Image.PreviewGroup>
</div>
);
}
5 changes: 4 additions & 1 deletion src/Image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ const ImageInternal: CompoundedComponent<ImageProps> = props => {
const canPreview = !!preview;

const {
type: previewType = 'image',
src: previewSrc,
open: previewOpen,
onOpenChange: onPreviewOpenChange,
Expand Down Expand Up @@ -178,8 +179,9 @@ const ImageInternal: CompoundedComponent<ImageProps> = props => {
() => ({
...imgCommonProps,
src,
type: previewType,
}),
[src, imgCommonProps],
[src, imgCommonProps, previewType],
);

const imageId = useRegisterImage(canPreview, registerData);
Expand Down Expand Up @@ -266,6 +268,7 @@ const ImageInternal: CompoundedComponent<ImageProps> = props => {
prefixCls={previewPrefixCls}
onClose={onPreviewClose}
mousePosition={mousePosition}
type={previewType}
src={src}
alt={alt}
imageInfo={{ width, height }}
Expand Down
94 changes: 54 additions & 40 deletions src/Preview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ export interface InternalPreviewConfig {
/** Better to use `classNames.root` instead */
rootClassName?: string;

// Image
// Media
type?: 'image' | 'video';
src?: string;
alt?: string;

Expand Down Expand Up @@ -121,7 +122,7 @@ export interface PreviewProps extends InternalPreviewConfig {
};
fallback?: string;

// Preview image
// Preview media
imgCommonProps?: React.ImgHTMLAttributes<HTMLImageElement>;
width?: string | number;
height?: string | number;
Expand Down Expand Up @@ -165,6 +166,7 @@ const Preview: React.FC<PreviewProps> = props => {
const {
prefixCls,
rootClassName,
type = 'image',
src,
alt,
imageInfo,
Expand Down Expand Up @@ -425,13 +427,24 @@ const Preview: React.FC<PreviewProps> = props => {
{/* Body */}
<div className={classnames(`${prefixCls}-body`, classNames.body)} style={bodyStyle}>
{/* Preview Image */}
{imageRender
? imageRender(imgNode, {
transform,
image,
...(groupContext ? { current } : {}),
})
: imgNode}
{type === 'image' &&
(imageRender
? imageRender(imgNode, {
transform,
image,
...(groupContext ? { current } : {}),
})
: imgNode)}
{type === 'video' && (
<video
className={`${prefixCls}-video`}
src={src}
width={props.width}
height={props.height}
controls
autoPlay
/>
)}
Comment on lines +430 to +447
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

视频渲染未透传 videoCommonProps 且默认 autoPlay 有 UX/兼容隐患

  • 未使用已暴露的 videoCommonProps,用户无法配置 controls/poster/preload/事件 等;
  • 默认 autoPlay 在多数浏览器会被静音门槛或策略阻断,建议默认不自动播放(或仅在 muted 时启用);
  • 可读性与无障碍:建议使用 aria-label={alt}
  • 建议允许 videoCommonProps.src 覆盖顶层 src
-                {type === 'video' && (
-                  <video
-                    className={`${prefixCls}-video`}
-                    src={src}
-                    width={props.width}
-                    height={props.height}
-                    controls
-                    autoPlay
-                  />
-                )}
+                {type === 'video' && (
+                  <video
+                    className={`${prefixCls}-video`}
+                    aria-label={alt}
+                    // 允许调用方在 videoCommonProps 中覆盖 src
+                    src={props.videoCommonProps?.src ?? src}
+                    width={props.width}
+                    height={props.height}
+                    // 默认开启基础控件与内联播放,可被调用方覆盖
+                    controls={props.videoCommonProps?.controls ?? true}
+                    playsInline={props.videoCommonProps?.playsInline ?? true}
+                    preload={props.videoCommonProps?.preload ?? 'metadata'}
+                    // 默认不自动播放,交由调用方显式开启
+                    autoPlay={props.videoCommonProps?.autoPlay}
+                    {...props.videoCommonProps}
+                  />
+                )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{type === 'image' &&
(imageRender
? imageRender(imgNode, {
transform,
image,
...(groupContext ? { current } : {}),
})
: imgNode)}
{type === 'video' && (
<video
className={`${prefixCls}-video`}
src={src}
width={props.width}
height={props.height}
controls
autoPlay
/>
)}
{type === 'image' &&
(imageRender
? imageRender(imgNode, {
transform,
image,
...(groupContext ? { current } : {}),
})
: imgNode)}
{type === 'video' && (
<video
className={`${prefixCls}-video`}
aria-label={alt}
// 允许调用方在 videoCommonProps 中覆盖 src
src={props.videoCommonProps?.src ?? src}
width={props.width}
height={props.height}
// 默认开启基础控件与内联播放,可被调用方覆盖
controls={props.videoCommonProps?.controls ?? true}
playsInline={props.videoCommonProps?.playsInline ?? true}
preload={props.videoCommonProps?.preload ?? 'metadata'}
// 默认不自动播放,交由调用方显式开启
autoPlay={props.videoCommonProps?.autoPlay}
{...props.videoCommonProps}
/>
)}
🤖 Prompt for AI Agents
In src/Preview/index.tsx around lines 431 to 448, the <video> element does not
forward the exposed videoCommonProps, forces autoPlay by default, lacks
aria-label for accessibility, and doesn't let videoCommonProps.src override the
top-level src; fix by spreading videoCommonProps onto the video element (after
explicitly setting className, width, height so caller props can override), use
videoCommonProps.src ?? src for the source, remove unconditional autoPlay and
only set autoPlay when videoCommonProps.autoPlay is true or when muted is true
(e.g., autoPlay: videoCommonProps.autoPlay || videoCommonProps.muted), ensure
controls/poster/preload and event handlers from videoCommonProps are supported,
and add aria-label={alt} for accessibility.

</div>

{/* Close Button */}
Expand All @@ -454,37 +467,38 @@ const Preview: React.FC<PreviewProps> = props => {
/>
)}

{/* Footer */}
<Footer
prefixCls={prefixCls}
showProgress={showOperationsProgress}
current={current}
count={count}
showSwitch={showLeftOrRightSwitches}
// Style
classNames={classNames}
styles={styles}
// Render
image={image}
transform={transform}
icons={icons}
countRender={countRender}
actionsRender={actionsRender}
// Scale
scale={scale}
minScale={minScale}
maxScale={maxScale}
// Actions
onActive={onActive}
onFlipY={onFlipY}
onFlipX={onFlipX}
onRotateLeft={onRotateLeft}
onRotateRight={onRotateRight}
onZoomOut={onZoomOut}
onZoomIn={onZoomIn}
onClose={onClose}
onReset={onReset}
/>
{type === 'image' && (
<Footer
prefixCls={prefixCls}
showProgress={showOperationsProgress}
current={current}
count={count}
showSwitch={showLeftOrRightSwitches}
// Style
classNames={classNames}
styles={styles}
// Render
image={image}
transform={transform}
icons={icons}
countRender={countRender}
actionsRender={actionsRender}
// Scale
scale={scale}
minScale={minScale}
maxScale={maxScale}
// Actions
onActive={onActive}
onFlipY={onFlipY}
onFlipX={onFlipX}
onRotateLeft={onRotateLeft}
onRotateRight={onRotateRight}
onZoomOut={onZoomOut}
onZoomIn={onZoomIn}
onClose={onClose}
onReset={onReset}
/>
)}
</div>
);
}}
Expand Down
3 changes: 2 additions & 1 deletion src/PreviewGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const Group: React.FC<PreviewGroupProps> = ({
const [keepOpenIndex, setKeepOpenIndex] = useState(false);

// >>> Image
const { src, ...imgCommonProps } = mergedItems[current]?.data || {};
const { src, type, ...imgCommonProps } = mergedItems[current]?.data || {};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

从数据中额外取出 alt 与 videoCommonProps,避免属性丢失并完成视频接线

当前将除 src/type 外的字段全部并入 imgCommonProps,会让 alt 被当作 img 属性下发且在预览端被覆盖;同时未传递 videoCommonProps。建议如下解构:

-  const { src, type, ...imgCommonProps } = mergedItems[current]?.data || {};
+  const { src, type, alt, videoCommonProps, ...imgCommonProps } =
+    mergedItems[current]?.data || {};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { src, type, ...imgCommonProps } = mergedItems[current]?.data || {};
const { src, type, alt, videoCommonProps, ...imgCommonProps } =
mergedItems[current]?.data || {};
🤖 Prompt for AI Agents
In src/PreviewGroup.tsx around line 70, the current destructure pulls all
non-src/type fields into imgCommonProps causing alt to be treated as an img prop
and dropped/overwritten and videoCommonProps to be omitted; change the
destructure to pull out alt and videoCommonProps explicitly (e.g. const { src,
type, alt, videoCommonProps, ...imgCommonProps } = mergedItems[current]?.data ||
{}), then use alt for the img element and pass videoCommonProps into the video
element when rendering so both alt and video-specific props are preserved.

// >>> Visible
const [isShowPreview, setShowPreview] = useMergedState(!!previewOpen, {
value: previewOpen,
Expand Down Expand Up @@ -136,6 +136,7 @@ const Group: React.FC<PreviewGroupProps> = ({
mousePosition={mousePosition}
imgCommonProps={imgCommonProps}
src={src}
type={type}
fallback={fallback}
icons={icons}
current={current}
Expand Down
2 changes: 1 addition & 1 deletion src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type ImageElementProps = Pick<
| 'srcSet'
| 'useMap'
| 'alt'
>;
> & { type?: 'image' | 'video' };

export type PreviewImageElementProps = {
data: ImageElementProps;
Expand Down