doctor-mail/apps/web/src/components/layout/main/usermenu.tsx

112 lines
4.5 KiB
TypeScript
Raw Normal View History

2025-01-23 23:59:49 +08:00
// ... existing imports ...
import { UserCircleIcon, Cog6ToothIcon, QuestionMarkCircleIcon, ArrowLeftStartOnRectangleIcon } from "@heroicons/react/24/outline";
import { useClickOutside } from "@web/src/hooks/useClickOutside";
import { useAuth } from "@web/src/providers/auth-provider";
import { motion, AnimatePresence } from "framer-motion";
import { useState, useRef } from "react";
import { Avatar } from "../../common/element/Avatar";
export function UserMenu() {
const [showMenu, setShowMenu] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
const { user, logout } = useAuth();
useClickOutside(menuRef, () => setShowMenu(false));
const menuItems = [
{
icon: <UserCircleIcon className="w-5 h-5" />,
label: '个人信息',
action: () => { },
color: 'text-primary-600'
},
{
icon: <Cog6ToothIcon className="w-5 h-5" />,
label: '设置',
action: () => { },
color: 'text-gray-600'
},
{
icon: <QuestionMarkCircleIcon className="w-5 h-5" />,
label: '帮助',
action: () => { },
color: 'text-gray-600'
},
{
icon: <ArrowLeftStartOnRectangleIcon className="w-5 h-5" />,
label: '注销',
action: () => logout(),
color: 'text-red-600'
},
];
return (
<div ref={menuRef} className="relative">
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => setShowMenu(!showMenu)}
className="relative rounded-full focus:outline-none
focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
focus:ring-offset-[#13294B]"
>
<Avatar
src={user?.avatar}
name={user?.showname || user?.username}
size={40}
className="ring-2 ring-white/80 hover:ring-blue-400
transition-all duration-300"
/>
<span className="absolute bottom-0 right-0 h-3 w-3
rounded-full bg-green-500 ring-2 ring-white" />
</motion.button>
<AnimatePresence>
{showMenu && (
<motion.div
initial={{ opacity: 0, scale: 0.95, y: -10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: -10 }}
transition={{
duration: 0.2,
type: "spring",
stiffness: 300,
damping: 30
}}
style={{ zIndex: 100 }}
className="absolute right-0 mt-2 w-56 origin-top-right
bg-white rounded-xl shadow-lg ring-1 ring-black/5
overflow-hidden"
>
<div className="p-4 border-b border-gray-100">
<h4 className="text-sm font-semibold text-gray-900">
{user?.showname}
</h4>
<p className="text-xs text-tertiary-300 mt-1">
{user?.username}
</p>
</div>
<div className="p-2">
{menuItems.map((item, index) => (
<motion.button
key={index}
whileHover={{ x: 4, backgroundColor: '#F3F4F6' }}
onClick={item.action}
className={`flex items-center gap-3 w-full p-2.5
rounded-lg text-sm font-medium
transition-colors duration-200
${item.color} hover:bg-gray-100`}
>
{item.icon}
<span>{item.label}</span>
</motion.button>
))}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}