File size: 2,835 Bytes
42da440
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import React, { useState } from "react";
import { motion, useAnimation } from "framer-motion";
import ButtonParticles from "./ButtonParticles";

const EnhancedButton = ({
  onClick,
  children,
  className = "",
  disabled = false,
  title = "",
  particleColor = "#ffffff",
  particleCount = 50,
  particleSize = 10,
  particleDuration = 3,
  ...props
}) => {
  const [isHovering, setIsHovering] = useState(false);
  const [hasBeenClickedSinceHover, setHasBeenClickedSinceHover] = useState(false);
  const [particleTrigger, setParticleTrigger] = useState(0);

  /* --- controller that drives ONLY the shake --- */
  const shake = useAnimation();

  const fireShake = () =>
    shake.start({
      x: [0, -3, 3, -3, 3, 0],
      y: [0,  3, -3,  3, -3, 0],
      transition: { duration: 0.25, ease: "easeInOut" }
    });

  const handleClick = (e) => {
    if (disabled) return;
    onClick?.(e);
    setHasBeenClickedSinceHover(true);
    setParticleTrigger((t) => t + 1);
    fireShake();
  };

  /* -------- hover / normal variants (scale only) -------- */
  const variants = {
    initial:        { scale: 1, filter: "brightness(1)" },
    hoverInitial:   {
      scale: [1, 1.06, 1],
      transition: { scale: { duration: 0.8, repeat: Infinity, ease: "easeInOut" } }
    },
    hoverClicked:   { scale: 1.03, transition: { duration: 0.2 } }
  };

  const state =
    disabled
      ? "initial"
      : isHovering
      ? hasBeenClickedSinceHover
        ? "hoverClicked"
        : "hoverInitial"
      : "initial";

  /* ---------------- render ---------------- */
  return (
    /* wrapper receives the shake controller */
    <motion.div animate={shake} initial={{ x: 0, y: 0 }}>
      <motion.button
        type="button"
        className={`relative overflow-visible transition-colors duration-200 ease-in-out
                    ${className} ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
        title={title}
        disabled={disabled}
        variants={variants}
        initial="initial"
        animate={state}
        whileTap={!disabled ? { scale: 0.8 } : false}  /* press-in squash only */
        onHoverStart={() => { setIsHovering(true); setHasBeenClickedSinceHover(false); }}
        onHoverEnd={() => setIsHovering(false)}
        onClick={handleClick}
        {...props}
      >
        <span className="relative z-10 flex items-center justify-center gap-2">
          {children}
        </span>

        {/* particles */}
        <div className="absolute inset-0 z-0 pointer-events-none">
          <ButtonParticles
            trigger={particleTrigger}
            color={particleColor}
            count={particleCount}
            size={particleSize}
            duration={particleDuration}
          />
        </div>
      </motion.button>
    </motion.div>
  );
};

export default EnhancedButton;