From b4bea10067c8e2d7e369261ef117e4cb421f999b Mon Sep 17 00:00:00 2001 From: jlightner Date: Fri, 3 Apr 2026 04:41:04 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Added=20hover-to-open=20with=20150ms=20?= =?UTF-8?q?leave=20delay=20and=20matchMedia=20desktop=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - "frontend/src/components/AdminDropdown.tsx" GSD-Task: S05/T01 --- frontend/src/components/AdminDropdown.tsx | 48 ++++++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/AdminDropdown.tsx b/frontend/src/components/AdminDropdown.tsx index 081c096..7eedb55 100644 --- a/frontend/src/components/AdminDropdown.tsx +++ b/frontend/src/components/AdminDropdown.tsx @@ -1,9 +1,48 @@ -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { Link } from "react-router-dom"; +const DESKTOP_MQ = "(min-width: 769px)"; +const LEAVE_DELAY_MS = 150; + export default function AdminDropdown() { const [open, setOpen] = useState(false); const dropdownRef = useRef(null); + const isDesktopRef = useRef(false); + const leaveTimerRef = useRef | null>(null); + + // Track desktop breakpoint via matchMedia + useEffect(() => { + const mql = window.matchMedia(DESKTOP_MQ); + isDesktopRef.current = mql.matches; + const onChange = (e: MediaQueryListEvent) => { + isDesktopRef.current = e.matches; + }; + mql.addEventListener("change", onChange); + return () => mql.removeEventListener("change", onChange); + }, []); + + // Clear leave timer on unmount + useEffect(() => { + return () => { + if (leaveTimerRef.current) clearTimeout(leaveTimerRef.current); + }; + }, []); + + const handleMouseEnter = useCallback(() => { + if (leaveTimerRef.current) { + clearTimeout(leaveTimerRef.current); + leaveTimerRef.current = null; + } + if (isDesktopRef.current) setOpen(true); + }, []); + + const handleMouseLeave = useCallback(() => { + if (!isDesktopRef.current) return; + leaveTimerRef.current = setTimeout(() => { + setOpen(false); + leaveTimerRef.current = null; + }, LEAVE_DELAY_MS); + }, []); // Close on outside click useEffect(() => { @@ -31,7 +70,12 @@ export default function AdminDropdown() { }, [open]); return ( -
+