Skip to content

Commit 8adf04c

Browse files
authored
Merge pull request #128 from Prathik018/feat/profile-dropdown
feat: add profile dropdown component with animated icons
2 parents 094e6a8 + bbf3ad7 commit 8adf04c

2 files changed

Lines changed: 196 additions & 0 deletions

File tree

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
"use client";
2+
3+
import React, { useRef, useState } from "react";
4+
import Link from "next/link";
5+
import UserCheckIcon from "../ui/user-check-icon";
6+
import GearIcon from "../ui/gear-icon";
7+
import FilledBellIcon from "../ui/filled-bell-icon";
8+
import MoonIcon from "../ui/moon-icon";
9+
import LogoutIcon from "../ui/logout-icon";
10+
import type { AnimatedIconHandle, AnimatedIconProps } from "../ui/types";
11+
12+
interface DropdownItemProps {
13+
icon: React.ComponentType<
14+
AnimatedIconProps & React.RefAttributes<AnimatedIconHandle>
15+
>;
16+
label: string;
17+
href?: string;
18+
onClick?: () => void;
19+
isAnimated?: boolean;
20+
rightElement?: React.ReactNode;
21+
}
22+
23+
const DropdownItem = ({
24+
icon: Icon,
25+
label,
26+
href,
27+
onClick,
28+
isAnimated = true,
29+
rightElement,
30+
}: DropdownItemProps) => {
31+
const ref = useRef<AnimatedIconHandle>(null);
32+
33+
const startAnimation = () => {
34+
if (isAnimated) ref.current?.startAnimation();
35+
};
36+
37+
const stopAnimation = () => {
38+
if (isAnimated) ref.current?.stopAnimation();
39+
};
40+
41+
const baseClasses =
42+
"group hover:bg-muted flex items-center rounded-xl px-4 py-3 transition-all cursor-pointer w-full";
43+
44+
const content = (
45+
<>
46+
<div className="flex min-w-0 items-center gap-3">
47+
<Icon
48+
ref={ref}
49+
className="text-muted-foreground group-hover:text-foreground h-5 w-5 shrink-0 transition-colors"
50+
disableHover={!isAnimated}
51+
/>
52+
<span className="text-muted-foreground group-hover:text-foreground truncate text-sm font-medium">
53+
{label}
54+
</span>
55+
</div>
56+
57+
{rightElement && (
58+
<div className="ml-auto flex shrink-0 items-center">{rightElement}</div>
59+
)}
60+
</>
61+
);
62+
63+
if (href) {
64+
return (
65+
<Link
66+
href={href}
67+
className={baseClasses}
68+
onMouseEnter={startAnimation}
69+
onMouseLeave={stopAnimation}
70+
onFocus={startAnimation}
71+
onBlur={stopAnimation}
72+
>
73+
{content}
74+
</Link>
75+
);
76+
}
77+
78+
if (rightElement) {
79+
return (
80+
<div
81+
className="group hover:bg-muted flex w-full items-center rounded-xl px-4 py-3 transition-all"
82+
onMouseEnter={startAnimation}
83+
onMouseLeave={stopAnimation}
84+
onFocus={startAnimation}
85+
onBlur={stopAnimation}
86+
>
87+
{content}
88+
</div>
89+
);
90+
}
91+
92+
return (
93+
<button
94+
type="button"
95+
onClick={onClick}
96+
className={baseClasses}
97+
onMouseEnter={startAnimation}
98+
onMouseLeave={stopAnimation}
99+
onFocus={startAnimation}
100+
onBlur={stopAnimation}
101+
>
102+
{content}
103+
</button>
104+
);
105+
};
106+
107+
interface ProfileDropdownProps {
108+
isAnimated?: boolean;
109+
}
110+
111+
const ProfileDropdown = ({ isAnimated = true }: ProfileDropdownProps) => {
112+
const [darkMode, setDarkMode] = useState(false);
113+
114+
return (
115+
<div className="bg-card border-border/80 w-full max-w-xs min-w-[260px] overflow-hidden rounded-2xl border shadow-xl">
116+
<div className="border-border/80 flex items-center gap-3 border-b p-4">
117+
<div className="h-10 w-10 shrink-0 rounded-full bg-[linear-gradient(135deg,#020024,#090979,#00d4ff)]" />
118+
119+
<div className="flex min-w-0 flex-col">
120+
<span className="text-foreground truncate text-sm font-semibold">
121+
John Doe
122+
</span>
123+
<span className="text-muted-foreground truncate text-xs">
124+
125+
</span>
126+
</div>
127+
</div>
128+
129+
<div className="space-y-1 p-2">
130+
<DropdownItem
131+
icon={UserCheckIcon}
132+
label="Profile"
133+
onClick={() => {}}
134+
isAnimated={isAnimated}
135+
/>
136+
137+
<DropdownItem
138+
icon={GearIcon}
139+
label="Account Settings"
140+
onClick={() => {}}
141+
isAnimated={isAnimated}
142+
/>
143+
144+
<DropdownItem
145+
icon={FilledBellIcon}
146+
label="Notifications"
147+
onClick={() => {}}
148+
isAnimated={isAnimated}
149+
/>
150+
151+
<DropdownItem
152+
icon={MoonIcon}
153+
label="Dark Mode"
154+
isAnimated={isAnimated}
155+
rightElement={
156+
<button
157+
type="button"
158+
aria-label="Toggle dark mode"
159+
aria-pressed={darkMode}
160+
onClick={() => setDarkMode(!darkMode)}
161+
className={`relative flex h-5 w-9 items-center rounded-full transition-colors ${
162+
darkMode ? "bg-primary" : "bg-gray-400"
163+
}`}
164+
>
165+
<span
166+
className={`h-4 w-4 transform rounded-full bg-white transition-transform ${
167+
darkMode ? "translate-x-4" : "translate-x-0.5"
168+
}`}
169+
/>
170+
</button>
171+
}
172+
/>
173+
174+
<DropdownItem
175+
icon={LogoutIcon}
176+
label="Logout"
177+
onClick={() => {}}
178+
isAnimated={isAnimated}
179+
/>
180+
</div>
181+
</div>
182+
);
183+
};
184+
185+
export default ProfileDropdown;

lib/examples.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import path from "path";
66
import TakeuforwardNavbar from "@/components/examples/takeuforward-navbar";
77
import NotionSidebar from "@/components/examples/notion-sidebar";
88
import Dock from "@/components/examples/dock";
9+
import ProfileDropdown from "@/components/examples/profile-dropdown";
910

1011
// Define the registry of examples
1112
// Add new examples to this array
@@ -78,6 +79,16 @@ const EXAMPLE_REGISTRY = [
7879
tags: ["UI", "Animation", "Framer Motion", "Interactive"],
7980
fullWidth: true,
8081
},
82+
{
83+
componentName: "Profile Dropdown",
84+
slug: "profile-dropdown",
85+
createdBy: "https://github.com/prathik018",
86+
filePath: "components/examples/profile-dropdown.tsx",
87+
component: ProfileDropdown,
88+
description:
89+
"A sleek profile dropdown menu with animated icons, user information, and a theme toggle. ",
90+
tags: ["Dropdown", "Profile", "Navigation", "Framer Motion", "UI"],
91+
},
8192
];
8293

8394
export function getExamples() {

0 commit comments

Comments
 (0)