setTheme("light")}
- >
-
+
setTheme("light")}>
+
) : (
-
setTheme("dark")}
- >
-
+
setTheme("dark")}>
+
)}
diff --git a/css/theme-toggle.css b/css/theme-toggle.css
new file mode 100644
index 0000000..44cf05c
--- /dev/null
+++ b/css/theme-toggle.css
@@ -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;
+}
diff --git a/cypress/component/themeToggle.cy.tsx b/cypress/component/themeToggle.cy.tsx
index c583362..410c86a 100644
--- a/cypress/component/themeToggle.cy.tsx
+++ b/cypress/component/themeToggle.cy.tsx
@@ -1,27 +1,30 @@
-import React from 'react';
+import React from "react";
import ThemeSwitcher from "../../components/ThemeSwitcher";
import Home from "@/app/page";
-import useDarkMode from '@/hooks/useDarkMode';
-import '../../app/globals.css';
+import "@/css/theme-toggle.css";
-describe('ThemeSwitcher component', () => {
- it('should toggle between light and dark mode', () => {
+describe("ThemeSwitcher component", () => {
+ it("should toggle between light and dark mode", () => {
cy.mount(
);
cy.mount(
);
cy.wait(100);
// checking that the toggle is there and light mode is active
- cy.get('.toggle').should('exist');
- cy.get('.toggle').should(($toggle) => {
- const backgroundImage = $toggle.css('background-image');
- expect(backgroundImage).to.include('linear-gradient(rgb(0, 255, 255), rgb(135, 206, 235)');
+ cy.get(".toggle").should("exist");
+ cy.get(".toggle").should(($toggle) => {
+ const backgroundImage = $toggle.css("background-image");
+ 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
- cy.get('.toggle').click();
- cy.get('.toggle-dark').should('exist');
- cy.get('.toggle-dark').should(($toggle) => {
- const backgroundImage = $toggle.css('background-image');
- expect(backgroundImage).to.include('linear-gradient(rgb(25, 25, 112), rgb(102, 51, 153)');
+ cy.get(".toggle").click();
+ cy.get(".toggle-dark").should("exist");
+ cy.get(".toggle-dark").should(($toggle) => {
+ const backgroundImage = $toggle.css("background-image");
+ expect(backgroundImage).to.include(
+ "linear-gradient(rgb(25, 25, 112), rgb(102, 51, 153)"
+ );
});
});
-});
\ No newline at end of file
+});
diff --git a/cypress/e2e/links.cy.ts b/cypress/e2e/links.cy.ts
index 47383c6..8410ec1 100644
--- a/cypress/e2e/links.cy.ts
+++ b/cypress/e2e/links.cy.ts
@@ -6,8 +6,10 @@ describe("Test all links", () => {
};
it("Check navbar links point to correct pathname as a slug", () => {
+ cy.viewport(600, 600);
cy.visit("/");
pages.forEach((page) => {
+ cy.get(".hamburger-button").click();
cy.contains(page, { matchCase: false }).click();
cy.location("pathname").should("eq", `/${navLinkToSlug(page)}`); // url path matches link name, replacing whitespace with hyphens
cy.go("back");
diff --git a/cypress/e2e/navbar.cy.tsx b/cypress/e2e/navbar.cy.tsx
new file mode 100644
index 0000000..0335489
--- /dev/null
+++ b/cypress/e2e/navbar.cy.tsx
@@ -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");
+ });
+});
diff --git a/cypress/videos/links.cy.ts.mp4 b/cypress/videos/links.cy.ts.mp4
index dfef946..65aab18 100644
Binary files a/cypress/videos/links.cy.ts.mp4 and b/cypress/videos/links.cy.ts.mp4 differ
diff --git a/cypress/videos/navbar.cy.tsx.mp4 b/cypress/videos/navbar.cy.tsx.mp4
new file mode 100644
index 0000000..74304c9
Binary files /dev/null and b/cypress/videos/navbar.cy.tsx.mp4 differ
diff --git a/hooks/useHamburgerClose.ts b/hooks/useHamburgerClose.ts
new file mode 100644
index 0000000..caed63f
--- /dev/null
+++ b/hooks/useHamburgerClose.ts
@@ -0,0 +1,55 @@
+interface HamburgerClose {
+ menuVisible: boolean;
+ setMenuVisible: Dispatch
>;
+ menuRef: RefObject;
+ hamburgerButtonRef: RefObject;
+ mouseOverMenu: boolean;
+ setMouseOverMenu: Dispatch>;
+ timeoutRef: RefObject;
+ 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;