mirror of
https://github.com/TheRealOwenRees/echecsfrance.git
synced 2026-07-23 12:36:57 +00:00
scroll to top button added
This commit is contained in:
@@ -5,7 +5,8 @@
|
|||||||
//TODO about page
|
//TODO about page
|
||||||
//TODO contact page needs working mailer
|
//TODO contact page needs working mailer
|
||||||
//TODO font size on mobile screen
|
//TODO font size on mobile screen
|
||||||
//TODO need a 'return to top' arrow button on smaller screens
|
//TODO scroll to top button positioning
|
||||||
|
//TODO add test for return to top button
|
||||||
//TODO SEO - next headers etc
|
//TODO SEO - next headers etc
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
//TODO logo for navbar and favicon
|
//TODO logo for navbar and favicon
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default async function Tournaments() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<main className="grid lg:grid-cols-2">
|
<main className="relative grid lg:grid-cols-2">
|
||||||
<div className="">
|
<div className="">
|
||||||
<TournamentMap tournamentData={JSON.parse(tournamentData)} />
|
<TournamentMap tournamentData={JSON.parse(tournamentData)} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { FaGithub, FaRegEnvelope } from "react-icons/fa";
|
|||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
return (
|
return (
|
||||||
<footer
|
<footer
|
||||||
className="grid w-full fixed bottom-0 justify-items-center py-2 px-5 text-white bg-teal-600 text-gray-900 dark:bg-gray-700"
|
className="grid w-full fixed bottom-0 justify-items-center py-2 px-5 text-white bg-teal-600 text-gray-900 dark:bg-gray-700 z-30"
|
||||||
data-cy="footer"
|
data-cy="footer"
|
||||||
>
|
>
|
||||||
<div className="p-2">
|
<div className="p-2">
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FaArrowUp } from "react-icons/fa";
|
||||||
|
import { handleScrollToTop } from "@/handlers/scrollHandlers";
|
||||||
|
|
||||||
|
const ScrollToTopButton = ({ isLgScreen }: { isLgScreen: boolean }) => {
|
||||||
|
// determine scrollable element based on screen size - window or div
|
||||||
|
const scrollToTopElement = isLgScreen
|
||||||
|
? document.getElementById("tournament-table")
|
||||||
|
: window;
|
||||||
|
|
||||||
|
const scrollToTopButtonClass = isLgScreen
|
||||||
|
? "absolute bottom-0 right-3 p-10"
|
||||||
|
: "sticky top-20";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={`${scrollToTopButtonClass} z-10 text-2xl text-teal-900 dark:text-white`}
|
||||||
|
>
|
||||||
|
<FaArrowUp onClick={() => handleScrollToTop(scrollToTopElement)} />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScrollToTopButton;
|
||||||
+27
-29
@@ -10,37 +10,35 @@ const SearchBar = ({
|
|||||||
setTournamentFilter,
|
setTournamentFilter,
|
||||||
}: SearchBarProps) => {
|
}: SearchBarProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="p-3 bg-white dark:bg-gray-800">
|
||||||
<div className="p-3 bg-white dark:bg-gray-800">
|
<label htmlFor="table-search" className="sr-only">
|
||||||
<label htmlFor="table-search" className="sr-only">
|
Search
|
||||||
Search
|
</label>
|
||||||
</label>
|
<div className="relative mt-1">
|
||||||
<div className="relative mt-1">
|
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||||
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
<svg
|
||||||
<svg
|
className="w-5 h-5 text-gray-500 dark:text-gray-400"
|
||||||
className="w-5 h-5 text-gray-500 dark:text-gray-400"
|
fill="currentColor"
|
||||||
fill="currentColor"
|
viewBox="0 0 20 20"
|
||||||
viewBox="0 0 20 20"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
>
|
||||||
>
|
<path
|
||||||
<path
|
fillRule="evenodd"
|
||||||
fillRule="evenodd"
|
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
||||||
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
clipRule="evenodd"
|
||||||
clipRule="evenodd"
|
></path>
|
||||||
></path>
|
</svg>
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="table-search"
|
|
||||||
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-80 pl-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
||||||
placeholder="Search for items"
|
|
||||||
value={tournamentFilter}
|
|
||||||
onChange={(e) => setTournamentFilter(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="table-search"
|
||||||
|
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-80 pl-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
|
placeholder="Search for items"
|
||||||
|
value={tournamentFilter}
|
||||||
|
onChange={(e) => setTournamentFilter(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { TournamentDataProps } from "@/types";
|
import { TournamentDataProps } from "@/types";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import SearchBar from "@/components/SearchBar";
|
import SearchBar from "@/components/SearchBar";
|
||||||
|
import ScrollToTopButton from "@/components/ScrollToTopButton";
|
||||||
|
|
||||||
export default function TournamentTable({
|
export default function TournamentTable({
|
||||||
tournamentData,
|
tournamentData,
|
||||||
@@ -11,6 +12,7 @@ export default function TournamentTable({
|
|||||||
const [searchQuery, setSearchQuery] = useState(""); // text from search bar
|
const [searchQuery, setSearchQuery] = useState(""); // text from search bar
|
||||||
const [filteredTournamentData, setFilteredTournamentData] =
|
const [filteredTournamentData, setFilteredTournamentData] =
|
||||||
useState(tournamentData);
|
useState(tournamentData);
|
||||||
|
const [isLgScreen, setIsLgScreen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFilteredTournamentData(
|
setFilteredTournamentData(
|
||||||
@@ -18,6 +20,18 @@ export default function TournamentTable({
|
|||||||
);
|
);
|
||||||
}, [searchQuery]);
|
}, [searchQuery]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
setIsLgScreen(window.innerWidth >= 1024);
|
||||||
|
};
|
||||||
|
handleResize();
|
||||||
|
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("resize", handleResize);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
// TODO move this section into its own function
|
// TODO move this section into its own function
|
||||||
if (filteredTournamentData.length === 0) {
|
if (filteredTournamentData.length === 0) {
|
||||||
tableData = (
|
tableData = (
|
||||||
@@ -57,25 +71,34 @@ export default function TournamentTable({
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
const stickyHeader =
|
|
||||||
"sticky top-0 p-3 bg-teal-600 text-white dark:bg-gray-600";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="w-full grid auto-rows-max pb-20 lg:h-[calc(100vh-174px)] lg:col-start-2 lg:col-end-3">
|
<section
|
||||||
|
className="tournament-table w-full grid auto-rows-max pb-20 lg:h-[calc(100vh-174px)] lg:col-start-2 lg:col-end-3 lg:overflow-y-scroll"
|
||||||
|
id="tournament-table"
|
||||||
|
>
|
||||||
<SearchBar
|
<SearchBar
|
||||||
tournamentFilter={searchQuery}
|
tournamentFilter={searchQuery}
|
||||||
setTournamentFilter={setSearchQuery}
|
setTournamentFilter={setSearchQuery}
|
||||||
/>
|
/>
|
||||||
|
<ScrollToTopButton isLgScreen={isLgScreen} />
|
||||||
<table
|
<table
|
||||||
className="relative table-fixed w-full text-center text-xs"
|
className="relative table-fixed w-full text-center text-xs"
|
||||||
data-cy="tournament-table"
|
data-cy="tournament-table"
|
||||||
>
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th className={stickyHeader}>Date</th>
|
<th className="sticky top-0 p-3 bg-teal-600 text-white dark:bg-gray-600">
|
||||||
<th className={stickyHeader}>Ville</th>
|
Date
|
||||||
<th className={stickyHeader}>Tournois</th>
|
</th>
|
||||||
<th className={stickyHeader}>Cadence</th>
|
<th className="sticky top-0 p-3 bg-teal-600 text-white dark:bg-gray-600">
|
||||||
|
Ville
|
||||||
|
</th>
|
||||||
|
<th className="sticky top-0 p-3 bg-teal-600 text-white dark:bg-gray-600">
|
||||||
|
Tournois
|
||||||
|
</th>
|
||||||
|
<th className="sticky top-0 p-3 bg-teal-600 text-white dark:bg-gray-600">
|
||||||
|
Cadence
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>{tableData}</tbody>
|
<tbody>{tableData}</tbody>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { ScrollableElement } from "@/types";
|
||||||
|
|
||||||
|
const isBrowser = () => typeof window !== "undefined";
|
||||||
|
|
||||||
|
export const handleScrollToTop = (element: ScrollableElement | null) => {
|
||||||
|
if (!isBrowser()) return;
|
||||||
|
element?.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user