mixvideo-v2/apps/desktop/src/components/AnimatedButton.tsx

196 lines
5.2 KiB
TypeScript

import React, { useState } from 'react';
import { LucideIcon } from 'lucide-react';
interface AnimatedButtonProps {
children: React.ReactNode;
onClick?: () => void;
variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'success';
size?: 'sm' | 'md' | 'lg';
icon?: LucideIcon;
iconPosition?: 'left' | 'right';
disabled?: boolean;
loading?: boolean;
className?: string;
ripple?: boolean;
glow?: boolean;
type?: 'button' | 'submit' | 'reset';
}
/**
* 增强的动画按钮组件
* 支持涟漪效果、发光效果和各种微交互
*/
export const AnimatedButton: React.FC<AnimatedButtonProps> = ({
children,
onClick,
variant = 'primary',
size = 'md',
icon: Icon,
iconPosition = 'left',
disabled = false,
loading = false,
className = '',
ripple = true,
glow = false,
type = 'button'
}) => {
const [ripples, setRipples] = useState<Array<{ id: number; x: number; y: number }>>([]);
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
if (disabled || loading) return;
// 创建涟漪效果
if (ripple) {
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const newRipple = { id: Date.now(), x, y };
setRipples(prev => [...prev, newRipple]);
// 移除涟漪效果
setTimeout(() => {
setRipples(prev => prev.filter(r => r.id !== newRipple.id));
}, 600);
}
onClick?.();
};
const getVariantClasses = () => {
const base = 'btn relative overflow-hidden';
const variants = {
primary: 'btn-primary',
secondary: 'btn-secondary',
ghost: 'btn-ghost',
danger: 'btn-danger',
success: 'btn-success'
};
return `${base} ${variants[variant]}`;
};
const getSizeClasses = () => {
const sizes = {
sm: 'btn-sm',
md: '',
lg: 'btn-lg'
};
return sizes[size];
};
const glowClasses = glow ? 'shadow-glow hover:shadow-glow-lg' : '';
const disabledClasses = disabled ? 'opacity-50 cursor-not-allowed' : '';
const loadingClasses = loading ? 'cursor-wait' : '';
return (
<button
type={type}
className={`
${getVariantClasses()}
${getSizeClasses()}
${glowClasses}
${disabledClasses}
${loadingClasses}
${className}
group
transform transition-all duration-200
hover:scale-105 active:scale-95
focus:outline-none focus:ring-2 focus:ring-offset-2
`}
onClick={handleClick}
disabled={disabled || loading}
>
{/* 涟漪效果 */}
{ripples.map(ripple => (
<span
key={ripple.id}
className="absolute bg-white bg-opacity-30 rounded-full animate-ping"
style={{
left: ripple.x - 10,
top: ripple.y - 10,
width: 20,
height: 20,
pointerEvents: 'none'
}}
/>
))}
{/* 按钮内容 */}
<span className="relative flex items-center justify-center gap-2">
{/* 左侧图标 */}
{Icon && iconPosition === 'left' && (
<Icon
size={size === 'sm' ? 14 : size === 'lg' ? 20 : 16}
className={`transition-transform duration-200 ${loading ? 'animate-spin' : 'group-hover:scale-110'}`}
/>
)}
{/* 加载状态 */}
{loading && (
<div className="w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin" />
)}
{/* 文本内容 */}
<span className={`transition-all duration-200 ${loading ? 'opacity-70' : ''}`}>
{children}
</span>
{/* 右侧图标 */}
{Icon && iconPosition === 'right' && (
<Icon
size={size === 'sm' ? 14 : size === 'lg' ? 20 : 16}
className={`transition-transform duration-200 ${loading ? 'animate-spin' : 'group-hover:scale-110'}`}
/>
)}
</span>
{/* 悬停光晕效果 */}
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white to-transparent opacity-0 group-hover:opacity-10 transition-opacity duration-300 -skew-x-12 transform translate-x-full group-hover:translate-x-[-100%] transition-transform duration-700" />
</button>
);
};
/**
* 浮动操作按钮组件
*/
export const FloatingActionButton: React.FC<{
icon: LucideIcon;
onClick: () => void;
className?: string;
size?: 'sm' | 'md' | 'lg';
}> = ({ icon: Icon, onClick, className = '', size = 'md' }) => {
const sizeClasses = {
sm: 'w-12 h-12',
md: 'w-14 h-14',
lg: 'w-16 h-16'
};
const iconSizes = {
sm: 20,
md: 24,
lg: 28
};
return (
<button
onClick={onClick}
className={`
${sizeClasses[size]}
bg-primary-600 hover:bg-primary-700 text-white
rounded-full shadow-lg hover:shadow-xl
flex items-center justify-center
transition-all duration-300
hover:scale-110 active:scale-95
focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2
group
${className}
`}
>
<Icon
size={iconSizes[size]}
className="transition-transform duration-200 group-hover:rotate-12"
/>
</button>
);
};