104 lines
2.9 KiB
TypeScript
104 lines
2.9 KiB
TypeScript
'use client'
|
|
|
|
import Image from 'next/image'
|
|
import { useState } from 'react'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
interface OptimizedImageProps {
|
|
src: string
|
|
alt: string
|
|
width: number
|
|
height: number
|
|
className?: string
|
|
priority?: boolean
|
|
fill?: boolean
|
|
sizes?: string
|
|
quality?: number
|
|
placeholder?: 'blur' | 'empty'
|
|
blurDataURL?: string
|
|
}
|
|
|
|
// Generate a simple blur placeholder
|
|
const shimmer = (w: number, h: number) => `
|
|
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
<defs>
|
|
<linearGradient id="g">
|
|
<stop stop-color="#f3f4f6" offset="20%" />
|
|
<stop stop-color="#e5e7eb" offset="50%" />
|
|
<stop stop-color="#f3f4f6" offset="70%" />
|
|
</linearGradient>
|
|
</defs>
|
|
<rect width="${w}" height="${h}" fill="#f3f4f6" />
|
|
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
|
|
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
|
|
</svg>`
|
|
|
|
const toBase64 = (str: string) =>
|
|
typeof window === 'undefined'
|
|
? Buffer.from(str).toString('base64')
|
|
: window.btoa(str)
|
|
|
|
export default function OptimizedImage({
|
|
src,
|
|
alt,
|
|
width,
|
|
height,
|
|
className,
|
|
priority = false,
|
|
fill = false,
|
|
sizes,
|
|
quality = 75,
|
|
placeholder = 'blur',
|
|
blurDataURL,
|
|
...props
|
|
}: OptimizedImageProps) {
|
|
const [imageError, setImageError] = useState(false)
|
|
const [imageLoaded, setImageLoaded] = useState(false)
|
|
|
|
// Default fallback image for products
|
|
const fallbackSrc = '/logo.png'
|
|
|
|
// Generate blur placeholder if not provided
|
|
const defaultBlurDataURL = `data:image/svg+xml;base64,${toBase64(shimmer(width, height))}`
|
|
|
|
const handleError = () => {
|
|
setImageError(true)
|
|
}
|
|
|
|
const handleLoad = () => {
|
|
setImageLoaded(true)
|
|
}
|
|
|
|
return (
|
|
<div className={cn("relative overflow-hidden", className)}>
|
|
<Image
|
|
src={imageError ? fallbackSrc : src}
|
|
alt={alt}
|
|
width={fill ? undefined : width}
|
|
height={fill ? undefined : height}
|
|
fill={fill}
|
|
priority={priority}
|
|
quality={quality}
|
|
sizes={sizes || fill ? '(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw' : undefined}
|
|
placeholder={placeholder}
|
|
blurDataURL={blurDataURL || defaultBlurDataURL}
|
|
className={cn(
|
|
"transition-all duration-300",
|
|
imageLoaded ? "opacity-100" : "opacity-0",
|
|
fill ? "object-cover" : "w-full h-full object-cover"
|
|
)}
|
|
onError={handleError}
|
|
onLoad={handleLoad}
|
|
{...props}
|
|
/>
|
|
|
|
{/* Loading shimmer effect */}
|
|
{!imageLoaded && (
|
|
<div className="absolute inset-0 bg-gradient-to-r from-gray-200 via-gray-100 to-gray-200 animate-pulse">
|
|
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent animate-shimmer"></div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|