import React, { useState, useRef } from 'react'; import { Loader2 } from 'lucide-react'; interface InteractiveButtonProps { children: React.ReactNode; onClick?: (e: React.MouseEvent) => void | Promise; variant?: 'primary' | 'secondary' | 'danger' | 'success' | 'ghost' | 'outline'; size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'; disabled?: boolean; loading?: boolean; icon?: React.ReactNode; iconPosition?: 'left' | 'right'; fullWidth?: boolean; ripple?: boolean; haptic?: boolean; className?: string; type?: 'button' | 'submit' | 'reset'; } /** * 增强的交互按钮组件 * 提供丰富的视觉反馈和微交互效果 */ export const InteractiveButton: React.FC = ({ children, onClick, variant = 'primary', size = 'md', disabled = false, loading = false, icon, iconPosition = 'left', fullWidth = false, ripple = true, haptic = true, className = '', type = 'button', }) => { const [isPressed, setIsPressed] = useState(false); const [ripples, setRipples] = useState>([]); const buttonRef = useRef(null); const rippleIdRef = useRef(0); const getVariantClasses = () => { const variants = { primary: 'bg-gradient-to-r from-primary-600 to-primary-700 hover:from-primary-700 hover:to-primary-800 text-white shadow-sm hover:shadow-md focus:ring-primary-500', secondary: 'bg-gray-100 hover:bg-gray-200 text-gray-900 shadow-sm hover:shadow focus:ring-gray-500', danger: 'bg-gradient-to-r from-red-600 to-red-700 hover:from-red-700 hover:to-red-800 text-white shadow-sm hover:shadow-md focus:ring-red-500', success: 'bg-gradient-to-r from-green-600 to-green-700 hover:from-green-700 hover:to-green-800 text-white shadow-sm hover:shadow-md focus:ring-green-500', ghost: 'hover:bg-gray-100 text-gray-700 hover:text-gray-900 focus:ring-gray-500', outline: 'border border-gray-300 hover:border-gray-400 bg-white hover:bg-gray-50 text-gray-700 hover:text-gray-900 shadow-sm focus:ring-gray-500', }; return variants[variant]; }; const getSizeClasses = () => { const sizes = { xs: 'px-2 py-1 text-xs', sm: 'px-3 py-1.5 text-sm', md: 'px-4 py-2 text-sm', lg: 'px-5 py-2.5 text-base', xl: 'px-6 py-3 text-lg', }; return sizes[size]; }; const getIconSize = () => { const iconSizes = { xs: 'w-3 h-3', sm: 'w-4 h-4', md: 'w-4 h-4', lg: 'w-5 h-5', xl: 'w-6 h-6', }; return iconSizes[size]; }; const handleClick = async (e: React.MouseEvent) => { if (disabled || loading) return; // 添加按压效果 setIsPressed(true); setTimeout(() => setIsPressed(false), 150); // 添加涟漪效果 if (ripple && buttonRef.current) { const rect = buttonRef.current.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const newRipple = { id: rippleIdRef.current++, x, y }; setRipples(prev => [...prev, newRipple]); // 移除涟漪效果 setTimeout(() => { setRipples(prev => prev.filter(r => r.id !== newRipple.id)); }, 600); } // 触觉反馈(如果支持) if (haptic && 'vibrate' in navigator) { navigator.vibrate(10); } // 执行点击处理 if (onClick) { await onClick(e); } }; const baseClasses = ` relative overflow-hidden inline-flex items-center justify-center font-medium rounded-lg transition-all duration-200 ease-out focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transform hover:scale-105 active:scale-95 ${isPressed ? 'animate-button-press' : ''} ${fullWidth ? 'w-full' : ''} `; return ( ); }; /** * 浮动操作按钮 */ interface FloatingActionButtonProps { onClick?: () => void; icon: React.ReactNode; tooltip?: string; variant?: 'primary' | 'secondary' | 'danger'; size?: 'sm' | 'md' | 'lg'; position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'; className?: string; } export const FloatingActionButton: React.FC = ({ onClick, icon, tooltip, variant = 'primary', size = 'md', position = 'bottom-right', className = '', }) => { const [showTooltip, setShowTooltip] = useState(false); const getVariantClasses = () => { const variants = { primary: 'bg-gradient-to-r from-primary-600 to-primary-700 hover:from-primary-700 hover:to-primary-800 text-white shadow-lg hover:shadow-xl', secondary: 'bg-white hover:bg-gray-50 text-gray-700 shadow-lg hover:shadow-xl border border-gray-200', danger: 'bg-gradient-to-r from-red-600 to-red-700 hover:from-red-700 hover:to-red-800 text-white shadow-lg hover:shadow-xl', }; return variants[variant]; }; const getSizeClasses = () => { const sizes = { sm: 'w-12 h-12', md: 'w-14 h-14', lg: 'w-16 h-16', }; return sizes[size]; }; const getPositionClasses = () => { const positions = { 'bottom-right': 'fixed bottom-6 right-6', 'bottom-left': 'fixed bottom-6 left-6', 'top-right': 'fixed top-6 right-6', 'top-left': 'fixed top-6 left-6', }; return positions[position]; }; const getIconSize = () => { const iconSizes = { sm: 'w-5 h-5', md: 'w-6 h-6', lg: 'w-8 h-8', }; return iconSizes[size]; }; return (
{/* 工具提示 */} {tooltip && showTooltip && (
{tooltip}
)}
); };