VetNet-LCPS / vetcopilot-app /src /components /FavoriteButton.tsx
thomasm291678
VetCopilot-LCPS: LCPS 2.0 本地推理引擎替换 DeepSeek API
793e953
Raw History Blame Contribute Delete
1.75 kB
import React, { useState } from 'react'
import { useTranslation } from '../i18n'
interface FavoriteButtonProps {
active?: boolean
onChange?: (active: boolean) => void
size?: 'sm' | 'md' | 'lg'
className?: string
}
export default function FavoriteButton({ active = false, onChange, size = 'md', className = '' }: FavoriteButtonProps) {
const { t } = useTranslation()
const [isActive, setIsActive] = useState(active)
const [animating, setAnimating] = useState(false)
const sizeClasses = { sm: 'w-6 h-6', md: 'w-8 h-8', lg: 'w-10 h-10' }
const iconSizes = { sm: 'w-3.5 h-3.5', md: 'w-4 h-4', lg: 'w-5 h-5' }
const handleClick = (e: React.MouseEvent) => {
e.stopPropagation()
const newState = !isActive
setIsActive(newState)
if (newState) {
setAnimating(true)
setTimeout(() => setAnimating(false), 400)
}
onChange?.(newState)
}
return (
<button
onClick={handleClick}
className={`${sizeClasses[size]} rounded-full flex items-center justify-center transition-all duration-300
${isActive ? 'bg-red-50 text-red-500 scale-110' : 'bg-gray-100 text-gray-400 hover:bg-gray-200 hover:text-gray-500'}
${animating ? 'animate-heart-beat' : ''}
${className}`}
aria-label={isActive ? t('components.favorite.remove') : t('components.favorite.add')}
>
<svg className={iconSizes[size]} viewBox="0 0 24 24" fill={isActive ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" />
</svg>
</button>
)
}