import { useEffect, useRef, useState } from 'react'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { ZoomIn, ZoomOut, Move } from 'lucide-react'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { toast } from 'sonner'; interface InteractiveImageProps { src: string; alt: string; className?: string; } const InteractiveImage = ({ src, alt, className }: InteractiveImageProps) => { const containerRef = useRef(null); const imageRef = useRef(null); const [scale, setScale] = useState(1); const [position, setPosition] = useState({ x: 0, y: 0 }); const [isDragging, setIsDragging] = useState(false); const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); const [showInstructions, setShowInstructions] = useState(true); const [isMobile, setIsMobile] = useState(false); // Check if device is mobile useEffect(() => { const checkMobile = () => { setIsMobile(window.innerWidth <= 768); }; checkMobile(); window.addEventListener('resize', checkMobile); return () => window.removeEventListener('resize', checkMobile); }, []); // Show instructions tooltip initially, then fade out useEffect(() => { if (showInstructions) { const timer = setTimeout(() => { setShowInstructions(false); }, 4000); return () => clearTimeout(timer); } }, [showInstructions]); // Display instruction toast on mount useEffect(() => { const message = isMobile ? "Tap to zoom and drag with your finger to explore" : "Click to zoom and drag to explore the image"; toast(message, { duration: 4000, icon: isMobile ? "👆" : "🖱️", }); }, [isMobile]); // Handle zoom in const zoomIn = () => { if (scale < 3) { setScale(prevScale => prevScale + 0.5); } }; // Handle zoom out const zoomOut = () => { if (scale > 1) { setScale(prevScale => prevScale - 0.5); // Reset position if zooming back to original size if (scale <= 1.5) { setPosition({ x: 0, y: 0 }); } } }; // Handle reset zoom/pan const resetView = () => { setScale(1); setPosition({ x: 0, y: 0 }); }; // Mouse events for dragging on desktop const handleMouseDown = (e: React.MouseEvent) => { if (scale > 1) { setIsDragging(true); setDragStart({ x: e.clientX - position.x, y: e.clientY - position.y }); } }; const handleMouseMove = (e: React.MouseEvent) => { if (isDragging && scale > 1) { const newX = e.clientX - dragStart.x; const newY = e.clientY - dragStart.y; // Add boundaries to prevent dragging too far const maxX = (scale - 1) * (imageRef.current?.offsetWidth || 0) / 2; const maxY = (scale - 1) * (imageRef.current?.offsetHeight || 0) / 2; setPosition({ x: Math.min(Math.max(newX, -maxX), maxX), y: Math.min(Math.max(newY, -maxY), maxY) }); } }; const handleMouseUp = () => { setIsDragging(false); }; // Touch events for mobile const handleTouchStart = (e: React.TouchEvent) => { if (scale > 1 && e.touches.length === 1) { setIsDragging(true); setDragStart({ x: e.touches[0].clientX - position.x, y: e.touches[0].clientY - position.y }); } }; const handleTouchMove = (e: React.TouchEvent) => { if (isDragging && scale > 1 && e.touches.length === 1) { const newX = e.touches[0].clientX - dragStart.x; const newY = e.touches[0].clientY - dragStart.y; // Add boundaries const maxX = (scale - 1) * (imageRef.current?.offsetWidth || 0) / 2; const maxY = (scale - 1) * (imageRef.current?.offsetHeight || 0) / 2; setPosition({ x: Math.min(Math.max(newX, -maxX), maxX), y: Math.min(Math.max(newY, -maxY), maxY) }); // Prevent page scrolling while dragging e.preventDefault(); } }; const handleTouchEnd = () => { setIsDragging(false); }; // Handle double tap/click to zoom out const handleDoubleClick = () => { if (scale > 1) { resetView(); } else { zoomIn(); } }; return (
{/* Interactive image container */}
1 && "cursor-move", className )} style={{ touchAction: scale > 1 ? "none" : "auto" }} onClick={() => scale === 1 && zoomIn()} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} onMouseLeave={handleMouseUp} onTouchStart={handleTouchStart} onTouchMove={handleTouchMove} onTouchEnd={handleTouchEnd} onDoubleClick={handleDoubleClick} > {alt} {/* Instruction overlay - shows initially then fades */} {showInstructions && (
{isMobile ? ( 👆 ) : ( 🖱️ )}

{isMobile ? "Tap to zoom, drag to explore" : "Click to zoom, drag to explore" }

Double-tap to reset

)}
{/* Controls overlay */}
Zoom In Zoom Out
{/* Help tooltip */}

Image Controls

Click image or zoom button to enlarge

Click and drag to pan when zoomed in

Double-click or use zoom out button to reset

); }; export default InteractiveImage;