From aa164813fbd6d88b35bd56e8c4afda746ac4463f Mon Sep 17 00:00:00 2001 From: David_LY Date: Fri, 26 Jan 2024 13:49:36 +0100 Subject: [PATCH 1/2] Created FullscreenImageModal component --- .../FullscreenImageModal.styles.ts | 8 ++ .../FullscreenImageModal.tsx | 80 +++++++++++++++++++ .../components/FullscreenImageModal/index.ts | 1 + .../common-ui-web/src/components/index.ts | 1 + .../components/FullscreenImageModal.test.tsx | 33 ++++++++ 5 files changed, 123 insertions(+) create mode 100644 packages/public/common-ui-web/src/components/FullscreenImageModal/FullscreenImageModal.styles.ts create mode 100644 packages/public/common-ui-web/src/components/FullscreenImageModal/FullscreenImageModal.tsx create mode 100644 packages/public/common-ui-web/src/components/FullscreenImageModal/index.ts create mode 100644 packages/public/common-ui-web/test/components/FullscreenImageModal.test.tsx diff --git a/packages/public/common-ui-web/src/components/FullscreenImageModal/FullscreenImageModal.styles.ts b/packages/public/common-ui-web/src/components/FullscreenImageModal/FullscreenImageModal.styles.ts new file mode 100644 index 000000000..96df39c71 --- /dev/null +++ b/packages/public/common-ui-web/src/components/FullscreenImageModal/FullscreenImageModal.styles.ts @@ -0,0 +1,8 @@ +import { Styles } from '@monkvision/types'; + +export const styles: Styles = { + image: { + maxWidth: '100%', + maxHeight: '100%', + }, +}; diff --git a/packages/public/common-ui-web/src/components/FullscreenImageModal/FullscreenImageModal.tsx b/packages/public/common-ui-web/src/components/FullscreenImageModal/FullscreenImageModal.tsx new file mode 100644 index 000000000..f586321b2 --- /dev/null +++ b/packages/public/common-ui-web/src/components/FullscreenImageModal/FullscreenImageModal.tsx @@ -0,0 +1,80 @@ +import React, { useState } from 'react'; +import { FullscreenModal } from '../FullscreenModal'; +import { styles } from './FullscreenImageModal.styles'; + +/** + * Props for the FullscreenImageModal component. + */ +export interface FullscreenImageModalProps { + show: boolean; + label?: string; + onClose?: () => void; + url: string; +} + +function calculatePosition( + viewPort: number, + imageDimension: number, + clickPosition: number, + scale: number, +) { + if (viewPort > imageDimension * 3) { + return 0; + } + const blackBand = (viewPort - imageDimension) / 2; + const maxPosition = (imageDimension - blackBand) / scale; + return Math.min(maxPosition, Math.max(-maxPosition, imageDimension / 2 - clickPosition)); +} + +/** + * FullscreenImageModal component used to display a full-screen modal for an image and able the user to zoom on it. + */ +export function FullscreenImageModal({ + show, + label = '', + onClose, + url, +}: FullscreenImageModalProps) { + const [isZoomed, setIsZoomed] = useState(false); + const [position, setPosition] = useState({ x: 0, y: 0 }); + const scale = 3; + + function handleZoom(event: React.MouseEvent) { + if (isZoomed) { + setPosition({ x: 0, y: 0 }); + } else { + const positionX = calculatePosition( + window.innerWidth, + event.currentTarget.offsetWidth, + event.nativeEvent.offsetX, + scale, + ); + const positionY = calculatePosition( + window.innerHeight, + event.currentTarget.offsetHeight, + event.nativeEvent.offsetY, + scale, + ); + setPosition({ x: positionX, y: positionY }); + } + setIsZoomed(!isZoomed); + } + + return ( + + {label} {}} + data-testid='image' + /> + + ); +} diff --git a/packages/public/common-ui-web/src/components/FullscreenImageModal/index.ts b/packages/public/common-ui-web/src/components/FullscreenImageModal/index.ts new file mode 100644 index 000000000..e10eada0a --- /dev/null +++ b/packages/public/common-ui-web/src/components/FullscreenImageModal/index.ts @@ -0,0 +1 @@ +export { FullscreenImageModal, type FullscreenImageModalProps } from './FullscreenImageModal'; diff --git a/packages/public/common-ui-web/src/components/index.ts b/packages/public/common-ui-web/src/components/index.ts index 057f4f940..5b0c52f5d 100644 --- a/packages/public/common-ui-web/src/components/index.ts +++ b/packages/public/common-ui-web/src/components/index.ts @@ -7,3 +7,4 @@ export * from './SwitchButton'; export * from './FullscreenModal'; export * from './BackdropDialog'; export * from './Slider'; +export * from './FullscreenImageModal'; diff --git a/packages/public/common-ui-web/test/components/FullscreenImageModal.test.tsx b/packages/public/common-ui-web/test/components/FullscreenImageModal.test.tsx new file mode 100644 index 000000000..327c94ea9 --- /dev/null +++ b/packages/public/common-ui-web/test/components/FullscreenImageModal.test.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import '@testing-library/jest-dom'; +import { screen, render, fireEvent } from '@testing-library/react'; +import { FullscreenImageModal, FullscreenImageModalProps } from '../../src'; + +const mockProps: FullscreenImageModalProps = { + show: true, + label: 'Test Label', + onClose: jest.fn(), + url: 'test-image-url', +}; + +describe('FullsreenImageModal component', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should', () => { + const { unmount } = render(); + + const image = screen.getByTestId('image') as HTMLImageElement; + expect(image.src).toContain(mockProps.url); + expect(image.alt).toEqual(mockProps.label); + expect(image.style.cursor).toEqual('zoom-in'); + expect(image.style.transform).toContain('scale(1)'); + + fireEvent.click(image); + expect(image.style.cursor).toEqual('zoom-out'); + expect(image.style.transform).toContain('scale(3)'); + + unmount(); + }); +}); From 12ad630d4e36317cb6cd69ef77692cbd449cb4f8 Mon Sep 17 00:00:00 2001 From: David_LY Date: Fri, 1 Mar 2024 16:31:39 +0100 Subject: [PATCH 2/2] fixed PR + added TSDoc --- packages/public/common-ui-web/README.md | 33 ++++++++++++++++ .../FullscreenImageModal.tsx | 39 ++++++++++++------- .../FullscreenModal/FullscreenModal.tsx | 9 +++++ 3 files changed, 68 insertions(+), 13 deletions(-) diff --git a/packages/public/common-ui-web/README.md b/packages/public/common-ui-web/README.md index 1c3a3f83a..86017b09f 100644 --- a/packages/public/common-ui-web/README.md +++ b/packages/public/common-ui-web/README.md @@ -132,6 +132,39 @@ function App() { --- +## FullscreenImageModal +### Description +Component used to display a full-screen modal for an image and able the user to zoom on it. + + +### Example +```tsx +import { FullscreenImageModal } from '@monkvision/common-ui-web'; + +function App() { + const [showFullscreenImageModal, setShowFullscreenImageModal] = useState(true); + + return ( + setShowFullscreenImageModal(false)} + /> + ); +} +``` + +### Props +| Prop | Type | Description | Required | Default Value | +|---------|--------------|-----------------------------------------------------------------------------------------------|----------|---------------| +| url | string | The URL of the image to display. | ✔️ | | +| show | boolean | Boolean indicating if the fullscreen image modal is displayed on the screen. | | `false` | +| label | string | Label displayed in the header at the top of the image modal. | | `''` | +| onClose | `() => void` | Callback called when the user presses the close button in the header at the top of the modal. | | | + +--- + ## FullscreenModal ### Description Component used to display a full screen modal on top of the screen. The content of the modal must be passed as children diff --git a/packages/public/common-ui-web/src/components/FullscreenImageModal/FullscreenImageModal.tsx b/packages/public/common-ui-web/src/components/FullscreenImageModal/FullscreenImageModal.tsx index f586321b2..43f20c05e 100644 --- a/packages/public/common-ui-web/src/components/FullscreenImageModal/FullscreenImageModal.tsx +++ b/packages/public/common-ui-web/src/components/FullscreenImageModal/FullscreenImageModal.tsx @@ -1,28 +1,42 @@ -import React, { useState } from 'react'; +import { useState, MouseEvent } from 'react'; import { FullscreenModal } from '../FullscreenModal'; import { styles } from './FullscreenImageModal.styles'; +const ZOOM_SCALE = 3; + /** * Props for the FullscreenImageModal component. */ export interface FullscreenImageModalProps { - show: boolean; + /** + * The URL of the image to display. + */ + url: string; + /** + * Boolean indicating if the modal is shown or not. + */ + show?: boolean; + /** + * Optional label for the image. + */ label?: string; + /** + * Callback function invoked when the modal is closed. + */ onClose?: () => void; - url: string; } function calculatePosition( viewPort: number, imageDimension: number, clickPosition: number, - scale: number, -) { + zoomScale: number, +): number { if (viewPort > imageDimension * 3) { return 0; } const blackBand = (viewPort - imageDimension) / 2; - const maxPosition = (imageDimension - blackBand) / scale; + const maxPosition = (imageDimension - blackBand) / zoomScale; return Math.min(maxPosition, Math.max(-maxPosition, imageDimension / 2 - clickPosition)); } @@ -30,16 +44,15 @@ function calculatePosition( * FullscreenImageModal component used to display a full-screen modal for an image and able the user to zoom on it. */ export function FullscreenImageModal({ - show, + url, + show = false, label = '', onClose, - url, }: FullscreenImageModalProps) { const [isZoomed, setIsZoomed] = useState(false); const [position, setPosition] = useState({ x: 0, y: 0 }); - const scale = 3; - function handleZoom(event: React.MouseEvent) { + const handleZoom = (event: MouseEvent) => { if (isZoomed) { setPosition({ x: 0, y: 0 }); } else { @@ -47,18 +60,18 @@ export function FullscreenImageModal({ window.innerWidth, event.currentTarget.offsetWidth, event.nativeEvent.offsetX, - scale, + ZOOM_SCALE, ); const positionY = calculatePosition( window.innerHeight, event.currentTarget.offsetHeight, event.nativeEvent.offsetY, - scale, + ZOOM_SCALE, ); setPosition({ x: positionX, y: positionY }); } setIsZoomed(!isZoomed); - } + }; return ( diff --git a/packages/public/common-ui-web/src/components/FullscreenModal/FullscreenModal.tsx b/packages/public/common-ui-web/src/components/FullscreenModal/FullscreenModal.tsx index e1698717c..f75be410f 100644 --- a/packages/public/common-ui-web/src/components/FullscreenModal/FullscreenModal.tsx +++ b/packages/public/common-ui-web/src/components/FullscreenModal/FullscreenModal.tsx @@ -6,8 +6,17 @@ import { styles } from './FullscreenModal.styles'; * Props that can be passed to the Fullscreen Modal component. */ export interface FullscreenModalProps { + /** + * Boolean indicating if the modal is shown or not. + */ show?: boolean; + /** + * Callback function invoked when the modal is closed. + */ onClose?: () => void; + /** + * Optional title for the modal. + */ title?: string; }