/* eslint-disable jsx-a11y/alt-text */ /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ import { ImgHTMLAttributes, useCallback, useMemo, memo, useState, useEffect, } from 'react' import {cn} from '../../lib/utils' import {getThumbhashBlobURL, getResizedImageUrl} from '../../utils' /** * An optimized image component that handles File objects, remote URLs, blob URL lifecycle management, and progressive loading with thumbhash placeholders. Ideal for displaying images from useImagePicker or any remote source. * @publicDocs */ export interface ImageDocProps { /** Remote image URL */ src?: string /** File object from useImagePicker (auto-manages blob URL lifecycle) */ file?: File /** Thumbhash string for progressive loading placeholder */ thumbhash?: string | null /** Aspect ratio (e.g., 16/9, "4/3", or "auto") */ aspectRatio?: number | string /** How the image should fit within its container */ objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down' | 'none' /** Alt text for accessibility */ alt?: string /** Callback when image finishes loading */ onLoad?: () => void } type ImageProps = ImgHTMLAttributes & { src?: string file?: File thumbhash?: string | null aspectRatio?: number | string objectFit?: 'cover' | 'contain' | 'fill' | 'scale-down' | 'none' } export const Image = memo(function Image(props: ImageProps) { const { src, file, thumbhash, onLoad, className, style, aspectRatio = 'auto', objectFit = 'contain', ...restProps } = props const [isLoaded, setIsLoaded] = useState(false) const [blobUrl, setBlobUrl] = useState(null) // Create and manage blob URL for File objects useEffect(() => { if (!file) { setBlobUrl(null) return } const url = URL.createObjectURL(file) setBlobUrl(url) // Cleanup on unmount or when file changes return () => { URL.revokeObjectURL(url) } }, [file]) const thumbhashBlobUrl = useMemo( () => getThumbhashBlobURL(thumbhash ?? undefined), [thumbhash] ) // Cleanup blob URL when it changes or component unmounts useEffect(() => { return () => { if (thumbhashBlobUrl) { URL.revokeObjectURL(thumbhashBlobUrl) } } }, [thumbhashBlobUrl]) const handleLoad = useCallback( (event: React.SyntheticEvent) => { setIsLoaded(true) onLoad?.(event) }, [onLoad] ) // Use blob URL if file is provided, otherwise use src const imageSrc = useMemo(() => { if (blobUrl) return blobUrl return getResizedImageUrl(src) }, [blobUrl, src]) return (
{thumbhashBlobUrl && !isLoaded && ( )}
) })