feat: Added hover-to-open with 150ms leave delay and matchMedia desktop…
- "frontend/src/components/AdminDropdown.tsx" GSD-Task: S05/T01
This commit is contained in:
parent
82998c6d8d
commit
b4bea10067
1 changed files with 46 additions and 2 deletions
|
|
@ -1,9 +1,48 @@
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
const DESKTOP_MQ = "(min-width: 769px)";
|
||||||
|
const LEAVE_DELAY_MS = 150;
|
||||||
|
|
||||||
export default function AdminDropdown() {
|
export default function AdminDropdown() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
const isDesktopRef = useRef(false);
|
||||||
|
const leaveTimerRef = useRef<ReturnType<typeof setTimeout> | 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
|
// Close on outside click
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -31,7 +70,12 @@ export default function AdminDropdown() {
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="admin-dropdown" ref={dropdownRef}>
|
<div
|
||||||
|
className="admin-dropdown"
|
||||||
|
ref={dropdownRef}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
className="admin-dropdown__trigger"
|
className="admin-dropdown__trigger"
|
||||||
onClick={() => setOpen((prev) => !prev)}
|
onClick={() => setOpen((prev) => !prev)}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue