Files
carrotskin/src/components/profile/CharacterCard.tsx

144 lines
5.2 KiB
TypeScript
Raw Normal View History

'use client';
import { motion } from 'framer-motion';
import { UserIcon, PencilIcon, TrashIcon, CheckIcon } from '@heroicons/react/24/outline';
import SkinViewer from '@/components/SkinViewer';
import type { Profile } from '@/lib/api';
interface CharacterCardProps {
profile: Profile;
skinUrl?: string;
isSlim?: boolean;
isEditing?: boolean;
editName?: string;
onEdit: (uuid: string, currentName: string) => void;
onSave: (uuid: string) => void;
onCancel: () => void;
onDelete: (uuid: string) => void;
onSetActive: (uuid: string) => void;
onSelectSkin: (uuid: string) => void;
onEditNameChange: (name: string) => void;
}
export default function CharacterCard({
profile,
skinUrl,
isSlim,
isEditing,
editName,
onEdit,
onSave,
onCancel,
onDelete,
onSetActive,
onSelectSkin,
onEditNameChange
}: CharacterCardProps) {
return (
<motion.div
key={profile.uuid}
className="bg-white/50 dark:bg-gray-800/50 backdrop-blur-lg rounded-2xl p-6 border border-white/20 dark:border-gray-700/50 shadow-lg"
whileHover={{ scale: 1.02, y: -5 }}
transition={{ duration: 0.2 }}
>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2 flex-1">
{isEditing ? (
<input
type="text"
value={editName}
onChange={(e) => onEditNameChange(e.target.value)}
className="text-lg font-semibold bg-transparent border-b border-orange-500 focus:outline-none text-gray-900 dark:text-white flex-1"
onBlur={() => onSave(profile.uuid)}
onKeyPress={(e) => e.key === 'Enter' && onSave(profile.uuid)}
autoFocus
/>
) : (
<>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white truncate">{profile.name}</h3>
<motion.button
onClick={() => onEdit(profile.uuid, profile.name)}
className="text-gray-500 hover:text-orange-500 transition-colors"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
title="改名"
>
<PencilIcon className="w-4 h-4" />
</motion.button>
</>
)}
</div>
{profile.is_active && (
<span className="px-2 py-1 bg-gradient-to-r from-green-500 to-emerald-500 text-white text-xs rounded-full flex items-center space-x-1">
<CheckIcon className="w-3 h-3" />
<span>使</span>
</span>
)}
</div>
<div className="aspect-square bg-gradient-to-br from-orange-100 to-amber-100 dark:from-gray-700 dark:to-gray-600 rounded-xl mb-4 flex items-center justify-center relative overflow-hidden">
{skinUrl ? (
<SkinViewer
skinUrl={skinUrl}
isSlim={isSlim}
width={180}
height={180}
autoRotate={false}
/>
) : (
<motion.div
className="w-20 h-20 bg-gradient-to-br from-orange-400 to-amber-500 rounded-lg shadow-lg flex items-center justify-center"
whileHover={{ scale: 1.1, rotate: 5 }}
transition={{ type: 'spring', stiffness: 300 }}
>
<UserIcon className="w-10 h-10 text-white" />
</motion.div>
)}
</div>
{/* 操作按钮 */}
<div className="flex gap-2">
{isEditing ? (
<>
<motion.button
onClick={() => onSave(profile.uuid)}
className="flex-1 bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 text-white text-sm py-2 px-3 rounded-lg transition-all duration-200"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
</motion.button>
<motion.button
onClick={onCancel}
className="flex-1 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 text-sm py-2 px-3 rounded-lg transition-all duration-200"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
</motion.button>
</>
) : (
<>
<motion.button
onClick={() => onSelectSkin(profile.uuid)}
className="flex-1 bg-gradient-to-r from-orange-500 to-amber-500 hover:from-orange-600 hover:to-amber-600 text-white text-sm py-2 px-3 rounded-lg transition-all duration-200"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
</motion.button>
<motion.button
onClick={() => onDelete(profile.uuid)}
className="px-3 py-2 border border-red-500 text-red-500 hover:bg-red-500 hover:text-white rounded-lg transition-all duration-200"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
<TrashIcon className="w-4 h-4" />
</motion.button>
</>
)}
</div>
</motion.div>
);
}