112 lines
2.4 KiB
TypeScript
112 lines
2.4 KiB
TypeScript
"use client";
|
|
|
|
import { motion } from "motion/react";
|
|
import { type MouseEvent, type ReactNode, useState } from "react";
|
|
|
|
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;
|
|
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,
|
|
type,
|
|
wrapperClassName = "",
|
|
"aria-label": ariaLabel,
|
|
}: MagneticButtonProps) {
|
|
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
|
|
const handleMouse = (e: MouseEvent<HTMLElement>) => {
|
|
const rect = e.currentTarget.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 } : {};
|
|
|
|
const motionProps = {
|
|
animate: {
|
|
x: position.x,
|
|
y: position.y,
|
|
},
|
|
transition: {
|
|
type: "spring" as const,
|
|
stiffness: 350,
|
|
damping: 15,
|
|
mass: 0.5,
|
|
},
|
|
};
|
|
|
|
if (as === "li") {
|
|
return (
|
|
<Tag
|
|
className={`${className} ${wrapperClassName}`.trim()}
|
|
onMouseMove={handleMouse}
|
|
onMouseLeave={handleMouseLeave}
|
|
onClick={onClick}
|
|
{...motionProps}
|
|
{...linkProps}
|
|
{...buttonProps}
|
|
aria-label={ariaLabel}
|
|
>
|
|
{children}
|
|
</Tag>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className={`inline-block ${wrapperClassName}`.trim()}>
|
|
<Tag
|
|
className={className}
|
|
onMouseMove={handleMouse}
|
|
onMouseLeave={handleMouseLeave}
|
|
onClick={onClick}
|
|
{...motionProps}
|
|
{...linkProps}
|
|
{...buttonProps}
|
|
aria-label={ariaLabel}
|
|
>
|
|
{children}
|
|
</Tag>
|
|
</div>
|
|
);
|
|
}
|