| 1 | /** |
| 2 | * FavoriteButton - 收藏按钮组件 |
| 3 | * 功能:支持收藏/取消收藏操作,带 loading 状态和红心动画 |
| 4 | */ |
| 5 | |
| 6 | 'use client' |
| 7 | |
| 8 | import { Heart, Loader2 } from 'lucide-react' |
| 9 | import { useTransClient } from '@/app/i18n/client' |
| 10 | import { Button } from '@/components/ui/button' |
| 11 | import { cn } from '@/utils/className' |
| 12 | |
| 13 | export interface IFavoriteButtonProps { |
| 14 | /** 是否已收藏 */ |
| 15 | isFavorited: boolean |
| 16 | /** 是否加载中 */ |
| 17 | isLoading?: boolean |
| 18 | /** 点击回调 */ |
| 19 | onClick?: (e: React.MouseEvent) => void |
| 20 | /** 是否只显示图标(不显示文字) */ |
| 21 | iconOnly?: boolean |
| 22 | /** 按钮变体 */ |
| 23 | variant?: 'default' | 'ghost' | 'outline' |
| 24 | /** 自定义类名 */ |
| 25 | className?: string |
| 26 | /** 图标大小 */ |
| 27 | iconSize?: 'sm' | 'md' | 'lg' |
| 28 | } |
| 29 | |
| 30 | /** |
| 31 | * FavoriteButton - 可复用的收藏按钮 |
| 32 | */ |
| 33 | export function FavoriteButton({ |
| 34 | isFavorited, |
| 35 | isLoading = false, |
| 36 | onClick, |
| 37 | iconOnly = false, |
| 38 | variant = 'ghost', |
| 39 | className, |
| 40 | iconSize = 'md', |
| 41 | }: IFavoriteButtonProps) { |
| 42 | const { t } = useTransClient('chat') |
| 43 | |
| 44 | const iconSizeClass = { |
| 45 | sm: 'w-3 h-3', |
| 46 | md: 'w-4 h-4', |
| 47 | lg: 'w-5 h-5', |
| 48 | }[iconSize] |
| 49 | |
| 50 | const handleClick = (e: React.MouseEvent) => { |
| 51 | e.stopPropagation() |
| 52 | e.preventDefault() |
| 53 | if (isLoading) |
| 54 | return |
| 55 | onClick?.(e) |
| 56 | } |
| 57 | |
| 58 | return ( |
| 59 | <Button |
| 60 | variant={variant} |
| 61 | size={iconOnly ? 'icon' : 'sm'} |
| 62 | onClick={handleClick} |
| 63 | disabled={isLoading} |
| 64 | className={cn( |
| 65 | 'cursor-pointer transition-colors', |
| 66 | iconOnly ? 'h-8 w-8' : 'h-8 px-2 gap-1', |
| 67 | className, |
| 68 | )} |
| 69 | aria-label={isFavorited ? t('task.unfavorite') : t('task.favorite')} |
| 70 | > |
| 71 | {isLoading ? ( |
| 72 | <Loader2 className={cn(iconSizeClass, 'animate-spin')} /> |
| 73 | ) : ( |
| 74 | <Heart |
| 75 | className={cn( |
| 76 | iconSizeClass, |
| 77 | 'transition-colors', |
| 78 | isFavorited && 'text-red-500 fill-red-500', |
| 79 | )} |
| 80 | /> |
| 81 | )} |
| 82 | {!iconOnly && ( |
| 83 | <span className="text-xs">{isFavorited ? t('task.unfavorite') : t('task.favorite')}</span> |
| 84 | )} |
| 85 | </Button> |
| 86 | ) |
| 87 | } |
| 88 | |
| 89 | export default FavoriteButton |
| 90 |