/Components/Application
Profile Dropdown
Animated profile dropdown component
Preview
Installation
"use client";
import * as React from "react";
import { useEffect, useRef, useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import {
User,
CreditCard,
Settings,
Headphones,
MessageCircle,
Repeat,
LogOut,
} from "lucide-react";
const ProfileDropdown = () => {
const [isOpen, setIsOpen] = useState(false);
const menuRef = useRef(null);
useEffect(() => {
function handleClickOutside(event: MouseEvent): void {
if (
menuRef.current &&
!(menuRef.current as HTMLElement).contains(event.target as Node)
) {
setIsOpen(false);
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [menuRef]);
return (
<div className="relative text-left flex items-start justify-end h-full w-full">
<button
onClick={() => setIsOpen(!isOpen)}
className="focus:outline-none rounded-full w-10 h-10 flex items-center justify-center shadow-md"
>
<img src="/logo.png" alt="Profile" className="rounded-full w-10 h-10" />
</button>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
transition={{
type: "spring",
stiffness: 300,
damping: 20,
}}
className="absolute top-12 mt-3 w-64 sm:w-72 bg-white dark:bg-secondary text-secondary-foreground rounded-2xl shadow-xl p-4 space-y-4 z-50 right-0"
ref={menuRef}
>
<div className="flex items-center gap-3 border-b border-secondary-foreground/10 pb-4">
<div className="relative shadow-xl rounded-full">
<img
src="/logo.png"
alt="User"
className="w-10 h-10 rounded-full"
/>
{/* Online badge */}
<span className="absolute bottom-0 right-0 block w-3 h-3 bg-green-500 border-2 border-white dark:border-gray-900 rounded-full"></span>
</div>
<div className="flex flex-col">
<h4 className="font-semibold text-sm sm:text-base">HextaUI</h4>
<p className="text-xs sm:text-sm">hi@hextastudio.com</p>
</div>
</div>
<div className="flex flex-col my-2">
<MenuItem icon={<User size={18} />} text="View profile" />
<MenuItem icon={<CreditCard size={18} />} text="Subscriptions" />
<MenuItem icon={<Settings size={18} />} text="Settings" />
</div>
<div className="flex flex-col my-2 pt-2 border-t border-secondary-foreground/10">
<MenuItem icon={<Headphones size={18} />} text="Support" />
<MenuItem icon={<MessageCircle size={18} />} text="Community" />
</div>
<div className="flex flex-col mt-2 pt-2 border-t border-secondary-foreground/10">
<MenuItem icon={<Repeat size={18} />} text="Switch account" />
<MenuItem icon={<LogOut size={18} />} text="Sign out" />
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
};
interface MenuItemProps {
icon: React.ReactNode;
text: string;
}
function MenuItem({ icon, text }: MenuItemProps) {
return (
<div className="flex items-center gap-2 hover:bg-primary/10 transition p-2 rounded-xl cursor-pointer opacity-80">
<div>{icon}</div>
<span className="text-xs sm:text-sm font-medium">{text}</span>
</div>
);
}
export { ProfileDropdown };
Usage
const ProfileDropdownExample = () => {
return (
<div className="h-1/2 flex items-center justify-start text-center flex-col w-full">
<nav className="rounded-full shadow-lg/5 bg-white dark:bg-secondary text-secondary-foreground max-w-2xl w-full">
<div className="container mx-auto px-4 py-3 flex justify-between items-center">
<div className="text-lg font-bold text-primary pl-4">HextaUI</div>
<div className="flex items-center gap-4 max-md:hidden">
<a href="#" className="text-sm hover:text-primary">
Home
</a>
<a href="#" className="text-sm hover:text-primary">
About
</a>
<a href="#" className="text-sm hover:text-primary">
Contact
</a>
<ProfileDropdown />
</div>
</div>
</nav>
</div>
);
};
export { ProfileDropdownExample };
Edit on GitHub
Last updated on