UNPKG

reactbits-mcp-server

Version:

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

94 lines (80 loc) 2.92 kB
import React from 'react'; import { gsap } from 'gsap'; import './FlowingMenu.css'; function FlowingMenu({ items = [] }) { return ( <div className="menu-wrap"> <nav className="menu"> {items.map((item, idx) => ( <MenuItem key={idx} {...item} /> ))} </nav> </div> ); } function MenuItem({ link, text, image }) { const itemRef = React.useRef(null); const marqueeRef = React.useRef(null); const marqueeInnerRef = React.useRef(null); const animationDefaults = { duration: 0.6, ease: 'expo' }; const findClosestEdge = (mouseX, mouseY, width, height) => { const topEdgeDist = distMetric(mouseX, mouseY, width / 2, 0); const bottomEdgeDist = distMetric(mouseX, mouseY, width / 2, height); return topEdgeDist < bottomEdgeDist ? 'top' : 'bottom'; }; const distMetric = (x, y, x2, y2) => { const xDiff = x - x2; const yDiff = y - y2; return xDiff * xDiff + yDiff * yDiff; }; const handleMouseEnter = (ev) => { if (!itemRef.current || !marqueeRef.current || !marqueeInnerRef.current) return; const rect = itemRef.current.getBoundingClientRect(); const x = ev.clientX - rect.left; const y = ev.clientY - rect.top; const edge = findClosestEdge(x, y, rect.width, rect.height); gsap.timeline({ defaults: animationDefaults }) .set(marqueeRef.current, { y: edge === 'top' ? '-101%' : '101%' }, 0) .set(marqueeInnerRef.current, { y: edge === 'top' ? '101%' : '-101%' }, 0) .to([marqueeRef.current, marqueeInnerRef.current], { y: '0%' }, 0); }; const handleMouseLeave = (ev) => { if (!itemRef.current || !marqueeRef.current || !marqueeInnerRef.current) return; const rect = itemRef.current.getBoundingClientRect(); const x = ev.clientX - rect.left; const y = ev.clientY - rect.top; const edge = findClosestEdge(x, y, rect.width, rect.height); gsap.timeline({ defaults: animationDefaults }) .to(marqueeRef.current, { y: edge === 'top' ? '-101%' : '101%' }, 0) .to(marqueeInnerRef.current, { y: edge === 'top' ? '101%' : '-101%' }, 0); }; const repeatedMarqueeContent = Array.from({ length: 4 }).map((_, idx) => ( <React.Fragment key={idx}> <span>{text}</span> <div className="marquee__img" style={{ backgroundImage: `url(${image})` }} /> </React.Fragment> )); return ( <div className="menu__item" ref={itemRef}> <a className="menu__item-link" href={link} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} > {text} </a> <div className="marquee" ref={marqueeRef}> <div className="marquee__inner-wrap" ref={marqueeInnerRef}> <div className="marquee__inner" aria-hidden="true"> {repeatedMarqueeContent} </div> </div> </div> </div> ); } export default FlowingMenu;