diff --git a/README.md b/README.md index 38a1efc3..014463d8 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ export interface UncontrolledProps { // Default: 'Expand image' a11yNameButtonZoom?: string - // Your image + // Your image (required) children: ReactNode // Provide your own unzoom button icon @@ -83,6 +83,13 @@ export interface UncontrolledProps { // Default: window scrollableEl?: Window | HTMLElement + // Provide your own custom modal content component + ZoomContent?: (props: { + img: ReactElement | null; + buttonUnzoom: ReactElement; + onUnzoom: () => void; + }) => ReactElement; + // Higher quality image attributes to use on zoom zoomImg?: ImgHTMLAttributes diff --git a/source/Controlled.tsx b/source/Controlled.tsx index 487b47af..fc4d2673 100644 --- a/source/Controlled.tsx +++ b/source/Controlled.tsx @@ -4,6 +4,8 @@ import React, { ElementType, ImgHTMLAttributes, KeyboardEvent, + MouseEvent, + ReactElement, ReactNode, createRef, } from 'react' @@ -41,6 +43,11 @@ export interface ControlledProps { isZoomed: boolean onZoomChange?: (value: boolean) => void scrollableEl?: Window | HTMLElement + ZoomContent?: (data: { + img: ReactElement | null + buttonUnzoom: ReactElement + onUnzoom: () => void + }) => ReactElement zoomImg?: ImgHTMLAttributes zoomMargin?: number } @@ -86,6 +93,7 @@ class ControlledBase extends Component() private refDialog = createRef() + private refModalContent = createRef() private refModalImg = createRef() private refWrap = createRef() @@ -95,6 +103,7 @@ class ControlledBase extends Component + : isSvg + ?
+ : null + + const modalBtnUnzoom = + + const modalContent = ZoomContent + ? + : <>{modalImg}{modalBtnUnzoom} + + // ========================================================================= + return (
@@ -193,46 +245,13 @@ class ControlledBase extends Component
-
- {isImg || isDiv - ? {imgAlt} - : undefined - } - {isSvg - ?
- : undefined - } - -
+
{modalContent}
) @@ -343,6 +362,15 @@ class ControlledBase extends Component) => { + if (e.target === this.refModalContent.current || e.target === this.refModalImg.current) { + this.handleUnzoom() + } + } + // =========================================================================== // Intercept default dialog.close() and use ours so we can animate diff --git a/source/styles.css b/source/styles.css index 40e0f4b8..52f955c2 100644 --- a/source/styles.css +++ b/source/styles.css @@ -69,7 +69,6 @@ [data-rmiz-modal-overlay] { position: absolute; inset: 0; - pointer-events: all; transition: background-color 0.3s; } [data-rmiz-modal-overlay="hidden"] { @@ -78,14 +77,16 @@ [data-rmiz-modal-overlay="visible"] { background-color: rgba(255, 255, 255, 1); } -[data-rmiz-modal][open] [data-rmiz-modal-content] { +[data-rmiz-modal-content] { position: relative; + pointer-events: all; + width: 100%; + height: 100%; } [data-rmiz-modal-img] { position: absolute; cursor: zoom-out; image-rendering: high-quality; - pointer-events: all; transform-origin: top left; transition: transform 0.3s; will-change: transform; diff --git a/stories/Img.stories.tsx b/stories/Img.stories.tsx index 6bbd9c8e..70e59b52 100644 --- a/stories/Img.stories.tsx +++ b/stories/Img.stories.tsx @@ -1,10 +1,10 @@ -import React from 'react' +import React, { ReactElement, useLayoutEffect, useMemo, useState } from 'react' import { ComponentStory, ComponentMeta } from '@storybook/react' import { waitFor, within, userEvent } from '@storybook/testing-library' import { expect } from '@storybook/jest' -import Zoom from '../source' +import Zoom, { UncontrolledProps } from '../source' import '../source/styles.css' import './base.css' @@ -176,6 +176,69 @@ export const CustomModalStyles: ComponentStory = (props) => (
) +export const ModalFigureCaption: ComponentStory = (props) => ( +
+

Modal With Figure And Caption

+

+ If you want more control over the zoom modal's content, you can pass + a ZoomContent component +

+
+ + {imgThatWanakaTree.alt} + +
+
+) + +const CustomZoomContent: UncontrolledProps['ZoomContent'] = ({ buttonUnzoom, img }) => { + const [isLoaded, setIsLoaded] = useState(false) + + const imgProps = (img as ReactElement)?.props + const imgWidth = imgProps?.width + const imgHeight = imgProps?.height + + const classCaption = useMemo(() => { + const hasWidthHeight = imgWidth && imgHeight + const imgRatioLargerThanWindow = imgWidth / imgHeight > window.innerWidth / window.innerHeight + + return cx({ + 'zoom-caption': true, + 'zoom-caption--loaded': isLoaded, + 'zoom-caption--bottom': hasWidthHeight && imgRatioLargerThanWindow, + 'zoom-caption--left': hasWidthHeight && !imgRatioLargerThanWindow, + }) + }, [imgWidth, imgHeight, isLoaded]) + + // @TODO: this needs to be set on load/unload + useLayoutEffect(() => { + setIsLoaded(true) + }, []) + + return <> + {buttonUnzoom} + +
+ {img} +
+ That Wanaka Tree, also known as the Wanaka Willow, is a willow tree + located at the southern end of Lake Wānaka in the Otago region of New + Zealand. + + Wikipedia, + That Wanaka Tree + + +
+
+ +} + export const CustomButtonIcons: ComponentStory = (props) => (

An image with custom zoom & unzoom icons

@@ -215,3 +278,18 @@ WithRegularZoomed.play = async ({ canvasElement }) => { await expect(canvas.getByLabelText('Minimize image')).toHaveFocus() }) } + +// ============================================================================= +// HELPERS + +const cx = (mods) => { + const cns = [] + + for (const k in mods) { + if (mods[k]) { + cns.push(k) + } + } + + return cns.join(' ') +} diff --git a/stories/base.css b/stories/base.css index 6553fe50..bcab50e1 100644 --- a/stories/base.css +++ b/stories/base.css @@ -87,3 +87,31 @@ img { width: 15px; line-height: 0; } +.zoom-caption { + position: absolute; + background-color: rgba(0, 0, 0, 0.85); + color: #fff; + font-size: 1.4rem; + padding: 1.7rem 2.5rem; + opacity: 0.0001; + transition: opacity 1s; +} +.zoom-caption--loaded { + opacity: 1; +} +.zoom-caption--bottom { + inset: auto 0 0 0; +} +.zoom-caption--left { + max-width: 40rem; + top: 50%; + transform: translateY(-50%); +} +.zoom-caption-cite { + display: block; + margin-top: 1.5rem; +} +.zoom-caption-link { + color: #fff; + text-underline-offset: 0.5rem; +}