117 lines
3.1 KiB
TypeScript
117 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { motion } from "framer-motion";
|
|
import Link from "next/link";
|
|
import { ReactNode } from "react";
|
|
|
|
interface AnimatedButtonProps {
|
|
href?: string;
|
|
onClick?: () => void;
|
|
children: ReactNode;
|
|
variant?: "primary" | "secondary" | "accent" | "outline";
|
|
size?: "sm" | "md" | "lg";
|
|
className?: string;
|
|
external?: boolean;
|
|
icon?: ReactNode;
|
|
iconPosition?: "left" | "right";
|
|
}
|
|
|
|
export default function AnimatedButton({
|
|
href,
|
|
onClick,
|
|
children,
|
|
variant = "primary",
|
|
size = "md",
|
|
className = "",
|
|
external = false,
|
|
icon,
|
|
iconPosition = "right",
|
|
}: AnimatedButtonProps) {
|
|
const baseStyles =
|
|
"relative inline-flex items-center justify-center gap-2 font-medium rounded-full overflow-hidden transition-all duration-300 cursor-pointer";
|
|
|
|
const variants = {
|
|
primary:
|
|
"bg-[hsl(0,100%,40%)] text-white hover:bg-[hsl(0,100%,35%)] shadow-lg hover:shadow-xl hover:shadow-[hsl(0,100%,40%)]/25",
|
|
secondary:
|
|
"bg-neutral-900 text-white hover:bg-neutral-800 dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-100",
|
|
accent:
|
|
"bg-gradient-to-r from-emerald-600 to-emerald-500 text-white hover:from-emerald-700 hover:to-emerald-600 shadow-lg hover:shadow-xl hover:shadow-emerald-500/25",
|
|
outline:
|
|
"bg-transparent border-2 border-neutral-200 text-neutral-900 hover:border-[hsl(0,100%,40%)] hover:text-[hsl(0,100%,40%)] dark:border-neutral-700 dark:text-white dark:hover:border-[hsl(0,100%,50%)] dark:hover:text-[hsl(0,100%,50%)]",
|
|
};
|
|
|
|
const sizes = {
|
|
sm: "px-4 py-2 text-sm",
|
|
md: "px-6 py-3 text-base",
|
|
lg: "px-8 py-4 text-lg",
|
|
};
|
|
|
|
const buttonContent = (
|
|
<>
|
|
{icon && iconPosition === "left" && (
|
|
<motion.span
|
|
className="inline-flex"
|
|
whileHover={{ x: -2 }}
|
|
transition={{ duration: 0.2 }}
|
|
>
|
|
{icon}
|
|
</motion.span>
|
|
)}
|
|
<span>{children}</span>
|
|
{icon && iconPosition === "right" && (
|
|
<motion.span
|
|
className="inline-flex"
|
|
whileHover={{ x: 2 }}
|
|
transition={{ duration: 0.2 }}
|
|
>
|
|
{icon}
|
|
</motion.span>
|
|
)}
|
|
</>
|
|
);
|
|
|
|
const motionProps = {
|
|
whileHover: { scale: 1.02, y: -2 },
|
|
whileTap: { scale: 0.98 },
|
|
transition: { type: "spring" as const, stiffness: 400, damping: 17 },
|
|
};
|
|
|
|
if (href) {
|
|
if (external) {
|
|
return (
|
|
<motion.a
|
|
href={href}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className={`${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`}
|
|
{...motionProps}
|
|
>
|
|
{buttonContent}
|
|
</motion.a>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<motion.div {...motionProps} className="inline-block">
|
|
<Link
|
|
href={href}
|
|
className={`${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`}
|
|
>
|
|
{buttonContent}
|
|
</Link>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<motion.button
|
|
onClick={onClick}
|
|
className={`${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`}
|
|
{...motionProps}
|
|
>
|
|
{buttonContent}
|
|
</motion.button>
|
|
);
|
|
}
|