From e5cb8328208abe78ce669ba2ea458e99b91efc86 Mon Sep 17 00:00:00 2001
From: bqy_fe <1743369777@qq.com>
Date: Mon, 11 Apr 2022 01:34:12 +0800
Subject: [PATCH 01/13] feat(image): add new features
---
components/_util/EventInterface.ts | 1 +
components/image/PreviewGroup.tsx | 19 +++
.../__tests__/__snapshots__/demo.test.js.snap | 56 ++++++--
components/image/demo/index.vue | 5 +-
.../image/demo/preview-group-visible.vue | 2 +-
components/image/demo/preview-src.vue | 31 +++++
components/image/index.tsx | 40 +++++-
components/image/index.zh-CN.md | 5 +
components/vc-dialog/IDialogPropTypes.ts | 2 +-
components/vc-image/src/Image.tsx | 72 ++++++++---
components/vc-image/src/Preview.tsx | 102 ++++++++++-----
components/vc-image/src/PreviewGroup.tsx | 122 +++++++++++++++---
12 files changed, 368 insertions(+), 89 deletions(-)
create mode 100644 components/image/demo/preview-src.vue
diff --git a/components/_util/EventInterface.ts b/components/_util/EventInterface.ts
index c8a4551c2f..b72777cf50 100644
--- a/components/_util/EventInterface.ts
+++ b/components/_util/EventInterface.ts
@@ -4,6 +4,7 @@ export type KeyboardEventHandler = (e: KeyboardEvent) => void;
export type CompositionEventHandler = (e: CompositionEvent) => void;
export type ClipboardEventHandler = (e: ClipboardEvent) => void;
export type ChangeEventHandler = (e: ChangeEvent) => void;
+export type WheelEventHandler = (e: WheelEvent) => void;
export type ChangeEvent = Event & {
target: {
value?: string | undefined;
diff --git a/components/image/PreviewGroup.tsx b/components/image/PreviewGroup.tsx
index 0666e2399b..47cdc49e64 100644
--- a/components/image/PreviewGroup.tsx
+++ b/components/image/PreviewGroup.tsx
@@ -2,6 +2,24 @@ import PreviewGroup from '../vc-image/src/PreviewGroup';
import { computed, defineComponent } from 'vue';
import useConfigInject from '../_util/hooks/useConfigInject';
+import RotateLeftOutlined from '@ant-design/icons-vue/RotateLeftOutlined';
+import RotateRightOutlined from '@ant-design/icons-vue/RotateRightOutlined';
+import ZoomInOutlined from '@ant-design/icons-vue/ZoomInOutlined';
+import ZoomOutOutlined from '@ant-design/icons-vue/ZoomOutOutlined';
+import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
+import LeftOutlined from '@ant-design/icons-vue/LeftOutlined';
+import RightOutlined from '@ant-design/icons-vue/RightOutlined';
+
+export const icons = {
+ rotateLeft: ,
+ rotateRight: ,
+ zoomIn: ,
+ zoomOut: ,
+ close: ,
+ left: ,
+ right: ,
+};
+
const InternalPreviewGroup = defineComponent({
name: 'AImagePreviewGroup',
inheritAttrs: false,
@@ -13,6 +31,7 @@ const InternalPreviewGroup = defineComponent({
return (
diff --git a/components/image/__tests__/__snapshots__/demo.test.js.snap b/components/image/__tests__/__snapshots__/demo.test.js.snap
index a357eefd0b..aff13950da 100644
--- a/components/image/__tests__/__snapshots__/demo.test.js.snap
+++ b/components/image/__tests__/__snapshots__/demo.test.js.snap
@@ -3,8 +3,11 @@
exports[`renders ./components/image/demo/basic.vue correctly 1`] = `

-
+
+
`;
exports[`renders ./components/image/demo/controlled-preview.vue correctly 1`] = `
@@ -13,7 +16,9 @@ exports[`renders ./components/image/demo/controlled-preview.vue correctly 1`] =

-
+
@@ -22,8 +27,11 @@ exports[`renders ./components/image/demo/controlled-preview.vue correctly 1`] =
exports[`renders ./components/image/demo/fallback.vue correctly 1`] = `

-
+
+
`;
exports[`renders ./components/image/demo/placeholder.vue correctly 1`] = `
@@ -37,7 +45,9 @@ exports[`renders ./components/image/demo/placeholder.vue correctly 1`] = `
-
+
@@ -52,35 +62,61 @@ exports[`renders ./components/image/demo/placeholder.vue correctly 1`] = `
exports[`renders ./components/image/demo/preview-group.vue correctly 1`] = `

-
+
+

-
+
+
+
`;
exports[`renders ./components/image/demo/preview-group-visible.vue correctly 1`] = `

-
+
+
`;
+
+exports[`renders ./components/image/demo/preview-src.vue correctly 1`] = `
+
+
+
+
+
+`;
diff --git a/components/image/demo/index.vue b/components/image/demo/index.vue
index f7989c94c6..5fc0911b80 100644
--- a/components/image/demo/index.vue
+++ b/components/image/demo/index.vue
@@ -4,8 +4,9 @@
-
+
+
@@ -13,6 +14,7 @@
import Basic from './basic.vue';
import Fallback from './fallback.vue';
import Placeholder from './placeholder.vue';
+import previewSrc from './preview-src.vue';
import PreviewGroup from './preview-group.vue';
import ControlledPreview from './controlled-preview.vue';
import previewGroupVisibleVue from './preview-group-visible.vue';
@@ -26,6 +28,7 @@ export default defineComponent({
components: {
Basic,
Fallback,
+ previewSrc,
Placeholder,
PreviewGroup,
ControlledPreview,
diff --git a/components/image/demo/preview-group-visible.vue b/components/image/demo/preview-group-visible.vue
index 75e386293d..1e3a2f2837 100644
--- a/components/image/demo/preview-group-visible.vue
+++ b/components/image/demo/preview-group-visible.vue
@@ -18,7 +18,7 @@ Preview a collection from one image.
+---
+order: 4
+title:
+ zh-CN: 自定义预览图片
+ en-US: Custom preview image
+---
+
+## zh-CN
+
+可以设置不同的预览图片。
+
+## en-US
+
+You can set different preview image.
+
+
+
+
+
+
+
diff --git a/components/image/index.tsx b/components/image/index.tsx
index 5001a7e728..67e3430e11 100644
--- a/components/image/index.tsx
+++ b/components/image/index.tsx
@@ -1,9 +1,12 @@
import type { App, ExtractPropTypes, ImgHTMLAttributes, Plugin } from 'vue';
-import { defineComponent } from 'vue';
+import { defineComponent, computed } from 'vue';
import ImageInternal from '../vc-image';
import { imageProps } from '../vc-image/src/Image';
+import defaultLocale from '../locale/en_US';
import useConfigInject from '../_util/hooks/useConfigInject';
-import PreviewGroup from './PreviewGroup';
+import PreviewGroup, { icons } from './PreviewGroup';
+import EyeOutlined from '@ant-design/icons-vue/EyeOutlined';
+import { getTransitionName } from '../_util/transition';
export type ImageProps = Partial<
ExtractPropTypes> &
@@ -14,11 +17,42 @@ const Image = defineComponent({
inheritAttrs: false,
props: imageProps() as any,
setup(props, { slots, attrs }) {
- const { prefixCls } = useConfigInject('image', props);
+ const { prefixCls, rootPrefixCls, configProvider } = useConfigInject('image', props);
+
+ const mergedPreview = computed(() => {
+ const { preview } = props;
+ const imageLocale = configProvider.locale?.Image || defaultLocale.Image;
+
+ if (preview === false) {
+ return preview;
+ }
+ const _preview = typeof preview === 'object' ? preview : {};
+
+ return {
+ mask: slots.previewMask ? (
+ slots.previewMask()
+ ) : (
+
+
+ {imageLocale?.preview}
+
+ ),
+ icons,
+ ..._preview,
+ transitionName: getTransitionName(rootPrefixCls.value, 'zoom', _preview.transitionName),
+ maskTransitionName: getTransitionName(
+ rootPrefixCls.value,
+ 'fade',
+ _preview.maskTransitionName,
+ ),
+ };
+ });
+
return () => {
return (
);
diff --git a/components/image/index.zh-CN.md b/components/image/index.zh-CN.md
index d73293594b..a37b787cfd 100644
--- a/components/image/index.zh-CN.md
+++ b/components/image/index.zh-CN.md
@@ -24,6 +24,7 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/D1dXz9PZqa/image.svg
| preview | 预览参数,为 `false` 时禁用 | boolean \| [previewType](#previewType) | true | 2.0.0 |
| src | 图片地址 | string | - | 2.0.0 |
| width | 图像宽度 | string \| number | - | 2.0.0 |
+| onError | 加载错误回调 | (event: Event) => void | - | |
### previewType
@@ -32,6 +33,10 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/D1dXz9PZqa/image.svg
visible?: boolean;
onVisibleChange?: (visible, prevVisible) => void;
getContainer: string | HTMLElement | (() => HTMLElement);
+ src?: string;
+ mask?: VNode;
+ maskClassName?: string;
+ current?: number;
}
```
diff --git a/components/vc-dialog/IDialogPropTypes.ts b/components/vc-dialog/IDialogPropTypes.ts
index 1c78abf2ef..c6abe677d4 100644
--- a/components/vc-dialog/IDialogPropTypes.ts
+++ b/components/vc-dialog/IDialogPropTypes.ts
@@ -1,7 +1,7 @@
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
import PropTypes from '../_util/vue-types';
-function dialogPropTypes() {
+export function dialogPropTypes() {
return {
keyboard: { type: Boolean, default: undefined },
mask: { type: Boolean, default: undefined },
diff --git a/components/vc-image/src/Image.tsx b/components/vc-image/src/Image.tsx
index 79ee7b9845..c674079497 100644
--- a/components/vc-image/src/Image.tsx
+++ b/components/vc-image/src/Image.tsx
@@ -1,21 +1,29 @@
-import type { ImgHTMLAttributes, CSSProperties, PropType } from 'vue';
-import { ref, watch, defineComponent, computed, onMounted } from 'vue';
+import type { ImgHTMLAttributes, CSSProperties, PropType, VNode } from 'vue';
+import { ref, watch, defineComponent, computed, onMounted, onBeforeUnmount } from 'vue';
import isNumber from 'lodash-es/isNumber';
import cn from '../../_util/classNames';
import PropTypes from '../../_util/vue-types';
import { getOffset } from '../../vc-util/Dom/css';
-
-import type { MouseEventHandler } from './Preview';
import Preview from './Preview';
+import type { MouseEventHandler } from '../../_util/EventInterface';
import PreviewGroup, { context } from './PreviewGroup';
-
+import type { IDialogChildProps } from '../../vc-dialog/IDialogPropTypes';
export type GetContainer = string | HTMLElement | (() => HTMLElement);
-export interface ImagePreviewType {
+import type { PreviewProps } from './Preview';
+
+export type ImagePreviewType = Omit<
+ IDialogChildProps,
+ 'mask' | 'visible' | 'closable' | 'prefixCls' | 'onClose' | 'afterClose' | 'wrapClassName'
+> & {
+ src?: string;
visible?: boolean;
onVisibleChange?: (value: boolean, prevValue: boolean) => void;
getContainer?: GetContainer | false;
-}
+ mask?: VNode | VNode[];
+ maskClassName?: string;
+ icons?: PreviewProps['icons'];
+};
export interface ImagePropsType extends Omit {
// Original
@@ -27,6 +35,8 @@ export interface ImagePropsType extends Omit ({
src: String,
@@ -40,10 +50,17 @@ export const imageProps = () => ({
type: [Boolean, Object] as PropType,
default: true as boolean | ImagePreviewType,
},
+ onClick: {
+ type: Function as PropType,
+ },
+ onError: {
+ type: Function as PropType,
+ },
});
-type ImageStatus = 'normal' | 'error' | 'loading';
+export type ImageProps = Partial>;
+export type ImageStatus = 'normal' | 'error' | 'loading';
-const mergeDefaultValue = (obj: T, defaultValues: object): T => {
+export const mergeDefaultValue = (obj: T, defaultValues: object): T => {
const res = { ...obj };
Object.keys(defaultValues).forEach(key => {
if (obj[key] === undefined) {
@@ -57,11 +74,11 @@ const ImageInternal = defineComponent({
name: 'Image',
inheritAttrs: false,
props: imageProps(),
- emits: ['click'],
+ emits: ['click', 'error'],
setup(props, { attrs, slots, emit }) {
const prefixCls = computed(() => props.prefixCls);
const previewPrefixCls = computed(() => `${prefixCls.value}-preview`);
- const preview = computed(() => {
+ const preview = computed(() => {
const defaultValues = {
visible: undefined,
onVisibleChange: () => {},
@@ -80,8 +97,8 @@ const ImageInternal = defineComponent({
const isControlled = computed(() => previewVisible.value !== undefined);
const isShowPreview = ref(!!previewVisible.value);
- watch(previewVisible, () => {
- isShowPreview.value = !!previewVisible.value;
+ watch(previewVisible, val => {
+ isShowPreview.value = Boolean(val);
});
watch(isShowPreview, (val, preVal) => {
onPreviewVisibleChange.value(val, preVal);
@@ -108,13 +125,15 @@ const ImageInternal = defineComponent({
const onLoad = () => {
status.value = 'normal';
};
- const onError = () => {
+ const onError = (e: Event) => {
status.value = 'error';
+ emit('error', e);
};
const onPreview: MouseEventHandler = e => {
if (!isControlled.value) {
const { left, top } = getOffset(e.target);
+
if (isPreviewGroup.value) {
setCurrent(currentId.value);
setGroupMousePosition({
@@ -163,7 +182,7 @@ const ImageInternal = defineComponent({
return () => {};
}
- unRegister = registerImage(currentId.value, props.src);
+ unRegister = registerImage(currentId.value, props.src, canPreview.value);
if (!canPreview.value) {
unRegister();
@@ -172,13 +191,20 @@ const ImageInternal = defineComponent({
{ flush: 'post', immediate: true },
);
});
+ onBeforeUnmount(unRegister);
const toSizePx = (l: number | string) => {
if (isNumber(l)) return l + 'px';
return l;
};
return () => {
- const { prefixCls, wrapperClassName, fallback, src, preview, placeholder, wrapperStyle } =
- props;
+ const {
+ prefixCls,
+ wrapperClassName,
+ fallback,
+ src: imgSrc,
+ placeholder,
+ wrapperStyle,
+ } = props;
const {
width,
height,
@@ -191,11 +217,12 @@ const ImageInternal = defineComponent({
class: cls,
style,
} = attrs as ImgHTMLAttributes;
+ const { icons, mask: previewMask, src: previewSrc, ...dialogProps } = preview.value;
+
const wrappperClass = cn(prefixCls, wrapperClassName, {
[`${prefixCls}-error`]: isError.value,
});
- const mergedSrc = isError.value && fallback ? fallback : src;
- const previewMask = slots.previewMask && slots.previewMask();
+ const mergedSrc = isError.value && fallback ? fallback : previewSrc ?? imgSrc;
const imgCommonProps = {
crossorigin,
decoding,
@@ -215,12 +242,13 @@ const ImageInternal = defineComponent({
...(style as CSSProperties),
},
};
+
return (
<>
{
emit('click', e);
@@ -238,7 +266,7 @@ const ImageInternal = defineComponent({
? {
src: fallback,
}
- : { onLoad, onError, src })}
+ : { onLoad, onError, src: imgSrc })}
ref={img}
/>
@@ -254,6 +282,7 @@ const ImageInternal = defineComponent({
{!isPreviewGroup.value && canPreview.value && (
)}
>
diff --git a/components/vc-image/src/Preview.tsx b/components/vc-image/src/Preview.tsx
index 55e9f4b1d0..b2d8c72634 100644
--- a/components/vc-image/src/Preview.tsx
+++ b/components/vc-image/src/Preview.tsx
@@ -1,47 +1,68 @@
-import { computed, defineComponent, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
-import RotateLeftOutlined from '@ant-design/icons-vue/RotateLeftOutlined';
-import RotateRightOutlined from '@ant-design/icons-vue/RotateRightOutlined';
-import ZoomInOutlined from '@ant-design/icons-vue/ZoomInOutlined';
-import ZoomOutOutlined from '@ant-design/icons-vue/ZoomOutOutlined';
-import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
-import LeftOutlined from '@ant-design/icons-vue/LeftOutlined';
-import RightOutlined from '@ant-design/icons-vue/RightOutlined';
+import {
+ computed,
+ defineComponent,
+ onMounted,
+ onUnmounted,
+ reactive,
+ ref,
+ watch,
+ createVNode,
+} from 'vue';
+import type { VNode, PropType } from 'vue';
import classnames from '../../_util/classNames';
import Dialog from '../../vc-dialog';
-import getIDialogPropTypes from '../../vc-dialog/IDialogPropTypes';
+import { type IDialogChildProps, dialogPropTypes } from '../../vc-dialog/IDialogPropTypes';
import { getOffset } from '../../vc-util/Dom/css';
import addEventListener from '../../vc-util/Dom/addEventListener';
import { warning } from '../../vc-util/warning';
import useFrameSetState from './hooks/useFrameSetState';
import getFixScaleEleTransPosition from './getFixScaleEleTransPosition';
+import type { MouseEventHandler, WheelEventHandler } from '../../_util/EventInterface';
import { context } from './PreviewGroup';
-const IDialogPropTypes = getIDialogPropTypes();
-export type MouseEventHandler = (payload: MouseEvent) => void;
-
-export interface PreviewProps extends Omit {
+export interface PreviewProps extends Omit {
onClose?: (e: Element) => void;
src?: string;
alt?: string;
+ mask?: VNode;
+ rootClassName?: string;
+ icons?: {
+ rotateLeft?: VNode;
+ rotateRight?: VNode;
+ zoomIn?: VNode;
+ zoomOut?: VNode;
+ close?: VNode;
+ left?: VNode;
+ right?: VNode;
+ };
}
const initialPosition = {
x: 0,
y: 0,
};
-const PreviewType = {
+export const previewProps = {
+ ...dialogPropTypes(),
src: String,
alt: String,
- ...IDialogPropTypes,
+ icons: {
+ type: Object as PropType,
+ default: () => ({} as PreviewProps['icons']),
+ },
+ mask: {
+ type: Object as PropType,
+ },
};
const Preview = defineComponent({
name: 'Preview',
inheritAttrs: false,
- props: PreviewType,
+ props: previewProps,
emits: ['close', 'afterClose'],
setup(props, { emit, attrs }) {
+ const { rotateLeft, rotateRight, zoomIn, zoomOut, close, left, right } = props.icons;
+
const scale = ref(1);
const rotate = ref(0);
const [position, setPosition] = useFrameSetState<{
@@ -65,17 +86,16 @@ const Preview = defineComponent({
const isMoving = ref(false);
const groupContext = context.inject();
const { previewUrls, current, isPreviewGroup, setCurrent } = groupContext;
- const previewGroupCount = computed(() => Object.keys(previewUrls).length);
- const previewUrlsKeys = computed(() => Object.keys(previewUrls));
- const currentPreviewIndex = computed(() =>
- previewUrlsKeys.value.indexOf(String(current.value)),
- );
- const combinationSrc = computed(() =>
- isPreviewGroup.value ? previewUrls[current.value] : props.src,
- );
+ const previewGroupCount = computed(() => previewUrls.value.size);
+ const previewUrlsKeys = computed(() => Array.from(previewUrls.value.keys()));
+ const currentPreviewIndex = computed(() => previewUrlsKeys.value.indexOf(current.value));
+ const combinationSrc = computed(() => {
+ return isPreviewGroup.value ? previewUrls.value.get(current.value) : props.src;
+ });
const showLeftOrRightSwitches = computed(
() => isPreviewGroup.value && previewGroupCount.value > 1,
);
+ const lastWheelZoomDirection = ref({ wheelDirection: 0 });
const onAfterClose = () => {
scale.value = 1;
@@ -106,7 +126,7 @@ const Preview = defineComponent({
// Without this mask close will abnormal
event.stopPropagation();
if (currentPreviewIndex.value > 0) {
- setCurrent(previewUrlsKeys.value[String(currentPreviewIndex.value - 1)]);
+ setCurrent(previewUrlsKeys.value[currentPreviewIndex.value - 1]);
}
};
@@ -115,7 +135,7 @@ const Preview = defineComponent({
// Without this mask close will abnormal
event.stopPropagation();
if (currentPreviewIndex.value < previewGroupCount.value - 1) {
- setCurrent(previewUrlsKeys.value[String(currentPreviewIndex.value + 1)]);
+ setCurrent(previewUrlsKeys.value[currentPreviewIndex.value + 1]);
}
};
@@ -126,28 +146,28 @@ const Preview = defineComponent({
const iconClassName = `${props.prefixCls}-operations-icon`;
const tools = [
{
- icon: CloseOutlined,
+ icon: close,
onClick: onClose,
type: 'close',
},
{
- icon: ZoomInOutlined,
+ icon: zoomIn,
onClick: onZoomIn,
type: 'zoomIn',
},
{
- icon: ZoomOutOutlined,
+ icon: zoomOut,
onClick: onZoomOut,
type: 'zoomOut',
disabled: computed(() => scale.value === 1),
},
{
- icon: RotateRightOutlined,
+ icon: rotateRight,
onClick: onRotateRight,
type: 'rotateRight',
},
{
- icon: RotateLeftOutlined,
+ icon: rotateLeft,
onClick: onRotateLeft,
type: 'rotateLeft',
},
@@ -175,6 +195,8 @@ const Preview = defineComponent({
};
const onMouseDown: MouseEventHandler = event => {
+ // Only allow main button
+ if (event.button !== 0) return;
event.preventDefault();
// Without this mask close will abnormal
event.stopPropagation();
@@ -193,6 +215,14 @@ const Preview = defineComponent({
});
}
};
+
+ const onWheelMove: WheelEventHandler = event => {
+ if (!props.visible) return;
+ event.preventDefault();
+ const wheelDirection = event.deltaY;
+ lastWheelZoomDirection.value = { wheelDirection };
+ };
+
let removeListeners = () => {};
onMounted(() => {
watch(
@@ -204,6 +234,9 @@ const Preview = defineComponent({
const onMouseUpListener = addEventListener(window, 'mouseup', onMouseUp, false);
const onMouseMoveListener = addEventListener(window, 'mousemove', onMouseMove, false);
+ const onScrollWheelListener = addEventListener(window, 'wheel', onWheelMove, {
+ passive: false,
+ });
try {
// Resolve if in iframe lost event
@@ -225,6 +258,7 @@ const Preview = defineComponent({
removeListeners = () => {
onMouseUpListener.remove();
onMouseMoveListener.remove();
+ onScrollWheelListener.remove();
/* istanbul ignore next */
if (onTopMouseUpListener) onTopMouseUpListener.remove();
@@ -263,7 +297,7 @@ const Preview = defineComponent({
onClick={onClick}
key={type}
>
-
+ {createVNode(IconType, { class: iconClassName })}
))}
@@ -291,7 +325,7 @@ const Preview = defineComponent({
})}
onClick={onSwitchLeft}
>
-
+ {left}
)}
{showLeftOrRightSwitches.value && (
@@ -302,7 +336,7 @@ const Preview = defineComponent({
})}
onClick={onSwitchRight}
>
-
+ {right}
)}
diff --git a/components/vc-image/src/PreviewGroup.tsx b/components/vc-image/src/PreviewGroup.tsx
index 0b65c6cd7b..466e613633 100644
--- a/components/vc-image/src/PreviewGroup.tsx
+++ b/components/vc-image/src/PreviewGroup.tsx
@@ -1,19 +1,39 @@
-import type { Ref } from 'vue';
-import { ref, provide, defineComponent, inject, reactive } from 'vue';
+import { PropType, Ref, ComputedRef } from 'vue';
+import { ref, provide, defineComponent, inject, watch, reactive, computed, watchEffect } from 'vue';
+import { ImagePreviewType, mergeDefaultValue } from './Image';
import Preview from './Preview';
+import type { PreviewProps } from './Preview';
+
+export interface PreviewGroupPreview
+ extends Omit {
+ /**
+ * If Preview the show img index
+ * @default 0
+ */
+ current?: number;
+}
export interface GroupConsumerProps {
previewPrefixCls?: string;
+ icons?: PreviewProps['icons'];
+ preview?: boolean | PreviewGroupPreview;
}
+
+interface PreviewUrl {
+ url: string;
+ canPreview: boolean;
+}
+
export interface GroupConsumerValue extends GroupConsumerProps {
isPreviewGroup?: Ref;
- previewUrls: Record;
- setPreviewUrls: (previewUrls: Record) => void;
+ previewUrls: ComputedRef