"use client"; import React, { useRef, useState } from "react"; import { AnimatePresence, motion, Easing } from "framer-motion"; function cn(...inputs: (string | undefined | null | boolean)[]) { return inputs.filter(Boolean).join(" "); } interface LensProps { children: React.ReactNode; zoomFactor?: number; lensSize?: number; position?: { x: number; y: number; }; isStatic?: boolean; isFocusing?: () => void; hovering?: boolean; setHovering?: (hovering: boolean) => void; className?: string; borderRadius?: string; borderWidth?: number; borderColor?: string; shadowIntensity?: 'none' | 'light' | 'medium' | 'heavy'; animationDuration?: number; animationEasing?: Easing | Easing[]; maskShape?: 'circle' | 'square'; opacity?: number; blurEdge?: boolean; smoothFollow?: boolean; disabled?: boolean; } export const Lens: React.FC = ({ children, zoomFactor = 1.5, lensSize = 170, isStatic = false, position = { x: 200, y: 150 }, hovering, setHovering, className, borderRadius = "lg", borderWidth = 0, borderColor = "border-gray-300", shadowIntensity = 'medium', animationDuration = 0.3, animationEasing = "easeOut", maskShape = 'circle', opacity = 1, blurEdge = false, smoothFollow = true, disabled = false, }) => { const containerRef = useRef(null); const [localIsHovering, setLocalIsHovering] = useState(false); const [mousePosition, setMousePosition] = useState({ x: 100, y: 100 }); const isHovering = hovering !== undefined ? hovering : localIsHovering; const setIsHovering = setHovering || setLocalIsHovering; const handleMouseMove = (e: React.MouseEvent) => { if (disabled || isStatic) return; const rect = e.currentTarget.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; if (smoothFollow) { setMousePosition({ x, y }); } else { // Snap to grid for less smooth following const gridSize = 20; setMousePosition({ x: Math.round(x / gridSize) * gridSize, y: Math.round(y / gridSize) * gridSize, }); } }; const shadowClasses = { none: '', light: 'shadow-sm', medium: 'shadow-md', heavy: 'shadow-xl', }; const getMaskImage = (x: number, y: number) => { const radius = lensSize / 2; // Using string concatenation for shape and gradient const shape = maskShape === 'circle' ? "circle " + radius + "px at " + x + "px " + y + "px" : "ellipse " + radius + "px " + radius + "px at " + x + "px " + y + "px"; const gradient = blurEdge ? "radial-gradient(" + shape + ", black 60%, transparent 100%)" : "radial-gradient(" + shape + ", black 100%, transparent 100%)"; return gradient; }; const currentX = isStatic ? position.x : mousePosition.x; const currentY = isStatic ? position.y : mousePosition.y; const lensContent = ( 0 && "border-" + borderWidth + " " + borderColor, // String concatenation shadowClasses[shadowIntensity] )} style={{ maskImage: getMaskImage(currentX, currentY), WebkitMaskImage: getMaskImage(currentX, currentY), transformOrigin: currentX + "px " + currentY + "px", // String concatenation zIndex: 50, }} >
{children}
); return (
{children} {isStatic ? (
{lensContent}
) : ( {isHovering && !disabled && (
{lensContent}
)}
)}
); };