Merge pull request #7 from TheRealOwenRees/css-to-tailwind

Css to tailwind
This commit is contained in:
Owen Rees
2023-06-16 15:20:19 +02:00
committed by GitHub
17 changed files with 276 additions and 122 deletions
+8 -1
View File
@@ -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"
}
} }
+19 -14
View File
@@ -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
-30
View File
@@ -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;
}
+29 -8
View File
@@ -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}
/>
}
</> </>
); );
}; };
+72 -6
View File
@@ -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 />
+3 -3
View File
@@ -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
+3 -6
View File
@@ -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;
} }
@@ -24,12 +23,10 @@ const ThemeSwitcher = () => {
<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>
); );
+30
View File
@@ -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;
}
+17 -14
View File
@@ -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)"
);
}); });
}); });
}); });
+2
View File
@@ -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");
+31
View File
@@ -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");
});
});
-5
View File
@@ -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"
}
-32
View File
@@ -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.
+55
View File
@@ -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;
+4
View File
@@ -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",