import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; import type { KeyboardEvent, JSX } from 'react'; import { parseSrcSet, parseStyleString } from '@redocly/theme/core/utils'; import { useModalScrollLock } from '@redocly/theme/core/hooks'; export type ImageProps = { src?: string; srcSet?: string; alt?: string; className?: string; width?: string | number; height?: string | number; border?: string; withLightbox?: boolean; lightboxStyle?: React.CSSProperties | string; style?: React.CSSProperties | string; }; export function Image(props: ImageProps): JSX.Element { const { src, srcSet, alt, className, width, height, border, style, withLightbox, lightboxStyle } = props; const lightboxContainerRef = useRef(null); const [lightboxImage, setLightboxImage] = useState(undefined); const parsedSourceSetMap = useMemo(() => { return srcSet ? parseSrcSet(srcSet) : new Map(); }, [srcSet]); const handleLightboxKeyDown = useCallback((e: KeyboardEvent) => { e.stopPropagation(); if (e.key === 'Escape') { setLightboxImage(undefined); } }, []); const handleImageClick = useCallback( (src: string) => { if (!withLightbox || lightboxImage) { return; } setLightboxImage(src); }, [withLightbox, lightboxImage], ); const handleCloseLightbox = useCallback(() => { setLightboxImage(undefined); }, []); useModalScrollLock(!!lightboxImage); useEffect(() => { if (lightboxImage) { lightboxContainerRef.current?.focus(); } }, [lightboxImage]); const combinedStyles: React.CSSProperties = { ...(withLightbox && { cursor: 'pointer' }), ...(border && { border }), ...(typeof style === 'string' ? parseStyleString(style) : style), }; const lightboxOverlayStyles: React.CSSProperties | undefined = typeof lightboxStyle === 'string' ? parseStyleString(lightboxStyle) : lightboxStyle; return ( <> {lightboxImage ? ( {alt} ) : null} {src ? ( {alt} handleImageClick(src)} /> ) : ( Array.from(parsedSourceSetMap).map(([key, value]) => ( handleImageClick(value)} /> )) )} ); } const ColorModeAwareImage = styled.img<{ colorMode: string; $withLightbox?: boolean }>` html:not(.${(props) => props.colorMode}) && { display: none; } ${({ $withLightbox }) => $withLightbox && ` cursor: pointer; `} `; const Overlay = styled.div` background-color: var(--bg-color-modal-overlay); grid-column: 1 / 2; grid-row: 1 / 2; height: 100%; width: 100%; z-index: -1; `; const LightboxContainer = styled.div` display: grid; height: 100vh; left: 0; position: fixed; top: 0; width: 100vw; z-index: var(--z-index-overlay); &:focus { outline: none; } img { cursor: pointer; grid-column: 1 / 2; grid-row: 1 / 2; margin: auto; max-width: 90%; max-height: 90%; } `;