Files

92 lines
2.1 KiB
TypeScript

"use client";
import { useRef, useState, type ReactNode } from "react";
import { motion } from "framer-motion";
interface MagneticButtonProps {
children: ReactNode;
className?: string;
href?: string;
onClick?: () => void;
strength?: number;
as?: "a" | "button" | "div" | "span" | "li";
target?: string;
rel?: string;
download?: boolean | string;
"data-cursor"?: string;
type?: "button" | "submit" | "reset";
wrapperClassName?: string;
"aria-label"?: string;
}
export default function MagneticButton({
children,
className = "",
href,
onClick,
strength = 0.3,
as = "button",
target,
rel,
download,
"data-cursor": dataCursor,
type,
wrapperClassName = "",
"aria-label": ariaLabel,
}: MagneticButtonProps) {
const ref = useRef<HTMLDivElement>(null);
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouse = (e: React.MouseEvent) => {
const el = ref.current;
if (!el) return;
const rect = el.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
setPosition({
x: (e.clientX - centerX) * strength,
y: (e.clientY - centerY) * strength,
});
};
const handleMouseLeave = () => {
setPosition({ x: 0, y: 0 });
};
const Tag = as === "a" ? motion.a : as === "button" ? motion.button : as === "div" ? motion.div : as === "span" ? motion.span : motion.li;
const linkProps = as === "a" ? { href, target, rel, download } : {};
const buttonProps = as === "button" ? { type } : {};
return (
<div
ref={ref}
onMouseMove={handleMouse}
onMouseLeave={handleMouseLeave}
className={`inline-block ${wrapperClassName}`.trim()}
>
<Tag
className={className}
onClick={onClick}
data-cursor={dataCursor}
animate={{
x: position.x,
y: position.y,
}}
transition={{
type: "spring",
stiffness: 350,
damping: 15,
mass: 0.5,
}}
{...linkProps}
{...buttonProps}
aria-label={ariaLabel}
>
{children}
</Tag>
</div>
);
}