Files
mixvideo-v2/apps/desktop/src/components/AnimatedButton.tsx
imeepos c366261008 feat: UI美化和UX改进 v0.1.5
- 优化设计系统和主题配置
  - 增强Tailwind配置,添加更丰富的色彩系统
  - 新增多种动画效果和过渡动画
  - 改进字体系统和间距设置
  - 添加阴影变体和渐变效果

- 改进项目列表页面UI/UX
  - 重新设计页面头部,添加渐变背景和统计信息
  - 优化项目卡片布局和响应式设计
  - 增强加载和错误状态的视觉效果
  - 添加交错动画效果

- 优化项目卡片组件
  - 重新设计卡片布局,添加背景装饰
  - 改进统计信息展示,使用渐变背景和徽章
  - 增强悬停效果和交互反馈
  - 优化菜单和按钮设计

- 改进表单和模态框体验
  - 重新设计模态框头部和布局
  - 增强表单字段的视觉设计和验证反馈
  - 添加加载状态和成功状态指示
  - 改进按钮样式和交互效果

- 增强加载状态和反馈机制
  - 重新设计EmptyState组件,添加装饰效果
  - 增强LoadingSpinner,支持多种动画样式
  - 改进ErrorMessage组件,支持多种消息类型
  - 新增SkeletonLoader组件用于骨架屏加载

- 添加微交互和动画效果
  - 新增AnimatedButton组件,支持涟漪效果
  - 创建PageTransition组件用于页面过渡
  - 添加多种动画工具组件
  - 增强按钮和卡片的微交互效果

- 优化响应式设计和移动端体验
  - 改进容器布局和间距设置
  - 优化移动端的触摸体验
  - 确保所有组件在不同屏幕尺寸下的完美适配

遵循前端开发规范,提升界面美观性和用户体验
2025-07-14 00:23:58 +08:00

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>
);
};