"use client"
import * as React from "react"
import { useState, useEffect } from "react"
// Define types for component props
export interface PulseButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children?: React.ReactNode
variant?: "primary" | "secondary" | "success" | "danger" | "warning" | "info" | "ghost"
size?: "sm" | "md" | "lg"
glowEffect?: "none" | "subtle" | "intense"
loading?: boolean
disabled?: boolean
fullWidth?: boolean
icon?: React.ReactNode
iconPosition?: "left" | "right"
animationType?: "ripple" | "pulse" | "glitch" | "magnetic" | "none"
className?: string
}
export const PulseButton = ({
children,
variant = "primary",
size = "md",
glowEffect = "subtle",
loading = false,
disabled = false,
fullWidth = false,
icon = null,
iconPosition = "left",
animationType = "ripple",
className = "",
...props
}: PulseButtonProps) => {
const [isHovered, setIsHovered] = useState(false)
const [isPressed, setIsPressed] = useState(false)
const [rippleEffect, setRippleEffect] = useState<Array<{
x: number,
y: number,
size: number,
id: number
}>>([])
const [rippleCount, setRippleCount] = useState(0)
const [buttonPosition, setButtonPosition] = useState({ x: 0, y: 0 })
const buttonRef = React.useRef<HTMLButtonElement>(null)
// Magnetic effect handling
const handleMouseMove = (e: React.MouseEvent<HTMLButtonElement>) => {
if (animationType !== 'magnetic' || disabled || loading) return
const rect = buttonRef.current?.getBoundingClientRect()
if (!rect) return
const x = e.clientX - rect.left
const y = e.clientY - rect.top
const centerX = rect.width / 2
const centerY = rect.height / 2
const distanceX = x - centerX
const distanceY = y - centerY
setButtonPosition({
x: distanceX / 10,
y: distanceY / 10
})
}
const resetMagneticEffect = () => {
if (animationType !== 'magnetic') return
setButtonPosition({ x: 0, y: 0 })
}
// Ripple effect handling
const createRipple = (e: React.MouseEvent<HTMLButtonElement>) => {
if (animationType !== 'ripple' || disabled || loading) return
const button = e.currentTarget
const rect = button.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
const size = Math.max(rect.width, rect.height) * 2
const newRipple = {
x,
y,
size,
id: rippleCount
}
setRippleEffect(prev => [...prev, newRipple])
setRippleCount(prev => prev + 1)
// Remove ripple after animation completes
setTimeout(() => {
setRippleEffect(prev => prev.filter(ripple => ripple.id !== newRipple.id))
}, 1000)
}
// Variant styles
const variantStyles = {
primary: {
background: "bg-gradient-to-r from-blue-500 to-indigo-600",
hover: "hover:from-blue-600 hover:to-indigo-700",
text: "text-white",
border: "border-blue-600",
glow: "rgba(79, 70, 229, 0.6)",
ripple: "bg-blue-200 bg-opacity-30",
loading: "border-blue-200 border-t-blue-600"
},
secondary: {
background: "bg-gradient-to-r from-gray-700 to-gray-900",
hover: "hover:from-gray-800 hover:to-gray-950",
text: "text-gray-100",
border: "border-gray-600",
glow: "rgba(107, 114, 128, 0.6)",
ripple: "bg-gray-200 bg-opacity-30",
loading: "border-gray-400 border-t-gray-200"
},
success: {
background: "bg-gradient-to-r from-emerald-500 to-green-600",
hover: "hover:from-emerald-600 hover:to-green-700",
text: "text-white",
border: "border-green-600",
glow: "rgba(16, 185, 129, 0.6)",
ripple: "bg-green-200 bg-opacity-30",
loading: "border-green-200 border-t-green-600"
},
danger: {
background: "bg-gradient-to-r from-red-500 to-rose-600",
hover: "hover:from-red-600 hover:to-rose-700",
text: "text-white",
border: "border-red-600",
glow: "rgba(225, 29, 72, 0.6)",
ripple: "bg-red-200 bg-opacity-30",
loading: "border-red-200 border-t-red-600"
},
warning: {
background: "bg-gradient-to-r from-amber-400 to-orange-500",
hover: "hover:from-amber-500 hover:to-orange-600",
text: "text-white",
border: "border-amber-500",
glow: "rgba(251, 191, 36, 0.6)",
ripple: "bg-amber-200 bg-opacity-30",
loading: "border-amber-200 border-t-amber-500"
},
info: {
background: "bg-gradient-to-r from-cyan-500 to-sky-600",
hover: "hover:from-cyan-600 hover:to-sky-700",
text: "text-white",
border: "border-cyan-600",
glow: "rgba(6, 182, 212, 0.6)",
ripple: "bg-cyan-200 bg-opacity-30",
loading: "border-cyan-200 border-t-cyan-600"
},
ghost: {
background: "bg-transparent backdrop-blur-sm",
hover: "hover:bg-white hover:bg-opacity-10",
text: "text-white",
border: "border-white border-opacity-30",
glow: "rgba(255, 255, 255, 0.3)",
ripple: "bg-white bg-opacity-20",
loading: "border-white border-opacity-20 border-t-white"
}
}
// Size styles
const sizeStyles = {
sm: "text-xs px-3 py-1.5 rounded-md",
md: "text-sm px-4 py-2 rounded-lg",
lg: "text-base px-6 py-3 rounded-xl"
}
// Glow effect styles
const glowEffectStyles = {
none: "",
subtle: "shadow-md transition-shadow duration-300",
intense: "shadow-lg transition-shadow duration-300"
}
// Animation type styles
const animationTypeStyles = {
ripple: "overflow-hidden transition-transform duration-200 active:scale-95",
pulse: "animate-pulse transition-transform duration-200 active:scale-95",
glitch: "transition-all duration-300 active:scale-95 glitch-effect",
magnetic: "transition-all duration-300",
none: "transition-colors duration-200 active:scale-95"
}
// Combine styles
const variantStyle = variantStyles[variant]
const sizeStyle = sizeStyles[size]
const glowStyle = glowEffectStyles[glowEffect]
const animationStyle = animationTypeStyles[animationType]
// Magnetic effect tracking
useEffect(() => {
if (animationType !== 'magnetic') return
const handleMouseLeave = () => {
resetMagneticEffect()
}
const button = buttonRef.current
if (button) {
button.addEventListener('mouseleave', handleMouseLeave)
return () => {
button.removeEventListener('mouseleave', handleMouseLeave)
}
}
}, [animationType])
return (
<button
ref={buttonRef}
className={`
relative group font-medium border border-opacity-30 select-none
inline-flex items-center justify-center transition-all
${variantStyle.background} ${variantStyle.hover} ${variantStyle.text} ${variantStyle.border}
${sizeStyle} ${glowStyle} ${animationStyle} ${fullWidth ? 'w-full' : ''}
${className} ${disabled ? 'opacity-50 cursor-not-allowed' : ''}
`}
disabled={disabled || loading}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => {
setIsHovered(false)
resetMagneticEffect()
}}
onMouseDown={() => setIsPressed(true)}
onMouseUp={() => setIsPressed(false)}
onMouseMove={handleMouseMove}
onClick={createRipple}
style={
animationType === 'magnetic'
? {
transform: `translate(${buttonPosition.x}px, ${buttonPosition.y}px)`,
boxShadow: isHovered && glowEffect !== 'none'
? `0 0 ${glowEffect === 'intense' ? '25px' : '15px'} ${variantStyle.glow}`
: 'none'
}
: {
boxShadow: isHovered && glowEffect !== 'none'
? `0 0 ${glowEffect === 'intense' ? '25px' : '15px'} ${variantStyle.glow}`
: 'none'
}
}
{...props}
>
{/* Ripple effects */}
{animationType === 'ripple' && rippleEffect.map(ripple => (
<span
key={ripple.id}
className={`absolute rounded-full ${variantStyle.ripple} animate-ripple`}
style={{
left: ripple.x - ripple.size / 2,
top: ripple.y - ripple.size / 2,
width: ripple.size,
height: ripple.size
}}
/>
))}
{/* Glitch effect overlay */}
{animationType === 'glitch' && isHovered && (
<>
<span className="absolute inset-0 block translate-x-0.5 -translate-y-0.5 bg-inherit opacity-30" />
<span className="absolute inset-0 block -translate-x-0.5 translate-y-0.5 bg-inherit opacity-30" />
</>
)}
{/* Leading icon */}
{icon && iconPosition === 'left' && (
<span className={`inline-flex ${children ? 'mr-2' : ''}`}>
{icon}
</span>
)}
{/* Loading spinner */}
{loading ? (
<div className="flex items-center justify-center">
<div className={`animate-spin rounded-full h-4 w-4 border-2 ${variantStyle.loading} mr-2`}></div>
{children && <span>{children}</span>}
</div>
) : (
children
)}
{/* Trailing icon */}
{icon && iconPosition === 'right' && (
<span className={`inline-flex ${children ? 'ml-2' : ''}`}>
{icon}
</span>
)}
{/* Pulse animation border */}
{animationType === 'pulse' && !disabled && (
<span className={`absolute inset-0 rounded-xl ${variantStyle.border} opacity-0 group-hover:opacity-100 animate-ping`}></span>
)}
</button>
)
}
// Demo components for showcasing the PulseButton
import { Rocket, Zap, AlertTriangle, ArrowRight, Upload, Check, Ban } from "lucide-react"
// Basic demo with default variants
export const PulseButtonDemo = () => {
return (
<div className="flex flex-wrap gap-4 p-6 bg-gray-950 rounded-xl">
<PulseButton variant="primary" icon={<Rocket size={16} />}>
Launch App
</PulseButton>
<PulseButton variant="secondary" animationType="pulse">
Secondary
</PulseButton>
<PulseButton variant="success" icon={<Check size={16} />} glowEffect="intense">
Confirm
</PulseButton>
<PulseButton variant="danger" icon={<Ban size={16} />}>
Delete
</PulseButton>
<PulseButton variant="warning" icon={<AlertTriangle size={16} />}>
Warning
</PulseButton>
<PulseButton variant="info" loading>
Loading...
</PulseButton>
<PulseButton variant="ghost" animationType="glitch">
Ghost Button
</PulseButton>
</div>
)
}
// Demo showcasing different animation types
export const PulseButtonAnimations = () => {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 p-6 bg-gray-950 rounded-xl">
<div className="flex flex-col items-center gap-2">
<span className="text-sm text-gray-400">Ripple Effect</span>
<PulseButton
variant="primary"
animationType="ripple"
icon={<Zap size={16} />}
>
Click Me
</PulseButton>
</div>
<div className="flex flex-col items-center gap-2">
<span className="text-sm text-gray-400">Pulse Animation</span>
<PulseButton
variant="info"
animationType="pulse"
icon={<ArrowRight size={16} />}
iconPosition="right"
>
Next Step
</PulseButton>
</div>
<div className="flex flex-col items-center gap-2">
<span className="text-sm text-gray-400">Glitch Effect</span>
<PulseButton
variant="danger"
animationType="glitch"
glowEffect="intense"
>
Glitch
</PulseButton>
</div>
<div className="flex flex-col items-center gap-2">
<span className="text-sm text-gray-400">Magnetic Effect</span>
<PulseButton
variant="ghost"
animationType="magnetic"
icon={<Upload size={16} />}
>
Upload
</PulseButton>
</div>
</div>
)
}
// Demo showcasing different sizes
export const PulseButtonSizes = () => {
return (
<div className="flex items-center gap-4 p-6 bg-gray-950 rounded-xl">
<PulseButton variant="primary" size="sm">
Small
</PulseButton>
<PulseButton variant="primary" size="md">
Medium
</PulseButton>
<PulseButton variant="primary" size="lg">
Large
</PulseButton>
</div>
)
}