1- import { useState } from "react" ;
1+ import { useState , useRef , useEffect } from "react" ;
22import Image from "next/image" ;
33import Link from "next/link" ;
44import { Bell , Plus , Menu , X } from "lucide-react" ;
55import { Button } from "@/components/ui/button" ;
6+ import { UserProfileDropdown } from "./UserProfileDropdown" ;
67
78interface 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 && (
0 commit comments