forked from CarrotSkin/carrotskin
Initial commit: CarrotSkin project setup
This commit is contained in:
154
src/components/profile/CharacterCard.tsx
Normal file
154
src/components/profile/CharacterCard.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
'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">
|
||||
{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 mr-2"
|
||||
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 flex-1">{profile.name}</h3>
|
||||
)}
|
||||
{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={200}
|
||||
height={200}
|
||||
className="w-full h-full"
|
||||
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>
|
||||
)}
|
||||
|
||||
{/* 皮肤选择按钮 */}
|
||||
<motion.button
|
||||
onClick={() => onSelectSkin(profile.uuid)}
|
||||
className="absolute bottom-2 right-2 bg-gradient-to-r from-orange-500 to-amber-500 text-white p-2 rounded-full shadow-lg"
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
title="选择皮肤"
|
||||
>
|
||||
<PencilIcon className="w-4 h-4" />
|
||||
</motion.button>
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="flex gap-2">
|
||||
{!profile.is_active && (
|
||||
<motion.button
|
||||
onClick={() => onSetActive(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>
|
||||
)}
|
||||
|
||||
{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={() => onEdit(profile.uuid, profile.name)}
|
||||
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 }}
|
||||
>
|
||||
<PencilIcon className="w-4 h-4 inline mr-1" />
|
||||
编辑
|
||||
</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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user