scroll to top button added

This commit is contained in:
Owen Rees
2023-06-20 15:00:59 +02:00
parent 5aaa1deeff
commit bcc3c74973
8 changed files with 97 additions and 40 deletions
+2 -1
View File
@@ -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
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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">
+25
View File
@@ -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
View File
@@ -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>
); );
}; };
+31 -8
View File
@@ -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>
+8
View File
@@ -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" });
};
+2
View File
@@ -12,3 +12,5 @@ export interface Tournament {
export interface TournamentDataProps { export interface TournamentDataProps {
tournamentData: Tournament[]; tournamentData: Tournament[];
} }
export type ScrollableElement = Window | HTMLElement;