Skip to content

Commit 9f67ce7

Browse files
committed
draft dropdown
1 parent 2cc99bf commit 9f67ce7

2 files changed

Lines changed: 131 additions & 17 deletions

File tree

frontend/components/navbar/NavbarActions.tsx

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { useState } from "react";
1+
import { useState, useRef, useEffect } from "react";
22
import Image from "next/image";
33
import Link from "next/link";
44
import { Bell, Plus, Menu, X } from "lucide-react";
55
import { Button } from "@/components/ui/button";
6+
import { UserProfileDropdown } from "./UserProfileDropdown";
67

78
interface Props {
89
createNewText: string;
@@ -20,16 +21,44 @@ export const NavbarActions = ({
2021
onToggleMobileMenu,
2122
}: Props) => {
2223
const [hasUnreadNotifications, setHasUnreadNotifications] = useState(false);
24+
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
25+
const containerRef = useRef<HTMLDivElement>(null);
2326

2427
const handleNotificationClick = () => {
2528
// TODO
2629
setHasUnreadNotifications(false);
2730
};
2831

2932
const handleAvatarClick = () => {
30-
// TODO
33+
setIsDropdownOpen(!isDropdownOpen);
3134
};
3235

36+
useEffect(() => {
37+
const handleClickOutside = (event: MouseEvent) => {
38+
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
39+
setIsDropdownOpen(false);
40+
}
41+
};
42+
43+
if (isDropdownOpen) {
44+
document.addEventListener("mousedown", handleClickOutside);
45+
return () => document.removeEventListener("mousedown", handleClickOutside);
46+
}
47+
}, [isDropdownOpen]);
48+
49+
useEffect(() => {
50+
const handleEscape = (event: KeyboardEvent) => {
51+
if (event.key === "Escape") {
52+
setIsDropdownOpen(false);
53+
}
54+
};
55+
56+
if (isDropdownOpen) {
57+
document.addEventListener("keydown", handleEscape);
58+
return () => document.removeEventListener("keydown", handleEscape);
59+
}
60+
}, [isDropdownOpen]);
61+
3362
return (
3463
<div className="flex items-center gap-2 sm:gap-3">
3564
{/* desktop only new listing button */}
@@ -78,21 +107,26 @@ export const NavbarActions = ({
78107
</Button>
79108

80109
{/* user avatar */}
81-
<Button
82-
variant="ghost"
83-
size="icon"
84-
className="overflow-hidden rounded-full p-0 transition-opacity hover:opacity-80"
85-
onClick={handleAvatarClick}
86-
aria-label="User menu"
87-
>
88-
<Image
89-
src="/images/default-avatar.png"
90-
alt="User avatar"
91-
width={40}
92-
height={40}
93-
className="rounded-full object-cover"
94-
/>
95-
</Button>
110+
<div className="relative" ref={containerRef}>
111+
<Button
112+
variant="ghost"
113+
size="icon"
114+
className="overflow-hidden rounded-full p-0 transition-opacity hover:opacity-80"
115+
onClick={handleAvatarClick}
116+
aria-label="User menu"
117+
aria-expanded={isDropdownOpen}
118+
>
119+
<Image
120+
src="/images/default-avatar.png"
121+
alt="User avatar"
122+
width={40}
123+
height={40}
124+
className="rounded-full object-cover"
125+
/>
126+
</Button>
127+
128+
<UserProfileDropdown isOpen={isDropdownOpen} onClose={() => setIsDropdownOpen(false)} />
129+
</div>
96130

97131
{/* mobile only menu toggle */}
98132
{mobileShowHamburger && (
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"use client";
2+
3+
import Link from "next/link";
4+
import { UserCircle, ClipboardList, Bookmark } from "lucide-react";
5+
import { cn } from "@/lib/utils";
6+
7+
interface UserProfileDropdownProps {
8+
isOpen: boolean;
9+
onClose: () => void;
10+
}
11+
12+
// TODO update hrefs once user pages are created
13+
export function UserProfileDropdown({ isOpen, onClose }: UserProfileDropdownProps) {
14+
if (!isOpen) return null;
15+
16+
return (
17+
<div
18+
className={cn(
19+
"absolute top-full right-0 z-50 mt-1 min-w-[200px]",
20+
"bg-popover rounded-md border shadow-md",
21+
"animate-in fade-in-0 zoom-in-95"
22+
)}
23+
role="menu"
24+
>
25+
<Link
26+
href="/"
27+
onClick={onClose}
28+
className={cn(
29+
"flex items-center gap-3 px-3 py-3 text-sm",
30+
"hover:bg-accent hover:text-accent-foreground transition-colors"
31+
)}
32+
role="menuitem"
33+
>
34+
<UserCircle className="h-5 w-5" />
35+
<span>My Profile</span>
36+
</Link>
37+
38+
<div className="text-foreground mt-1 px-3 py-1 text-xs font-bold">Selling</div>
39+
<Link
40+
href="/"
41+
onClick={onClose}
42+
className={cn(
43+
"flex items-center gap-3 px-3 py-3 text-sm",
44+
"hover:bg-accent hover:text-accent-foreground transition-colors"
45+
)}
46+
role="menuitem"
47+
>
48+
<ClipboardList className="h-5 w-5" />
49+
<span>My Listings</span>
50+
</Link>
51+
52+
<div className="text-foreground mt-1 px-3 py-1 text-xs font-bold">Buying</div>
53+
<Link
54+
href="/"
55+
onClick={onClose}
56+
className={cn(
57+
"flex items-center gap-3 px-3 py-3 text-sm",
58+
"hover:bg-accent hover:text-accent-foreground transition-colors"
59+
)}
60+
role="menuitem"
61+
>
62+
<Bookmark className="h-5 w-5" />
63+
<span>Saved Listings</span>
64+
</Link>
65+
66+
<Link
67+
href="/"
68+
onClick={onClose}
69+
className={cn(
70+
"flex items-center gap-3 px-3 py-3 text-sm",
71+
"hover:bg-accent hover:text-accent-foreground transition-colors"
72+
)}
73+
role="menuitem"
74+
>
75+
<ClipboardList className="h-5 w-5" />
76+
<span>My Purchases</span>
77+
</Link>
78+
</div>
79+
);
80+
}

0 commit comments

Comments
 (0)