mirror of
https://github.com/TheRealOwenRees/echecsfrance.git
synced 2026-07-23 04:26:57 +00:00
Simplify burger handling
This commit is contained in:
@@ -1,48 +1,44 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useRef, useState } from "react";
|
import { useAtom } from "jotai";
|
||||||
|
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { burgerMenuIsOpenAtom } from "@/app/atoms";
|
||||||
|
|
||||||
import HamburgerMenu from "./HamburgerMenu";
|
import HamburgerMenu from "./HamburgerMenu";
|
||||||
|
|
||||||
const Hamburger = () => {
|
const Hamburger = () => {
|
||||||
const [menuVisible, setMenuVisible] = useState(false);
|
const [burgerMenuIsOpen, setBurgerMenuIsOpen] = useAtom(burgerMenuIsOpenAtom);
|
||||||
const hamburgerButtonRef = useRef<HTMLDivElement>(null);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
ref={hamburgerButtonRef}
|
|
||||||
className="hamburger-button relative z-[99999] space-y-2"
|
className="hamburger-button relative z-[99999] space-y-2"
|
||||||
data-test="hamburger-button"
|
data-test="hamburger-button"
|
||||||
onClick={() => setMenuVisible(!menuVisible)}
|
onClick={() => setBurgerMenuIsOpen(!burgerMenuIsOpen)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"h-0.5 w-8 bg-gray-600 transition-all duration-300 ease-in-out dark:bg-white",
|
"h-0.5 w-8 bg-gray-600 transition-all duration-300 ease-in-out dark:bg-white",
|
||||||
menuVisible &&
|
burgerMenuIsOpen &&
|
||||||
"translate-x-[1px] translate-y-2.5 rotate-45 bg-white",
|
"translate-x-[1px] translate-y-2.5 rotate-45 bg-white",
|
||||||
)}
|
)}
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"h-0.5 w-8 bg-gray-600 transition-transform duration-300 ease-linear dark:bg-white",
|
"h-0.5 w-8 bg-gray-600 transition-transform duration-300 ease-linear dark:bg-white",
|
||||||
menuVisible && "scale-0 bg-white opacity-0",
|
burgerMenuIsOpen && "scale-0 bg-white opacity-0",
|
||||||
)}
|
)}
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"h-0.5 w-8 bg-gray-600 transition-transform duration-300 ease-in-out dark:bg-white",
|
"h-0.5 w-8 bg-gray-600 transition-transform duration-300 ease-in-out dark:bg-white",
|
||||||
menuVisible && "-translate-y-2.5 -rotate-45 bg-white",
|
burgerMenuIsOpen && "-translate-y-2.5 -rotate-45 bg-white",
|
||||||
)}
|
)}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<HamburgerMenu
|
<HamburgerMenu />
|
||||||
menuVisible={menuVisible}
|
|
||||||
setMenuVisible={setMenuVisible}
|
|
||||||
hamburgerButtonRef={hamburgerButtonRef}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,69 +1,30 @@
|
|||||||
import { Dispatch, RefObject, SetStateAction, useRef, useState } from "react";
|
import { Dispatch, RefObject, SetStateAction, useRef, useState } from "react";
|
||||||
|
|
||||||
|
import { useAtom } from "jotai";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import useHamburgerClose from "@/hooks/useHamburgerClose";
|
import { burgerMenuIsOpenAtom } from "@/app/atoms";
|
||||||
import { Link } from "@/utils/navigation";
|
import { Link } from "@/utils/navigation";
|
||||||
|
|
||||||
interface HamburgerMenuState {
|
const HamburgerMenu = () => {
|
||||||
menuVisible: boolean;
|
|
||||||
setMenuVisible: Dispatch<SetStateAction<boolean>>;
|
|
||||||
hamburgerButtonRef: RefObject<HTMLDivElement>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const HamburgerMenu = ({
|
|
||||||
menuVisible,
|
|
||||||
setMenuVisible,
|
|
||||||
hamburgerButtonRef,
|
|
||||||
}: HamburgerMenuState) => {
|
|
||||||
const t = useTranslations("Nav");
|
const t = useTranslations("Nav");
|
||||||
const [mouseOverMenu, setMouseOverMenu] = useState(false);
|
const [burgerMenuIsOpen, setBurgerMenuIsOpen] = useAtom(burgerMenuIsOpenAtom);
|
||||||
const timeoutRef = useRef<NodeJS.Timeout | undefined>();
|
|
||||||
const menuRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const menuTimeout = () => {
|
const closeMenu = () => setBurgerMenuIsOpen(false);
|
||||||
timeoutRef.current = setTimeout(() => {
|
|
||||||
setMenuVisible(false);
|
|
||||||
setMouseOverMenu(false);
|
|
||||||
}, 4000);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseEnterMenu = () => {
|
|
||||||
setMouseOverMenu(true);
|
|
||||||
clearTimeout(timeoutRef.current);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseLeaveMenu = () => {
|
|
||||||
setMouseOverMenu(false);
|
|
||||||
clearTimeout(timeoutRef.current);
|
|
||||||
};
|
|
||||||
|
|
||||||
useHamburgerClose({
|
|
||||||
menuVisible,
|
|
||||||
setMenuVisible,
|
|
||||||
menuRef,
|
|
||||||
hamburgerButtonRef,
|
|
||||||
mouseOverMenu,
|
|
||||||
setMouseOverMenu,
|
|
||||||
timeoutRef,
|
|
||||||
menuTimeout,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={menuRef}
|
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"fixed right-0 top-0 z-[9999] h-[calc(100svh-5rem)] w-full",
|
"fixed right-0 top-0 z-[9999] h-[calc(100svh-3rem)] w-full",
|
||||||
"flex items-center justify-center bg-primary-600 transition-transform duration-200 ease-linear dark:bg-gray-600 md:hidden",
|
"flex items-center justify-center bg-primary-600 transition-transform duration-200 ease-linear dark:bg-gray-600 md:hidden",
|
||||||
!menuVisible && "translate-x-full",
|
!burgerMenuIsOpen && "translate-x-full",
|
||||||
)}
|
)}
|
||||||
onMouseEnter={handleMouseEnterMenu}
|
|
||||||
onMouseLeave={handleMouseLeaveMenu}
|
|
||||||
>
|
>
|
||||||
<ul className="list-reset text-white">
|
<ul className="list-reset text-white">
|
||||||
<li className="py-5 text-center text-xl">
|
<li className="py-5 text-center text-xl">
|
||||||
<Link
|
<Link
|
||||||
|
onClick={closeMenu}
|
||||||
href="/tournaments"
|
href="/tournaments"
|
||||||
className="border-b-2 border-transparent transition-all duration-300 ease-in-out hover:border-white"
|
className="border-b-2 border-transparent transition-all duration-300 ease-in-out hover:border-white"
|
||||||
>
|
>
|
||||||
@@ -73,6 +34,7 @@ const HamburgerMenu = ({
|
|||||||
|
|
||||||
<li className="py-5 text-center text-xl">
|
<li className="py-5 text-center text-xl">
|
||||||
<Link
|
<Link
|
||||||
|
onClick={closeMenu}
|
||||||
href="/clubs"
|
href="/clubs"
|
||||||
className="border-b-2 border-transparent transition-all duration-300 ease-in-out hover:border-white"
|
className="border-b-2 border-transparent transition-all duration-300 ease-in-out hover:border-white"
|
||||||
>
|
>
|
||||||
@@ -82,6 +44,7 @@ const HamburgerMenu = ({
|
|||||||
|
|
||||||
<li className="py-5 text-center text-xl">
|
<li className="py-5 text-center text-xl">
|
||||||
<Link
|
<Link
|
||||||
|
onClick={closeMenu}
|
||||||
href="/elo"
|
href="/elo"
|
||||||
className="border-b-2 border-transparent transition-all duration-300 ease-in-out hover:border-white"
|
className="border-b-2 border-transparent transition-all duration-300 ease-in-out hover:border-white"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Club, TimeControl, Tournament } from "@/types";
|
|||||||
import atomWithDebounce from "@/utils/atomWithDebounce";
|
import atomWithDebounce from "@/utils/atomWithDebounce";
|
||||||
import { normalizedContains } from "@/utils/string";
|
import { normalizedContains } from "@/utils/string";
|
||||||
|
|
||||||
|
export const burgerMenuIsOpenAtom = atom(false);
|
||||||
export const mapBoundsAtom = atom<LatLngBounds | null>(null);
|
export const mapBoundsAtom = atom<LatLngBounds | null>(null);
|
||||||
export const syncVisibleAtom = atom(true);
|
export const syncVisibleAtom = atom(true);
|
||||||
export const searchStringAtom = atom("");
|
export const searchStringAtom = atom("");
|
||||||
|
|||||||
+1
-1
@@ -220,7 +220,7 @@ export const Map = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="tournament-map" className="flex h-content flex-col">
|
<section id="tournament-map" className="flex h-content flex-col">
|
||||||
<div className="p-3 lg:hidden">{filters}</div>
|
{filters && <div className="p-3 lg:hidden">{filters}</div>}
|
||||||
|
|
||||||
<MapContainer
|
<MapContainer
|
||||||
center={center}
|
center={center}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { useEffect, useRef, useState } from "react";
|
|||||||
|
|
||||||
import { FaArrowUp } from "react-icons/fa";
|
import { FaArrowUp } from "react-icons/fa";
|
||||||
|
|
||||||
import { handleScrollToTop } from "@/handlers/scrollHandlers";
|
|
||||||
import { useBreakpoint } from "@/hooks/tailwind";
|
import { useBreakpoint } from "@/hooks/tailwind";
|
||||||
import { ScrollableElement } from "@/types";
|
import { ScrollableElement } from "@/types";
|
||||||
|
import { handleScrollToTop } from "@/utils/scrollHandlers";
|
||||||
|
|
||||||
const ScrollToTopButton = () => {
|
const ScrollToTopButton = () => {
|
||||||
const scrollToTopElementRef = useRef<ScrollableElement | null>(null);
|
const scrollToTopElementRef = useRef<ScrollableElement | null>(null);
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
import { Dispatch, RefObject, SetStateAction, useEffect } from "react";
|
|
||||||
|
|
||||||
interface HamburgerClose {
|
|
||||||
menuVisible: boolean;
|
|
||||||
setMenuVisible: Dispatch<SetStateAction<boolean>>;
|
|
||||||
menuRef: RefObject<HTMLDivElement>;
|
|
||||||
hamburgerButtonRef: RefObject<HTMLDivElement>;
|
|
||||||
mouseOverMenu: boolean;
|
|
||||||
setMouseOverMenu: Dispatch<SetStateAction<boolean>>;
|
|
||||||
timeoutRef: RefObject<NodeJS.Timeout | undefined>;
|
|
||||||
menuTimeout: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useHamburgerClose = ({
|
|
||||||
menuVisible,
|
|
||||||
setMenuVisible,
|
|
||||||
menuRef,
|
|
||||||
hamburgerButtonRef,
|
|
||||||
mouseOverMenu,
|
|
||||||
setMouseOverMenu,
|
|
||||||
timeoutRef,
|
|
||||||
menuTimeout,
|
|
||||||
}: HamburgerClose) => {
|
|
||||||
useEffect(() => {
|
|
||||||
const handleMouseDownOutsideMenu = (event: MouseEvent) => {
|
|
||||||
if (
|
|
||||||
menuVisible &&
|
|
||||||
menuRef.current &&
|
|
||||||
!menuRef.current.contains(event.target as Node) &&
|
|
||||||
hamburgerButtonRef.current &&
|
|
||||||
!hamburgerButtonRef.current.contains(event.target as Node)
|
|
||||||
) {
|
|
||||||
setMenuVisible(false);
|
|
||||||
setMouseOverMenu(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLinkClick = () => {
|
|
||||||
setMenuVisible(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener("mousedown", handleMouseDownOutsideMenu);
|
|
||||||
const links = document.querySelectorAll("a");
|
|
||||||
links.forEach((link) => {
|
|
||||||
link.addEventListener("click", handleLinkClick);
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("mousedown", handleMouseDownOutsideMenu);
|
|
||||||
links.forEach((link) => {
|
|
||||||
link.removeEventListener("click", handleLinkClick);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}, [menuVisible]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (menuVisible && !mouseOverMenu) {
|
|
||||||
menuTimeout();
|
|
||||||
} else {
|
|
||||||
if (timeoutRef.current) {
|
|
||||||
clearTimeout(timeoutRef.current);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [mouseOverMenu, menuVisible]);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useHamburgerClose;
|
|
||||||
Reference in New Issue
Block a user