scroll to the top of the score on when new scores are loaded
Some checks failed
Deploy / deploy (push) Has been cancelled
Some checks failed
Deploy / deploy (push) Has been cancelled
This commit is contained in:
parent
732599a721
commit
08ce0cf5aa
@ -3,7 +3,7 @@ import useWindowDimensions from "@/hooks/use-window-dimensions";
|
|||||||
import { ClockIcon, TrophyIcon, XMarkIcon } from "@heroicons/react/24/solid";
|
import { ClockIcon, TrophyIcon, XMarkIcon } from "@heroicons/react/24/solid";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { motion, useAnimation, Variants } from "framer-motion";
|
import { motion, useAnimation, Variants } from "framer-motion";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import Card from "../card";
|
import Card from "../card";
|
||||||
import Pagination from "../input/pagination";
|
import Pagination from "../input/pagination";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
@ -57,10 +57,10 @@ export default function PlayerScores({ initialScoreData, initialSearch, player,
|
|||||||
const [currentScores, setCurrentScores] = useState<ScoreSaberPlayerScoresPageToken | undefined>(initialScoreData);
|
const [currentScores, setCurrentScores] = useState<ScoreSaberPlayerScoresPageToken | undefined>(initialScoreData);
|
||||||
const [searchTerm, setSearchTerm] = useState(initialSearch || "");
|
const [searchTerm, setSearchTerm] = useState(initialSearch || "");
|
||||||
const debouncedSearchTerm = useDebounce(searchTerm, 250);
|
const debouncedSearchTerm = useDebounce(searchTerm, 250);
|
||||||
|
const [shouldFetch, setShouldFetch] = useState(false); // New state to control fetching
|
||||||
|
const topOfScoresRef = useRef(null);
|
||||||
|
|
||||||
const isSearchActive = debouncedSearchTerm.length >= 3;
|
const isSearchActive = debouncedSearchTerm.length >= 3;
|
||||||
const [shouldFetch, setShouldFetch] = useState(false); // New state to control fetching
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: scores,
|
data: scores,
|
||||||
isError,
|
isError,
|
||||||
@ -79,12 +79,20 @@ export default function PlayerScores({ initialScoreData, initialSearch, player,
|
|||||||
enabled: shouldFetch && (debouncedSearchTerm.length >= 3 || debouncedSearchTerm.length === 0), // Only enable if we set shouldFetch to true
|
enabled: shouldFetch && (debouncedSearchTerm.length >= 3 || debouncedSearchTerm.length === 0), // Only enable if we set shouldFetch to true
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleScoreLoad = useCallback(async () => {
|
/**
|
||||||
|
* Starts the animation for the scores.
|
||||||
|
*/
|
||||||
|
const handleScoreAnimation = useCallback(async () => {
|
||||||
await controls.start(previousPage >= pageState.page ? "hiddenRight" : "hiddenLeft");
|
await controls.start(previousPage >= pageState.page ? "hiddenRight" : "hiddenLeft");
|
||||||
setCurrentScores(scores);
|
setCurrentScores(scores);
|
||||||
await controls.start("visible");
|
await controls.start("visible");
|
||||||
}, [scores, controls, previousPage, pageState.page]);
|
}, [scores, controls, previousPage, pageState.page]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the score sort.
|
||||||
|
*
|
||||||
|
* @param newSort the new sort
|
||||||
|
*/
|
||||||
const handleSortChange = (newSort: ScoreSort) => {
|
const handleSortChange = (newSort: ScoreSort) => {
|
||||||
if (newSort !== pageState.sort) {
|
if (newSort !== pageState.sort) {
|
||||||
setPageState({ page: 1, sort: newSort });
|
setPageState({ page: 1, sort: newSort });
|
||||||
@ -92,15 +100,11 @@ export default function PlayerScores({ initialScoreData, initialSearch, player,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
/**
|
||||||
if (scores) handleScoreLoad();
|
* Change the score search term.
|
||||||
}, [scores, handleScoreLoad]);
|
*
|
||||||
|
* @param query the new search term
|
||||||
useEffect(() => {
|
*/
|
||||||
const newUrl = `/player/${player.id}/${pageState.sort}/${pageState.page}${isSearchActive ? `?search=${debouncedSearchTerm}` : ""}`;
|
|
||||||
window.history.replaceState({ ...window.history.state, as: newUrl, url: newUrl }, "", newUrl);
|
|
||||||
}, [pageState, debouncedSearchTerm, player.id, isSearchActive]);
|
|
||||||
|
|
||||||
const handleSearchChange = (query: string) => {
|
const handleSearchChange = (query: string) => {
|
||||||
setSearchTerm(query);
|
setSearchTerm(query);
|
||||||
if (query.length >= 3) {
|
if (query.length >= 3) {
|
||||||
@ -110,14 +114,50 @@ export default function PlayerScores({ initialScoreData, initialSearch, player,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the score search term.
|
||||||
|
*/
|
||||||
const clearSearch = () => {
|
const clearSearch = () => {
|
||||||
setSearchTerm("");
|
setSearchTerm("");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle score animation.
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (scores) handleScoreAnimation();
|
||||||
|
}, [scores, handleScoreAnimation]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle updating the URL when the page number,
|
||||||
|
* sort, or search term changes.
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
const newUrl = `/player/${player.id}/${pageState.sort}/${pageState.page}${isSearchActive ? `?search=${debouncedSearchTerm}` : ""}`;
|
||||||
|
window.history.replaceState({ ...window.history.state, as: newUrl, url: newUrl }, "", newUrl);
|
||||||
|
}, [pageState, debouncedSearchTerm, player.id, isSearchActive]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle scrolling to the top of the
|
||||||
|
* scores when new scores are loaded.
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (topOfScoresRef.current) {
|
||||||
|
const topOfScoresPosition = (topOfScoresRef.current as any).getBoundingClientRect().top + window.scrollY;
|
||||||
|
window.scrollTo({
|
||||||
|
top: topOfScoresPosition - 55, // Navbar height (plus some padding)
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [pageState, topOfScoresRef]);
|
||||||
|
|
||||||
const invalidSearch = searchTerm.length >= 1 && searchTerm.length < 3;
|
const invalidSearch = searchTerm.length >= 1 && searchTerm.length < 3;
|
||||||
return (
|
return (
|
||||||
<Card className="flex gap-1">
|
<Card className="flex gap-1">
|
||||||
<div className="flex flex-col items-center w-full gap-2 relative">
|
<div className="flex flex-col items-center w-full gap-2 relative">
|
||||||
|
{/* Where to scroll to when new scores are loaded */}
|
||||||
|
<div ref={topOfScoresRef} className="absolute flex h-11 p-11" />
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{scoreSort.map(sortOption => (
|
{scoreSort.map(sortOption => (
|
||||||
<Button
|
<Button
|
||||||
|
Reference in New Issue
Block a user