diff --git a/assets/index.less b/assets/index.less index 1afaa2b..8e8d9e7 100644 --- a/assets/index.less +++ b/assets/index.less @@ -28,6 +28,7 @@ &-img { width: 100%; height: auto; + overflow: hidden; &-placeholder { background-color: @background-color; background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjhweCIgaGVpZ2h0PSIyMnB4IiB2aWV3Qm94PSIwIDAgMjggMjIiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDU1LjIgKDc4MTgxKSAtIGh0dHBzOi8vc2tldGNoYXBwLmNvbSAtLT4KICAgIDx0aXRsZT5pbWFnZS1maWxs5aSH5Lu9PC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGcgaWQ9Iuafpeeci+WbvueJh+S8mOWMljQuMCIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9IuWKoOi9veWbvueJhyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU3Mi4wMDAwMDAsIC01MDYuMDAwMDAwKSI+CiAgICAgICAgICAgIDxnIGlkPSJpbWFnZS1maWxs5aSH5Lu9IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1NzAuMDAwMDAwLCA1MDEuMDAwMDAwKSI+CiAgICAgICAgICAgICAgICA8cmVjdCBpZD0iUmVjdGFuZ2xlIiBmaWxsPSIjMDAwMDAwIiBvcGFjaXR5PSIwIiB4PSIwIiB5PSIwIiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiPjwvcmVjdD4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0yOSw1IEwzLDUgQzIuNDQ2ODc1LDUgMiw1LjQ0Njg3NSAyLDYgTDIsMjYgQzIsMjYuNTUzMTI1IDIuNDQ2ODc1LDI3IDMsMjcgTDI5LDI3IEMyOS41NTMxMjUsMjcgMzAsMjYuNTUzMTI1IDMwLDI2IEwzMCw2IEMzMCw1LjQ0Njg3NSAyOS41NTMxMjUsNSAyOSw1IFogTTEwLjU2MjUsOS41IEMxMS42NjU2MjUsOS41IDEyLjU2MjUsMTAuMzk2ODc1IDEyLjU2MjUsMTEuNSBDMTIuNTYyNSwxMi42MDMxMjUgMTEuNjY1NjI1LDEzLjUgMTAuNTYyNSwxMy41IEM5LjQ1OTM3NSwxMy41IDguNTYyNSwxMi42MDMxMjUgOC41NjI1LDExLjUgQzguNTYyNSwxMC4zOTY4NzUgOS40NTkzNzUsOS41IDEwLjU2MjUsOS41IFogTTI2LjYyMTg3NSwyMy4xNTkzNzUgQzI2LjU3ODEyNSwyMy4xOTY4NzUgMjYuNTE4NzUsMjMuMjE4NzUgMjYuNDU5Mzc1LDIzLjIxODc1IEw1LjUzNzUsMjMuMjE4NzUgQzUuNCwyMy4yMTg3NSA1LjI4NzUsMjMuMTA2MjUgNS4yODc1LDIyLjk2ODc1IEM1LjI4NzUsMjIuOTA5Mzc1IDUuMzA5Mzc1LDIyLjg1MzEyNSA1LjM0Njg3NSwyMi44MDYyNSBMMTAuNjY4NzUsMTYuNDkzNzUgQzEwLjc1NjI1LDE2LjM4NzUgMTAuOTE1NjI1LDE2LjM3NSAxMS4wMjE4NzUsMTYuNDYyNSBDMTEuMDMxMjUsMTYuNDcxODc1IDExLjA0Mzc1LDE2LjQ4MTI1IDExLjA1MzEyNSwxNi40OTM3NSBMMTQuMTU5Mzc1LDIwLjE4MTI1IEwxOS4xLDE0LjMyMTg3NSBDMTkuMTg3NSwxNC4yMTU2MjUgMTkuMzQ2ODc1LDE0LjIwMzEyNSAxOS40NTMxMjUsMTQuMjkwNjI1IEMxOS40NjI1LDE0LjMgMTkuNDc1LDE0LjMwOTM3NSAxOS40ODQzNzUsMTQuMzIxODc1IEwyNi42NTkzNzUsMjIuODA5Mzc1IEMyNi43NDA2MjUsMjIuOTEyNSAyNi43MjgxMjUsMjMuMDcxODc1IDI2LjYyMTg3NSwyMy4xNTkzNzUgWiIgaWQ9IlNoYXBlIiBmaWxsPSIjRThFOEU4Ij48L3BhdGg+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==); @@ -36,6 +37,46 @@ } } + &-cover { + position: absolute; + right: 0; + bottom: 0; + left: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: #fff; + background-color: rgba(0, 0, 0, 0.5); + transition: background-color 0.3s; + + &-top { + top: 0; + left: 0; + right: 0; + height: max-content; + justify-content: flex-start; + padding: 5px; + } + &-bottom { + bottom: 0; + left: 0; + right: 0; + height: max-content; + justify-content: flex-end; + padding: 5px; + } + &-center { + top: 50%; + left: 0; + right: 0; + transform: translateY(-50%); + height: 100%; + align-items: center; + justify-content: center; + } + } + &-placeholder { .box; } diff --git a/docs/demo/coverPlacement.md b/docs/demo/coverPlacement.md new file mode 100644 index 0000000..174253e --- /dev/null +++ b/docs/demo/coverPlacement.md @@ -0,0 +1,8 @@ +--- +title: coverPlacement +nav: + title: Demo + path: /demo +--- + + diff --git a/docs/examples/coverPlacement.tsx b/docs/examples/coverPlacement.tsx new file mode 100644 index 0000000..6053ffb --- /dev/null +++ b/docs/examples/coverPlacement.tsx @@ -0,0 +1,42 @@ +import type { CoverConfig } from '@rc-component/image'; +import Image from '@rc-component/image'; +import * as React from 'react'; +import '../../assets/index.less'; +import { defaultIcons } from './common'; + +export default function Base() { + const [placement, setPlacement] = React.useState('center'); + return ( +
+
+ + +
+
+ { + console.log('click'); + }} + preview={{ + icons: defaultIcons, + onOpenChange: open => { + console.log('open', open); + }, + zIndex: 9999, + cover: { + coverNode: 'Click to Preview', + placement, + }, + }} + /> +
+ ); +} diff --git a/package.json b/package.json index 80ae9b3..d8c77a7 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"", "start": "dumi dev", "test": "rc-test", + "test:update": "rc-test -u", "tsc": "bunx tsc --noEmit" }, "dependencies": { diff --git a/src/Image.tsx b/src/Image.tsx index b2b80e3..8b23034 100644 --- a/src/Image.tsx +++ b/src/Image.tsx @@ -19,8 +19,12 @@ export interface ImgInfo { height: string | number; } +export interface CoverConfig { + coverNode?: React.ReactNode; + placement?: 'top' | 'bottom' | 'center'; +} export interface PreviewConfig extends Omit { - cover?: React.ReactNode; + cover?: React.ReactNode | CoverConfig; // Similar to InternalPreviewConfig but not have `current` imageRender?: ( @@ -121,6 +125,14 @@ 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; + // ============================ Open ============================ const [isShowPreview, setShowPreview] = useMergedState(!!previewOpen, { value: previewOpen, @@ -237,13 +249,13 @@ const ImageInternal: CompoundedComponent = props => { {/* Preview Click Mask */} {cover !== false && canPreview && (
- {cover} + {coverNode}
)} diff --git a/tests/__snapshots__/basic.test.tsx.snap b/tests/__snapshots__/basic.test.tsx.snap index 591e324..4c1c335 100644 --- a/tests/__snapshots__/basic.test.tsx.snap +++ b/tests/__snapshots__/basic.test.tsx.snap @@ -11,7 +11,7 @@ exports[`Basic snapshot 1`] = ` width="200" />
`; diff --git a/tests/basic.test.tsx b/tests/basic.test.tsx index 7270581..526823e 100644 --- a/tests/basic.test.tsx +++ b/tests/basic.test.tsx @@ -1,8 +1,16 @@ -import { fireEvent, render } from '@testing-library/react'; +import { act, fireEvent, render } from '@testing-library/react'; import React from 'react'; -import Image from '../src'; +import Image, { CoverConfig } from '../src'; describe('Basic', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + it('snapshot', () => { const { asFragment } = render( { const operationsElement = baseElement.querySelector('.rc-image-preview'); expect(operationsElement).toHaveStyle({ zIndex: 9999 }); }); + it('cover placement should work', () => { + const App = () => { + const [placement, setPlacement] = React.useState<'top' | 'bottom' | 'center'>('center'); + return ( + <> + + + + ); + }; + const { container } = render(); + const coverElement = container.querySelector('.rc-image-cover'); + expect(coverElement).toHaveClass('rc-image-cover-center'); + + fireEvent.change(container.querySelector('#placement'), { + target: { value: 'top' }, + }); + // Wait for the state update to take effect + act(() => { + jest.runAllTimers(); + }); + expect(coverElement).toHaveClass('rc-image-cover-top'); + + fireEvent.change(container.querySelector('#placement'), { + target: { value: 'bottom' }, + }); + // Wait for the state update to take effect + act(() => { + jest.runAllTimers(); + }); + expect(coverElement).toHaveClass('rc-image-cover-bottom'); + }); });