'use client'; import * as React from 'react'; import { motion, AnimatePresence, type HTMLMotionProps } from 'framer-motion'; import { cva, type VariantProps } from 'class-variance-authority'; import { cn } from '@/lib/utils'; const rippleButtonVariants = cva( 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all duration-300 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-[#1C1C1C] focus-visible:ring-offset-2 focus-visible:ring-offset-white relative overflow-hidden', { variants: { variant: { default: 'bg-[#C41E3A] text-white hover:bg-[#A01830] hover:shadow-[0_8px_20px_rgba(196,30,58,0.35)]', secondary: 'bg-[#1C1C1C] text-white hover:bg-[#0A0A0A] hover:shadow-[0_8px_20px_rgba(28,28,28,0.35)]', destructive: 'bg-[#C41E3A] text-white hover:bg-[#A01830] focus-visible:ring-[#C41E3A]', outline: 'border-2 border-[#1C1C1C] bg-transparent text-[#1C1C1C] hover:bg-[#F5F5F5] hover:shadow-[0_4px_12px_rgba(28,28,28,0.2)]', ghost: 'text-[#3D3D3D] hover:bg-[#F5F5F5] hover:text-[#1C1C1C]', link: 'text-[#1C1C1C] underline-offset-4 hover:underline hover:text-[#C41E3A]', seal: 'bg-[#C41E3A] text-white font-semibold hover:bg-[#A01830] shadow-[0_6px_20px_rgba(196,30,58,0.3)]', }, size: { default: 'h-10 px-4 py-2', sm: 'h-8 rounded-md px-3 text-xs', lg: 'h-12 rounded-lg px-6 text-base', icon: 'h-10 w-10', }, }, defaultVariants: { variant: 'default', size: 'default', }, } ); interface Ripple { x: number; y: number; id: number; } export interface RippleButtonProps extends VariantProps { rippleColor?: string; rippleDuration?: number; children?: React.ReactNode; onClick?: (e: React.MouseEvent) => void; className?: string; disabled?: boolean; } const RippleButton = React.forwardRef( ({ className, variant, size, rippleColor, rippleDuration = 800, onClick, children, disabled, ...props }, ref) => { const [ripples, setRipples] = React.useState([]); const handleClick = (e: React.MouseEvent) => { if (disabled) return; const button = e.currentTarget; const rect = button.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const id = Date.now(); setRipples((prev) => [...prev, { x, y, id }]); setTimeout(() => { setRipples((prev) => prev.filter((r) => r.id !== id)); }, rippleDuration); onClick?.(e); }; const getRippleColor = () => { if (rippleColor) return rippleColor; if (variant === 'outline' || variant === 'ghost' || variant === 'link') { return 'rgba(196, 30, 58, 0.2)'; } return 'rgba(255, 255, 255, 0.4)'; }; return ( {children} {ripples.map((ripple) => ( ))} ); } ); RippleButton.displayName = 'RippleButton'; export interface SealButtonProps extends VariantProps { children?: React.ReactNode; onClick?: (e: React.MouseEvent) => void; className?: string; disabled?: boolean; } const SealButton = React.forwardRef( ({ className, variant = 'seal', size, onClick, children, disabled, ...props }, ref) => { const [isPressed, setIsPressed] = React.useState(false); const [showInk, setShowInk] = React.useState(false); const handleClick = (e: React.MouseEvent) => { if (disabled) return; setIsPressed(true); setShowInk(true); setTimeout(() => setIsPressed(false), 600); setTimeout(() => setShowInk(false), 800); onClick?.(e); }; return ( {showInk && (
)} {children} ); } ); SealButton.displayName = 'SealButton'; export { RippleButton, SealButton, rippleButtonVariants };