import { useState, useCallback } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
shape: 'circle' | 'square' | 'star' | 'heart';
interface ParticleButtonProps {
children: React.ReactNode;
particleSize?: [number, number];
particleColors?: string[];
particleShape?: 'circle' | 'square' | 'star' | 'heart' | 'mixed';
burstDirection?: 'radial' | 'upward' | 'explosion';
variant?: 'default' | 'gradient' | 'outline' | 'neon' | 'glass';
const shapes = ['circle', 'square', 'star', 'heart'] as const;
'#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4',
'#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F',
export default function ParticleButton({
particleColors = defaultColors,
burstDirection = 'radial',
}: ParticleButtonProps) {
const [particles, setParticles] = useState<Particle[]>([]);
const [isAnimating, setIsAnimating] = useState(false);
const getShape = (): Particle['shape'] => {
if (particleShape === 'mixed') {
return shapes[Math.floor(Math.random() * shapes.length)];
const createParticles = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
if (disabled || isAnimating) return;
const rect = e.currentTarget.getBoundingClientRect();
const centerX = rect.width / 2;
const centerY = rect.height / 2;
const newParticles: Particle[] = [];
for (let i = 0; i < particleCount; i++) {
switch (burstDirection) {
angle = -90 + (Math.random() - 0.5) * 60;
velocity = spread * (0.5 + Math.random() * 0.5);
angle = Math.random() * 360;
velocity = spread * (0.8 + Math.random() * 0.4);
angle = (360 / particleCount) * i + Math.random() * 20;
velocity = spread * (0.6 + Math.random() * 0.4);
size: particleSize[0] + Math.random() * (particleSize[1] - particleSize[0]),
color: particleColors[Math.floor(Math.random() * particleColors.length)],
rotation: Math.random() * 360,
setParticles(newParticles);
}, [disabled, isAnimating, particleCount, particleSize, particleColors, burstDirection, spread, duration, onClick, particleShape]);
default: 'bg-neutral-900 text-white hover:bg-neutral-800 dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-100',
gradient: 'bg-gradient-to-r from-purple-600 via-pink-600 to-orange-500 text-white hover:opacity-90',
outline: 'border-2 border-neutral-900 text-neutral-900 hover:bg-neutral-900 hover:text-white dark:border-white dark:text-white dark:hover:bg-white dark:hover:text-neutral-900',
neon: 'bg-cyan-500 text-white shadow-[0_0_20px_rgba(6,182,212,0.5)] hover:shadow-[0_0_30px_rgba(6,182,212,0.7)] hover:bg-cyan-400',
glass: 'bg-white/10 backdrop-blur-md border border-white/20 text-white hover:bg-white/20',
const renderShape = (particle: Particle) => {
const baseStyle = { backgroundColor: particle.color };
switch (particle.shape) {
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
relative overflow-visible
px-6 py-3 rounded-full font-medium
transition-all duration-200
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500
disabled:opacity-50 disabled:cursor-not-allowed
${variantStyles[variant]}
onClick={createParticles}
<span className="relative z-10">{children}</span>
{particles.map((particle) => {
const radians = (particle.angle * Math.PI) / 180;
const endX = Math.cos(radians) * particle.velocity;
const endY = Math.sin(radians) * particle.velocity;
className="absolute pointer-events-none"
rotate: particle.rotation + 180,
exit={{ opacity: 0, scale: 0 }}
duration: duration / 1000,
ease: [0.25, 0.46, 0.45, 0.94],