Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 258x 1x 1x 1x 1x 1x 1x 1x 1x 1x 46x 46x 46x 46x 46x 46x 46x 46x 46x 1x 1x 1x 1x 1x 1x 1x 1x 1x | 'use client';
import { useState } from 'react';
import Image, { ImageProps } from 'next/image';
import { cn } from '@/lib/core';
/**
* ProductImage Component Props
*/
export interface ProductImageProps extends Omit<ImageProps, 'src' | 'onError'> {
/** Image source URL */
src: string | null | undefined;
/** Fallback image URL */
fallback?: string;
/** Show loading skeleton */
showSkeleton?: boolean;
/** Container class name */
containerClassName?: string;
}
/**
* ProductImage - Enhanced Next.js Image with built-in error handling
*
* Features:
* - Automatic fallback to placeholder on error
* - Loading skeleton support
* - Handles null/undefined src gracefully
* - Optimized for product images
* - Consistent placeholder across app
*
* @example
* ```tsx
* // Basic usage
* <ProductImage
* src={product.imgs?.previews[0]}
* alt={product.title}
* width={250}
* height={250}
* />
*
* // With custom fallback
* <ProductImage
* src={imageSrc}
* fallback="/images/custom-placeholder.png"
* alt="Product"
* width={200}
* height={200}
* />
*
* // With loading skeleton
* <ProductImage
* src={product.image}
* showSkeleton
* alt="Product"
* width={300}
* height={300}
* />
* ```
*/
export function ProductImage({
src,
fallback = '/images/placeholder.png',
showSkeleton = false,
containerClassName,
className,
alt,
...props
}: ProductImageProps) {
const [imageError, setImageError] = useState(false);
const [isLoading, setIsLoading] = useState(true);
// Use fallback if src is null/undefined or if image failed to load
const imageSrc = !src || imageError ? fallback : src;
return (
<div className={cn('relative', containerClassName)}>
{showSkeleton && isLoading && (
<div className="absolute inset-0 animate-pulse bg-gray-200 dark:bg-gray-700 rounded" />
)}
<Image
src={imageSrc}
alt={alt}
className={cn(className)}
onError={() => setImageError(true)}
onLoad={() => setIsLoading(false)}
placeholder="empty"
{...props}
/>
</div>
);
}
/**
* ProductThumbnail - Optimized for small product thumbnails
*/
export interface ProductThumbnailProps extends Omit<ProductImageProps, 'width' | 'height'> {
/** Thumbnail size (default: 90) */
size?: number;
}
export function ProductThumbnail({ size = 90, ...props }: ProductThumbnailProps) {
return (
<ProductImage
width={size}
height={size}
{...props}
/>
);
}
/**
* ProductPreview - Optimized for product preview images
*/
export interface ProductPreviewProps extends Omit<ProductImageProps, 'width' | 'height'> {
/** Preview size (default: 250) */
size?: number;
}
export function ProductPreview({ size = 250, ...props }: ProductPreviewProps) {
return (
<ProductImage
width={size}
height={size}
{...props}
/>
);
}
|