File size: 3,125 Bytes
373c769
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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;