import UI from "../ui";
import React, { useMemo } from "react";
import type { ImgProps } from "./img.types";
/**
* Img - A semantic image component with accessibility and performance best practices.
*
* This component wraps the native `
` element with enhanced features:
* - **Responsive images** via optional srcset/sizes
* - **Lazy loading** by default for performance
* - **Error handling** with configurable fallback placeholders
* - **Type safety** with full TypeScript support
*
* ## Accessibility Patterns (WCAG 2.1 AA)
*
* ### Decorative Images
* Images that are purely visual decoration should use an empty alt attribute.
* These images are typically borders, patterns, or visual separators.
*
* @example
* ```tsx
* // ✅ GOOD: Decorative border image
*
*
* // ✅ GOOD: Background pattern
*
* ```
*
* ### Semantic Images
* Images that convey information must have descriptive alt text that explains
* the content and purpose of the image.
*
* @example
* ```tsx
* // ✅ GOOD: Informative image with descriptive alt
*
*
* // ✅ GOOD: Product photo with context
*
* ```
*
* ## Performance Optimization
*
* ### Lazy Loading
* By default, images use lazy loading to improve page load performance.
* Only use `loading="eager"` for above-the-fold images.
*
* @example
* ```tsx
* // ✅ GOOD: Lazy load below-the-fold image
*
*
* // ✅ GOOD: Eager load hero image
*
* ```
*
* ### Responsive Images
* Use srcset and sizes for responsive images to serve appropriate image sizes
* based on viewport width, improving performance and bandwidth usage.
*
* @example
* ```tsx
* // ✅ GOOD: Responsive image with multiple sizes
*
* ```
*
* ## Error Handling
*
* @example
* ```tsx
* // ✅ GOOD: Custom placeholder on error
*
*
* // ✅ GOOD: Custom error handler
*
{
* console.error('Image failed to load')
* logToAnalytics('image_error', { src: e.currentTarget.src })
* }}
* alt="Photo"
* />
* ```
*
* @param {ImgProps} props - Component props extending native img attributes
* @returns {React.ReactElement} Image element with enhanced functionality
*
* @see {@link ImgProps} for complete prop documentation
* @see https://www.w3.org/WAI/WCAG21/Understanding/non-text-content.html
*/
export const Img = ({
src = "//",
alt,
width = 480,
height,
styles,
loading = "lazy",
placeholder,
fetchpriority = "low",
decoding = "auto",
srcSet,
sizes,
onError,
onLoad,
...props
}: ImgProps) => {
/**
* Generates a performant, responsive SVG gradient placeholder.
* Uses data URI to avoid network requests and memoizes based on dimensions.
* The SVG uses viewBox for perfect scaling at any size.
*
* Features:
* - Zero network requests (works offline)
* - ~900 bytes vs. 5-10KB external image
* - Responsive with viewBox
* - Attractive gradient (indigo → purple → pink)
* - Dimension text for debugging
*/
const defaultPlaceholder = useMemo(() => {
const w = typeof width === "number" ? width : 480;
const h = typeof height === "number" ? height : Math.round(w * 0.75);
// Responsive SVG with attractive gradient and dimension text
const svg = ``;
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
}, [width, height]);
const fallbackPlaceholder = placeholder ?? defaultPlaceholder;
/**
* Handles image load errors.
* Calls custom error handler if provided, then applies fallback placeholder.
* The custom handler can prevent the default fallback by calling e.preventDefault().
*/
const handleImgError = (
e: React.SyntheticEvent
): void => {
// Call custom error handler first (for logging, analytics, etc.)
if (onError) {
onError(e);
}
// Apply fallback unless preventDefault() was called
if (!e.defaultPrevented) {
// Avoid infinite error loop by checking if already showing placeholder
if (e.currentTarget.src !== fallbackPlaceholder) {
e.currentTarget.src = fallbackPlaceholder;
}
}
};
/**
* Handles successful image load.
* Calls custom load handler if provided.
*/
const handleImgLoad = (
e: React.SyntheticEvent
): void => {
onLoad?.(e);
};
return (
);
};
export default Img;
Img.displayName = "Img";