92 lines
2.1 KiB
TypeScript
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>
|
|
);
|
|
}
|