UNPKG

reactbits-mcp-server

Version:

MCP Server for React Bits - Access 99+ React components with animations, backgrounds, and UI elements

265 lines (228 loc) 7.95 kB
import { useRef, useEffect, useMemo } from 'react'; import { gsap } from 'gsap'; import { Draggable } from 'gsap/Draggable'; import './StickerPeel.css'; gsap.registerPlugin(Draggable); const StickerPeel = ({ imageSrc, rotate = 30, peelBackHoverPct = 30, peelBackActivePct = 40, peelEasing = 'power3.out', peelHoverEasing = 'power2.out', width = 200, shadowIntensity = 0.6, lightingIntensity = 0.1, initialPosition = 'center', peelDirection = 0, className = '' }) => { const containerRef = useRef(null); const dragTargetRef = useRef(null); const pointLightRef = useRef(null); const pointLightFlippedRef = useRef(null); const draggableInstanceRef = useRef(null); const defaultPadding = 10; useEffect(() => { const target = dragTargetRef.current; if (!target) return; let startX = 0, startY = 0; if (initialPosition === 'center') { return; } if (typeof initialPosition === 'object' && initialPosition.x !== undefined && initialPosition.y !== undefined) { startX = initialPosition.x; startY = initialPosition.y; } gsap.set(target, { x: startX, y: startY }); }, [initialPosition]); useEffect(() => { const target = dragTargetRef.current; const boundsEl = target.parentNode; draggableInstanceRef.current = Draggable.create(target, { type: 'x,y', bounds: boundsEl, inertia: true, onDrag() { const rot = gsap.utils.clamp(-24, 24, this.deltaX * 0.4); gsap.to(target, { rotation: rot, duration: 0.15, ease: 'power1.out' }); }, onDragEnd() { const rotationEase = 'power2.out'; const duration = 0.8; gsap.to(target, { rotation: 0, duration, ease: rotationEase }); } })[0]; const handleResize = () => { if (draggableInstanceRef.current) { draggableInstanceRef.current.update(); const currentX = gsap.getProperty(target, "x"); const currentY = gsap.getProperty(target, "y"); const boundsRect = boundsEl.getBoundingClientRect(); const targetRect = target.getBoundingClientRect(); const maxX = boundsRect.width - targetRect.width; const maxY = boundsRect.height - targetRect.height; const newX = Math.max(0, Math.min(currentX, maxX)); const newY = Math.max(0, Math.min(currentY, maxY)); if (newX !== currentX || newY !== currentY) { gsap.to(target, { x: newX, y: newY, duration: 0.3, ease: "power2.out" }); } } }; window.addEventListener('resize', handleResize); window.addEventListener('orientationchange', handleResize); return () => { window.removeEventListener('resize', handleResize); window.removeEventListener('orientationchange', handleResize); if (draggableInstanceRef.current) { draggableInstanceRef.current.kill(); } }; }, []); useEffect(() => { const updateLight = (e) => { const rect = containerRef.current?.getBoundingClientRect(); if (!rect) return; const x = e.clientX - rect.left; const y = e.clientY - rect.top; gsap.set(pointLightRef.current, { attr: { x, y } }); const normalizedAngle = Math.abs(peelDirection % 360); if (normalizedAngle !== 180) { gsap.set(pointLightFlippedRef.current, { attr: { x, y: rect.height - y } }); } else { gsap.set(pointLightFlippedRef.current, { attr: { x: -1000, y: -1000 } }); } }; const container = containerRef.current; if (container) { container.addEventListener('mousemove', updateLight); return () => container.removeEventListener('mousemove', updateLight); } }, [peelDirection]); useEffect(() => { const container = containerRef.current; if (!container) return; const handleTouchStart = () => { container.classList.add('touch-active'); }; const handleTouchEnd = () => { container.classList.remove('touch-active'); }; container.addEventListener('touchstart', handleTouchStart); container.addEventListener('touchend', handleTouchEnd); container.addEventListener('touchcancel', handleTouchEnd); return () => { container.removeEventListener('touchstart', handleTouchStart); container.removeEventListener('touchend', handleTouchEnd); container.removeEventListener('touchcancel', handleTouchEnd); }; }, []); const cssVars = useMemo( () => ({ '--sticker-rotate': `${rotate}deg`, '--sticker-p': `${defaultPadding}px`, '--sticker-peelback-hover': `${peelBackHoverPct}%`, '--sticker-peelback-active': `${peelBackActivePct}%`, '--sticker-peel-easing': peelEasing, '--sticker-peel-hover-easing': peelHoverEasing, '--sticker-width': `${width}px`, '--sticker-shadow-opacity': shadowIntensity, '--sticker-lighting-constant': lightingIntensity, '--peel-direction': `${peelDirection}deg` }), [ rotate, peelBackHoverPct, peelBackActivePct, peelEasing, peelHoverEasing, width, shadowIntensity, lightingIntensity, peelDirection ] ); return ( <div className={`draggable ${className}`} ref={dragTargetRef} style={cssVars}> <svg width="0" height="0"> <defs> <filter id="pointLight"> <feGaussianBlur stdDeviation="1" result="blur" /> <feSpecularLighting result="spec" in="blur" specularExponent="100" specularConstant={lightingIntensity} lightingColor="white" > <fePointLight ref={pointLightRef} x="100" y="100" z="300" /> </feSpecularLighting> <feComposite in="spec" in2="SourceGraphic" result="lit" /> <feComposite in="lit" in2="SourceAlpha" operator="in" /> </filter> <filter id="pointLightFlipped"> <feGaussianBlur stdDeviation="10" result="blur" /> <feSpecularLighting result="spec" in="blur" specularExponent="100" specularConstant={lightingIntensity * 7} lightingColor="white" > <fePointLight ref={pointLightFlippedRef} x="100" y="100" z="300" /> </feSpecularLighting> <feComposite in="spec" in2="SourceGraphic" result="lit" /> <feComposite in="lit" in2="SourceAlpha" operator="in" /> </filter> <filter id="dropShadow"> <feDropShadow dx="2" dy="4" stdDeviation={3 * shadowIntensity} floodColor="black" floodOpacity={shadowIntensity} /> </filter> <filter id="expandAndFill"> <feOffset dx="0" dy="0" in="SourceAlpha" result="shape" /> <feFlood floodColor="rgb(179,179,179)" result="flood" /> <feComposite operator="in" in="flood" in2="shape" /> </filter> </defs> </svg> <div className="sticker-container" ref={containerRef} > <div className="sticker-main"> <div className="sticker-lighting"> <img src={imageSrc} alt="" className="sticker-image" draggable="false" onContextMenu={(e) => e.preventDefault()} /> </div> </div> <div className="flap"> <div className="flap-lighting"> <img src={imageSrc} alt="" className="flap-image" draggable="false" onContextMenu={(e) => e.preventDefault()} /> </div> </div> </div> </div> ); }; export default StickerPeel;