|
import React from 'react';
|
|
import { gsap } from 'gsap';
|
|
|
|
function FlowingMenu({ items = [], activeItem, onItemClick }) {
|
|
return (
|
|
<div className="flowing-menu-container">
|
|
<nav className="flowing-menu-nav">
|
|
{items.map((item, idx) => (
|
|
<MenuItem
|
|
key={item.id}
|
|
link="#"
|
|
text={item.label}
|
|
image={`https://picsum.photos/600/400?random=${idx + 1}`}
|
|
isActive={activeItem === item.id}
|
|
onClick={() => onItemClick(item.id)}
|
|
icon={item.icon}
|
|
/>
|
|
))}
|
|
</nav>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function MenuItem({ link, text, image, isActive, onClick, icon }) {
|
|
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 = (mouseX - width / 2) ** 2 + mouseY ** 2;
|
|
const bottomEdgeDist = (mouseX - width / 2) ** 2 + (mouseY - height) ** 2;
|
|
return topEdgeDist < bottomEdgeDist ? 'top' : 'bottom';
|
|
};
|
|
|
|
const handleMouseEnter = (ev) => {
|
|
if (!itemRef.current || !marqueeRef.current || !marqueeInnerRef.current) return;
|
|
|
|
const rect = itemRef.current.getBoundingClientRect();
|
|
const edge = findClosestEdge(
|
|
ev.clientX - rect.left,
|
|
ev.clientY - rect.top,
|
|
rect.width,
|
|
rect.height
|
|
);
|
|
|
|
gsap.timeline({ defaults: animationDefaults })
|
|
.set(marqueeRef.current, { y: edge === 'top' ? '-101%' : '101%' })
|
|
.set(marqueeInnerRef.current, { y: edge === 'top' ? '101%' : '-101%' })
|
|
.to([marqueeRef.current, marqueeInnerRef.current], { y: '0%' });
|
|
};
|
|
|
|
const handleMouseLeave = (ev) => {
|
|
if (!itemRef.current || !marqueeRef.current || !marqueeInnerRef.current) return;
|
|
|
|
const rect = itemRef.current.getBoundingClientRect();
|
|
const edge = findClosestEdge(
|
|
ev.clientX - rect.left,
|
|
ev.clientY - rect.top,
|
|
rect.width,
|
|
rect.height
|
|
);
|
|
|
|
gsap.timeline({ defaults: animationDefaults })
|
|
.to(marqueeRef.current, { y: edge === 'top' ? '-101%' : '101%' })
|
|
.to(marqueeInnerRef.current, { y: edge === 'top' ? '101%' : '-101%' });
|
|
};
|
|
|
|
const repeatedMarqueeContent = [];
|
|
|
|
return (
|
|
<div className={`flowing-menu-item ${isActive ? 'active' : ''}`} ref={itemRef}>
|
|
<a
|
|
className="menu-item-link"
|
|
href={link}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
onClick();
|
|
}}
|
|
onMouseEnter={handleMouseEnter}
|
|
onMouseLeave={handleMouseLeave}
|
|
>
|
|
<span className="menu-icon">{icon}</span>
|
|
<span className="menu-text">{text}</span>
|
|
</a>
|
|
<div className="marquee-overlay" ref={marqueeRef}>
|
|
<div className="marquee-inner" ref={marqueeInnerRef}>
|
|
<div className="marquee-content">
|
|
{repeatedMarqueeContent}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default FlowingMenu; |