UNPKG

reactbits-mcp-server

Version:

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

116 lines (104 loc) 3.04 kB
import { motion } from 'framer-motion'; import { useEffect, useRef, useState, useMemo } from 'react'; const buildKeyframes = (from, steps) => { const keys = new Set([ ...Object.keys(from), ...steps.flatMap((s) => Object.keys(s)), ]); const keyframes = {}; keys.forEach((k) => { keyframes[k] = [from[k], ...steps.map((s) => s[k])]; }); return keyframes; }; const BlurText = ({ text = '', delay = 200, className = '', animateBy = 'words', direction = 'top', threshold = 0.1, rootMargin = '0px', animationFrom, animationTo, easing = (t) => t, onAnimationComplete, stepDuration = 0.35, }) => { const elements = animateBy === 'words' ? text.split(' ') : text.split(''); const [inView, setInView] = useState(false); const ref = useRef(null); useEffect(() => { if (!ref.current) return; const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setInView(true); observer.unobserve(ref.current); } }, { threshold, rootMargin } ); observer.observe(ref.current); return () => observer.disconnect(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [threshold, rootMargin]); const defaultFrom = useMemo( () => direction === 'top' ? { filter: 'blur(10px)', opacity: 0, y: -50 } : { filter: 'blur(10px)', opacity: 0, y: 50 }, [direction] ); const defaultTo = useMemo( () => [ { filter: 'blur(5px)', opacity: 0.5, y: direction === 'top' ? 5 : -5, }, { filter: 'blur(0px)', opacity: 1, y: 0 }, ], [direction] ); const fromSnapshot = animationFrom ?? defaultFrom; const toSnapshots = animationTo ?? defaultTo; const stepCount = toSnapshots.length + 1; const totalDuration = stepDuration * (stepCount - 1); const times = Array.from({ length: stepCount }, (_, i) => stepCount === 1 ? 0 : i / (stepCount - 1) ); return ( <p ref={ref} className={className} style={{ display: 'flex', flexWrap: 'wrap' }} > {elements.map((segment, index) => { const animateKeyframes = buildKeyframes(fromSnapshot, toSnapshots); const spanTransition = { duration: totalDuration, times, delay: (index * delay) / 1000, }; (spanTransition).ease = easing; return ( <motion.span className="inline-block will-change-[transform,filter,opacity]" key={index} initial={fromSnapshot} animate={inView ? animateKeyframes : fromSnapshot} transition={spanTransition} onAnimationComplete={ index === elements.length - 1 ? onAnimationComplete : undefined } > {segment === ' ' ? '\u00A0' : segment} {animateBy === 'words' && index < elements.length - 1 && '\u00A0'} </motion.span> ); })} </p> ); }; export default BlurText;