diff --git a/src/Image.tsx b/src/Image.tsx index 30ab9ce1..d3ae4d58 100644 --- a/src/Image.tsx +++ b/src/Image.tsx @@ -1,4 +1,4 @@ -import { isReactRenderable, useControlledState } from '@rc-component/util'; +import { isNonNullable, isReactRenderable, useControlledState } from '@rc-component/util'; import { clsx } from 'clsx'; import * as React from 'react'; import { useContext, useMemo, useState } from 'react'; @@ -43,8 +43,10 @@ export interface PreviewConfig extends Omit, 'placeholder' | 'onClick' | 'onKeyDown'> { +export interface ImageProps extends Omit< + React.ImgHTMLAttributes, + 'placeholder' | 'onClick' | 'onKeyDown' +> { // Misc prefixCls?: string; previewPrefixCls?: string; @@ -127,15 +129,17 @@ const ImageInternal: CompoundedComponent = props => { ...restProps }: PreviewConfig = preview && typeof preview === 'object' ? preview : {}; - const coverPlacement = - typeof cover === 'object' && (cover as CoverConfig).placement - ? (cover as CoverConfig).placement || 'center' - : 'center'; - - const coverNode = - typeof cover === 'object' && (cover as CoverConfig).coverNode - ? (cover as CoverConfig).coverNode - : (cover as React.ReactNode); + // Identify the config by its shape, independently of its node's content. + const coverConfig = + isNonNullable(cover) && + typeof cover === 'object' && + ('coverNode' in cover || + 'placement' in cover || + (Object.getPrototypeOf(cover) === Object.prototype && Reflect.ownKeys(cover).length === 0)) + ? (cover as CoverConfig) + : undefined; + const coverPlacement = coverConfig?.placement || 'center'; + const coverNode = coverConfig ? coverConfig.coverNode : (cover as React.ReactNode); // ============================ Open ============================ const [isShowPreview, setShowPreview] = useControlledState(!!previewOpen, previewOpen); @@ -243,7 +247,7 @@ const ImageInternal: CompoundedComponent = props => { onClick={canPreview ? onPreview : onClick} role={canPreview ? 'button' : otherProps.role} tabIndex={canPreview && otherProps.tabIndex == null ? 0 : otherProps.tabIndex} - aria-label={canPreview ? otherProps['aria-label'] ?? alt : otherProps['aria-label']} + aria-label={canPreview ? (otherProps['aria-label'] ?? alt) : otherProps['aria-label']} onKeyDown={onPreviewKeyDown} style={{ width, diff --git a/src/Preview/index.tsx b/src/Preview/index.tsx index 03a43f47..6b3314b8 100644 --- a/src/Preview/index.tsx +++ b/src/Preview/index.tsx @@ -1,6 +1,12 @@ import CSSMotion from '@rc-component/motion'; import Portal, { type PortalProps } from '@rc-component/portal'; -import { KeyCode, useEvent, useLayoutEffect, useLockFocus } from '@rc-component/util'; +import { + isReactRenderable, + KeyCode, + useEvent, + useLayoutEffect, + useLockFocus, +} from '@rc-component/util'; import { clsx } from 'clsx'; import React, { useContext, useEffect, useRef, useState } from 'react'; import { PreviewGroupContext } from '../context'; @@ -474,7 +480,9 @@ const Preview: React.FC = props => { {closeIcon !== false && closeIcon !== null && ( { + it.each([0, false, null, undefined, ''])('renders a configured cover node %s', coverNode => { + const { container } = render( + , + ); + expect(container.querySelector('.rc-image-cover-top').textContent).toBe( + coverNode === 0 ? '0' : '', + ); + }); + + it.each([null, {}])('accepts an empty cover %s', cover => { + const { container } = render(); + expect(container.querySelector('.rc-image-cover').textContent).toBe(''); + }); + + it('preserves element, array and portal covers', () => { + const target = document.createElement('div'); + const covers = [A, [A], createPortal('A', target)]; + const { container, rerender } = render( + , + ); + expect(container.querySelector('.rc-image-cover').textContent).toBe('A'); + rerender(); + expect(container.querySelector('.rc-image-cover').textContent).toBe('A'); + rerender(); + expect(target.textContent).toBe('A'); + }); + + it.each([0, false, null, true, undefined])('preserves the close icon %s', closeIcon => { + const { baseElement } = render( + , + ); + const button = baseElement.querySelector('.rc-image-preview-close'); + if (closeIcon === false || closeIcon === null) { + expect(button).toBeNull(); + } else { + expect(button.textContent).toBe(closeIcon === 0 ? '0' : 'DEFAULT'); + } + }); +});