mirror of
https://github.com/TheRealOwenRees/echecsfrance.git
synced 2026-07-23 04:26:57 +00:00
Merge pull request #7 from TheRealOwenRees/css-to-tailwind
Css to tailwind
This commit is contained in:
+8
-1
@@ -1,6 +1,13 @@
|
|||||||
{
|
{
|
||||||
"extends": [
|
"extends": [
|
||||||
|
"next",
|
||||||
"next/core-web-vitals",
|
"next/core-web-vitals",
|
||||||
"plugin:cypress/recommended"
|
"plugin:cypress/recommended"
|
||||||
]
|
],
|
||||||
|
"globals": {
|
||||||
|
"React": "readonly"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-unused-vars": "warn"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,33 @@
|
|||||||
improve hamburger menu - button and menu
|
//TODO readme
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
need a 'return to top' arrow button on smaller screens
|
//TODO hover on table needs fixing - this only seemed to happen once I moved the logic into its own hook <- error is occurring on all hovers at the moment. Client/SSR issue?
|
||||||
|
|
||||||
mobile map needs improving
|
//TODO font size on mobile screen
|
||||||
|
|
||||||
multi-language i18n support - https://nextjs.org/docs/app/building-your-application/routing/internationalization
|
//TODO need a 'return to top' arrow button on smaller screens
|
||||||
|
|
||||||
tests for tournois, testing the loading of map and table, then counting the markers
|
//TODO mobile map needs improving
|
||||||
|
|
||||||
logo for navbar and favicon
|
//TODO multi-language i18n support - https://nextjs.org/docs/app/building-your-application/routing/internationalization
|
||||||
|
|
||||||
hover on table needs fixing - this only seemed to happen once I moved the logic into its own hook <- error is occurring on all hovers at the moment. Client/SSR issue?
|
//TODO tests for tournois, testing the loading of map and table, then counting the markers
|
||||||
|
|
||||||
document my new hook <- consider removing as I believe this is the reason hover is broken
|
//TODO logo for navbar and favicon
|
||||||
|
|
||||||
error handling
|
//TODO document my new hook <- consider removing as I believe this is the reason hover is broken
|
||||||
|
|
||||||
e2e tests for tournament page:
|
//TODO error handling
|
||||||
|
|
||||||
|
//TODO e2e tests for tournament page:
|
||||||
- make sure number of markers = tournamentData.length
|
- make sure number of markers = tournamentData.length
|
||||||
|
|
||||||
about page
|
//TODO about page
|
||||||
|
|
||||||
contact page needs working mailer
|
//TODO contact page needs working mailer
|
||||||
|
|
||||||
SEO
|
//TODO SEO
|
||||||
|
|
||||||
consider offering GraphQL support
|
//TODO consider offering GraphQL support
|
||||||
|
|
||||||
|
//TODO move smaller ui components into a new folder, and make them reusable - such as using generic prop names
|
||||||
|
|||||||
@@ -25,33 +25,3 @@ body {
|
|||||||
)
|
)
|
||||||
rgb(var(--background-start-rgb));
|
rgb(var(--background-start-rgb));
|
||||||
}
|
}
|
||||||
.toggle {
|
|
||||||
height: 2em;
|
|
||||||
width: 3em;
|
|
||||||
border-radius: 50px;
|
|
||||||
margin: auto;
|
|
||||||
background-image: linear-gradient(aqua, skyblue);
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.toggle-dark {
|
|
||||||
height: 2em;
|
|
||||||
width: 3em;
|
|
||||||
border-radius: 50px;
|
|
||||||
margin: auto;
|
|
||||||
background-image: linear-gradient(midnightblue, rebeccapurple);
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.theme-icon{
|
|
||||||
margin-left: 0.2em;
|
|
||||||
height: 2em;
|
|
||||||
width: 1.2em;
|
|
||||||
color: yellow;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-icon-dark{
|
|
||||||
height: 2em;
|
|
||||||
width: 1.2em;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,41 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import HamburgerMenu from "@/components/HamburgerMenu";
|
import HamburgerMenu from "@/components/HamburgerMenu";
|
||||||
import { useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
|
|
||||||
// TODO make hamburger menu slide in from the right instead of conditional rendering
|
|
||||||
const Hamburger = () => {
|
const Hamburger = () => {
|
||||||
const [menuVisible, setMenuVisible] = useState(false);
|
const [menuVisible, setMenuVisible] = useState(false);
|
||||||
|
const hamburgerButtonRef = useRef<HTMLDivElement>(null);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{" "}
|
<div
|
||||||
<div className="space-y-2" onClick={() => setMenuVisible(!menuVisible)}>
|
ref={hamburgerButtonRef}
|
||||||
<div className="w-8 h-0.5 bg-gray-600 dark:bg-white"></div>
|
className="hamburger-button space-y-2 relative z-[99999]"
|
||||||
<div className="w-8 h-0.5 bg-gray-600 dark:bg-white"></div>
|
onClick={() => setMenuVisible(!menuVisible)}
|
||||||
<div className="w-8 h-0.5 bg-gray-600 dark:bg-white"></div>
|
>
|
||||||
|
<div
|
||||||
|
className={`w-8 h-0.5 bg-gray-600 dark:bg-white transition-transform duration-300 ease-in-out ${
|
||||||
|
menuVisible ? "rotate-45 translate-y-2.5 translate-x-[1px]" : ""
|
||||||
|
}`}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={`w-8 h-0.5 bg-gray-600 dark:bg-white transition-transform duration-300 ease-linear ${
|
||||||
|
menuVisible ? "opacity-0 rotate-0 scale-0" : ""
|
||||||
|
}`}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className={`w-8 h-0.5 bg-gray-600 dark:bg-white transition-transform duration-300 ease-in-out ${
|
||||||
|
menuVisible ? "-rotate-45 -translate-y-2.5" : ""
|
||||||
|
}`}
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
{menuVisible && <HamburgerMenu />}
|
{
|
||||||
|
<HamburgerMenu
|
||||||
|
menuVisible={menuVisible}
|
||||||
|
setMenuVisible={setMenuVisible}
|
||||||
|
hamburgerButtonRef={hamburgerButtonRef}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,84 @@
|
|||||||
|
interface HamburgerMenuState {
|
||||||
|
menuVisible: boolean;
|
||||||
|
setMenuVisible: Dispatch<SetStateAction<boolean>>;
|
||||||
|
hamburgerButtonRef: RefObject<HTMLDivElement>;
|
||||||
|
}
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import ThemeSwitcher from "@/components/ThemeSwitcher";
|
import ThemeSwitcher from "@/components/ThemeSwitcher";
|
||||||
|
import { Dispatch, RefObject, SetStateAction, useRef, useState } from "react";
|
||||||
|
import useHamburgerClose from "@/hooks/useHamburgerClose";
|
||||||
|
|
||||||
|
const HamburgerMenu = ({
|
||||||
|
menuVisible,
|
||||||
|
setMenuVisible,
|
||||||
|
hamburgerButtonRef,
|
||||||
|
}: HamburgerMenuState) => {
|
||||||
|
const [mouseOverMenu, setMouseOverMenu] = useState(false);
|
||||||
|
const timeoutRef = useRef<NodeJS.Timeout | undefined>();
|
||||||
|
const menuRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const menuTimeout = () => {
|
||||||
|
timeoutRef.current = setTimeout(() => {
|
||||||
|
setMenuVisible(false);
|
||||||
|
setMouseOverMenu(false);
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseEnterMenu = () => {
|
||||||
|
setMouseOverMenu(true);
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeaveMenu = () => {
|
||||||
|
setMouseOverMenu(false);
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
};
|
||||||
|
|
||||||
|
useHamburgerClose({
|
||||||
|
menuVisible,
|
||||||
|
setMenuVisible,
|
||||||
|
menuRef,
|
||||||
|
hamburgerButtonRef,
|
||||||
|
mouseOverMenu,
|
||||||
|
setMouseOverMenu,
|
||||||
|
timeoutRef,
|
||||||
|
menuTimeout,
|
||||||
|
});
|
||||||
|
|
||||||
const HamburgerMenu = () => {
|
|
||||||
return (
|
return (
|
||||||
<div className="hamburgerMenu absolute top-16 right-0 z-[9999] bg-teal-600 flex md:hidden dark:bg-gray-600">
|
<div
|
||||||
<ul className="list-reset text-white p-5">
|
ref={menuRef}
|
||||||
|
className={`absolute top-0 -right-[173px] ${
|
||||||
|
menuVisible ? "-translate-x-full" : ""
|
||||||
|
} z-[9999] bg-teal-600 flex md:hidden dark:bg-gray-600 transition-transform duration-500 ease-linear`}
|
||||||
|
onMouseEnter={handleMouseEnterMenu}
|
||||||
|
onMouseLeave={handleMouseLeaveMenu}
|
||||||
|
>
|
||||||
|
<ul className="list-reset text-white mt-16 p-5">
|
||||||
<li className="py-5">
|
<li className="py-5">
|
||||||
<Link href="/tournois">Tournois</Link>
|
<Link
|
||||||
|
href="/tournois"
|
||||||
|
className="border-b-2 border-transparent hover:border-white transition-all ease-in-out duration-300"
|
||||||
|
>
|
||||||
|
Tournois
|
||||||
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="py-5">
|
<li className="py-5">
|
||||||
<Link href="/qui-sommes-nous">Qui Sommes-Nous</Link>
|
<Link
|
||||||
|
href="/qui-sommes-nous"
|
||||||
|
className="border-b-2 border-transparent hover:border-white transition-all ease-in-out duration-300"
|
||||||
|
>
|
||||||
|
Qui Sommes-Nous
|
||||||
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="py-5">
|
<li className="py-5">
|
||||||
<Link href="/contactez-nous">Contactez-Nous</Link>
|
<Link
|
||||||
|
href="/contactez-nous"
|
||||||
|
className="border-b-2 border-transparent hover:border-white transition-all ease-in-out duration-300"
|
||||||
|
>
|
||||||
|
Contactez-Nous
|
||||||
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="py-5">
|
<li className="py-5">
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher />
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Hamburger from "@/components/Hamburger";
|
|||||||
|
|
||||||
export default function Navbar() {
|
export default function Navbar() {
|
||||||
return (
|
return (
|
||||||
<nav className="w-full relative border-b-[1px] pt-5 mt-0 px-5 md:pt-2 bg-white dark:bg-gray-800 dark:border-gray-700">
|
<nav className="w-full relative border-b-[1px] pt-5 mt-0 px-5 md:pt-2 bg-white dark:bg-gray-800 dark:border-gray-700 overflow-x-clip">
|
||||||
<div className="container mx-auto flex items-center">
|
<div className="container mx-auto flex items-center">
|
||||||
<div className="pb-3 justify-center flex w-full md:w-1/2 md:pb-0 md:justify-start font-extrabold">
|
<div className="pb-3 justify-center flex w-full md:w-1/2 md:pb-0 md:justify-start font-extrabold">
|
||||||
<Link
|
<Link
|
||||||
@@ -14,11 +14,11 @@ export default function Navbar() {
|
|||||||
<span className="text-2xl">Echecs France</span>
|
<span className="text-2xl">Echecs France</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="pb-2 md:hidden">
|
<div className="mobile-menu pb-2 md:hidden">
|
||||||
<Hamburger />
|
<Hamburger />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="hidden pt-2 justify-center md:flex md:w-1/2 md:justify-end">
|
<div className="desktop-menu hidden pt-2 justify-center md:flex md:w-1/2 md:justify-end">
|
||||||
<ul className="list-reset text-gray-900 dark:text-white no-underline flex flex-1 justify-around md:flex-none items-center">
|
<ul className="list-reset text-gray-900 dark:text-white no-underline flex flex-1 justify-around md:flex-none items-center">
|
||||||
<li className="mr-10">
|
<li className="mr-10">
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
import { MdBrightness2, MdCircle } from "react-icons/md";
|
import { MdBrightness2, MdCircle } from "react-icons/md";
|
||||||
import useDarkMode from "@/hooks/useDarkMode";
|
import useDarkMode from "@/hooks/useDarkMode";
|
||||||
import { useEffect, useState} from 'react'
|
import { useEffect, useState } from "react";
|
||||||
|
import "@/css/theme-toggle.css";
|
||||||
|
|
||||||
// TODO write tests for light/mode
|
|
||||||
// TODO fix TS error on setTheme
|
|
||||||
const ThemeSwitcher = () => {
|
const ThemeSwitcher = () => {
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const [colorTheme, setTheme] = useDarkMode();
|
const [colorTheme, setTheme] = useDarkMode();
|
||||||
@@ -14,7 +13,7 @@ const ThemeSwitcher = () => {
|
|||||||
setMounted(true);
|
setMounted(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if(!mounted) {
|
if (!mounted) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,14 +21,12 @@ const ThemeSwitcher = () => {
|
|||||||
<div>
|
<div>
|
||||||
{colorTheme === "light" ? (
|
{colorTheme === "light" ? (
|
||||||
<div className="toggle-dark" onClick={() => setTheme("light")}>
|
<div className="toggle-dark" onClick={() => setTheme("light")}>
|
||||||
<MdBrightness2 className="theme-icon-dark" />
|
<MdBrightness2 className="theme-icon-dark" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
) : (
|
) : (
|
||||||
<div className="toggle" onClick={() => setTheme("dark")}>
|
<div className="toggle" onClick={() => setTheme("dark")}>
|
||||||
<MdCircle className="theme-icon" />
|
<MdCircle className="theme-icon" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
.toggle {
|
||||||
|
height: 2em;
|
||||||
|
width: 3em;
|
||||||
|
border-radius: 50px;
|
||||||
|
margin: auto;
|
||||||
|
background-image: linear-gradient(aqua, skyblue);
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.toggle-dark {
|
||||||
|
height: 2em;
|
||||||
|
width: 3em;
|
||||||
|
border-radius: 50px;
|
||||||
|
margin: auto;
|
||||||
|
background-image: linear-gradient(midnightblue, rebeccapurple);
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.theme-icon{
|
||||||
|
margin-left: 0.2em;
|
||||||
|
height: 2em;
|
||||||
|
width: 1.2em;
|
||||||
|
color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-icon-dark{
|
||||||
|
height: 2em;
|
||||||
|
width: 1.2em;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
@@ -1,27 +1,30 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import ThemeSwitcher from "../../components/ThemeSwitcher";
|
import ThemeSwitcher from "../../components/ThemeSwitcher";
|
||||||
import Home from "@/app/page";
|
import Home from "@/app/page";
|
||||||
import useDarkMode from '@/hooks/useDarkMode';
|
import "@/css/theme-toggle.css";
|
||||||
import '../../app/globals.css';
|
|
||||||
|
|
||||||
describe('ThemeSwitcher component', () => {
|
describe("ThemeSwitcher component", () => {
|
||||||
it('should toggle between light and dark mode', () => {
|
it("should toggle between light and dark mode", () => {
|
||||||
cy.mount(<Home />);
|
cy.mount(<Home />);
|
||||||
cy.mount(<ThemeSwitcher />);
|
cy.mount(<ThemeSwitcher />);
|
||||||
cy.wait(100);
|
cy.wait(100);
|
||||||
|
|
||||||
// checking that the toggle is there and light mode is active
|
// checking that the toggle is there and light mode is active
|
||||||
cy.get('.toggle').should('exist');
|
cy.get(".toggle").should("exist");
|
||||||
cy.get('.toggle').should(($toggle) => {
|
cy.get(".toggle").should(($toggle) => {
|
||||||
const backgroundImage = $toggle.css('background-image');
|
const backgroundImage = $toggle.css("background-image");
|
||||||
expect(backgroundImage).to.include('linear-gradient(rgb(0, 255, 255), rgb(135, 206, 235)');
|
expect(backgroundImage).to.include(
|
||||||
|
"linear-gradient(rgb(0, 255, 255), rgb(135, 206, 235)"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
// checking that the toggle is clickable and dark mode is active
|
// checking that the toggle is clickable and dark mode is active
|
||||||
cy.get('.toggle').click();
|
cy.get(".toggle").click();
|
||||||
cy.get('.toggle-dark').should('exist');
|
cy.get(".toggle-dark").should("exist");
|
||||||
cy.get('.toggle-dark').should(($toggle) => {
|
cy.get(".toggle-dark").should(($toggle) => {
|
||||||
const backgroundImage = $toggle.css('background-image');
|
const backgroundImage = $toggle.css("background-image");
|
||||||
expect(backgroundImage).to.include('linear-gradient(rgb(25, 25, 112), rgb(102, 51, 153)');
|
expect(backgroundImage).to.include(
|
||||||
|
"linear-gradient(rgb(25, 25, 112), rgb(102, 51, 153)"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -6,8 +6,10 @@ describe("Test all links", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
it("Check navbar links point to correct pathname as a slug", () => {
|
it("Check navbar links point to correct pathname as a slug", () => {
|
||||||
|
cy.viewport(600, 600);
|
||||||
cy.visit("/");
|
cy.visit("/");
|
||||||
pages.forEach((page) => {
|
pages.forEach((page) => {
|
||||||
|
cy.get(".hamburger-button").click();
|
||||||
cy.contains(page, { matchCase: false }).click();
|
cy.contains(page, { matchCase: false }).click();
|
||||||
cy.location("pathname").should("eq", `/${navLinkToSlug(page)}`); // url path matches link name, replacing whitespace with hyphens
|
cy.location("pathname").should("eq", `/${navLinkToSlug(page)}`); // url path matches link name, replacing whitespace with hyphens
|
||||||
cy.go("back");
|
cy.go("back");
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
describe("Mobile Navbar", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.viewport(600, 600);
|
||||||
|
cy.visit("/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("hamburger menu on mobile screens", () => {
|
||||||
|
cy.get(".mobile-menu").should("be.not.hidden");
|
||||||
|
cy.get(".desktop-menu").should("be.hidden");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("hamburger button available", () => {
|
||||||
|
cy.get(".hamburger-button").should("be.visible");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Desktop Navbar", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.viewport("macbook-15");
|
||||||
|
cy.visit("/");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("desktop menu on larger screens", () => {
|
||||||
|
cy.get(".mobile-menu").should("be.hidden");
|
||||||
|
cy.get(".desktop-menu").should("be.not.hidden");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("hamburger button hidden", () => {
|
||||||
|
cy.get(".hamburger-button").should("be.not.visible");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Using fixtures to represent data",
|
|
||||||
"email": "hello@cypress.io",
|
|
||||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"_id": "1",
|
|
||||||
"town": "Testville",
|
|
||||||
"department": "55",
|
|
||||||
"tournament": "Test Tournament 1",
|
|
||||||
"url": "https://www.google.com",
|
|
||||||
"time_control": "Cadence Lente",
|
|
||||||
"date": "01/01/29",
|
|
||||||
"coordinates": [55, 1]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "2",
|
|
||||||
"town": "Testville 2",
|
|
||||||
"department": "55",
|
|
||||||
"tournament": "Test Tournament 2",
|
|
||||||
"url": "https://www.google.com",
|
|
||||||
"time_control": "Rapide",
|
|
||||||
"date": "01/01/29",
|
|
||||||
"coordinates": [55, 0.5]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_id": "3",
|
|
||||||
"town": "Testville 3",
|
|
||||||
"department": "55",
|
|
||||||
"tournament": "Test Tournament 3",
|
|
||||||
"url": "https://www.google.com",
|
|
||||||
"time_control": "BLitz",
|
|
||||||
"date": "01/01/28",
|
|
||||||
"coordinates": [55.5, 0.5]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,55 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
import { Dispatch, RefObject, SetStateAction, useEffect } from "react";
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener("mousedown", handleMouseDownOutsideMenu);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousedown", handleMouseDownOutsideMenu);
|
||||||
|
};
|
||||||
|
}, [menuVisible]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (menuVisible && !mouseOverMenu) {
|
||||||
|
menuTimeout();
|
||||||
|
} else {
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [mouseOverMenu, menuVisible]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useHamburgerClose;
|
||||||
@@ -2,6 +2,10 @@
|
|||||||
"name": "client",
|
"name": "client",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.16.0",
|
||||||
|
"npm": "please-use-npm"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
|
|||||||
Reference in New Issue
Block a user