add skeleton for mini rankings
All checks were successful
Deploy / deploy (push) Successful in 5m16s
All checks were successful
Deploy / deploy (push) Successful in 5m16s
This commit is contained in:
parent
0757012f02
commit
794de708b9
7
src/app/components/ui/skeleton.tsx
Normal file
7
src/app/components/ui/skeleton.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { cn } from "@/common/utils";
|
||||||
|
|
||||||
|
function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
|
return <div className={cn("animate-pulse rounded-md bg-primary/10", className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Skeleton };
|
@ -12,11 +12,11 @@ import Pagination from "../input/pagination";
|
|||||||
import LeaderboardScore from "./leaderboard-score";
|
import LeaderboardScore from "./leaderboard-score";
|
||||||
import { scoreAnimation } from "@/components/score/score-animation";
|
import { scoreAnimation } from "@/components/score/score-animation";
|
||||||
|
|
||||||
type Props = {
|
type LeaderboardScoresProps = {
|
||||||
leaderboard: ScoreSaberLeaderboardToken;
|
leaderboard: ScoreSaberLeaderboardToken;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LeaderboardScores({ leaderboard }: Props) {
|
export default function LeaderboardScores({ leaderboard }: LeaderboardScoresProps) {
|
||||||
const { width } = useWindowDimensions();
|
const { width } = useWindowDimensions();
|
||||||
const controls = useAnimation();
|
const controls = useAnimation();
|
||||||
|
|
||||||
|
@ -26,9 +26,9 @@ const items: NavbarItem[] = [
|
|||||||
|
|
||||||
// Helper function to render each navbar item
|
// Helper function to render each navbar item
|
||||||
const renderNavbarItem = (item: NavbarItem) => (
|
const renderNavbarItem = (item: NavbarItem) => (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center w-fit gap-2">
|
||||||
{item.icon && <div className="mr-2">{item.icon}</div>}
|
{item.icon && <div>{item.icon}</div>}
|
||||||
<div>{item.name}</div>
|
<p className="hidden lg:block">{item.name}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ export default function Navbar() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full sticky top-0 z-[999]">
|
<div className="w-full sticky top-0 z-[999]">
|
||||||
<div className="h-10 items-center flex justify-between bg-secondary/95">
|
<div className="h-10 items-center flex justify-between bg-secondary/95 px-1">
|
||||||
{/* Left-aligned items */}
|
{/* Left-aligned items */}
|
||||||
<div className="flex items-center h-full">
|
<div className="flex items-center h-full">
|
||||||
<ProfileButton />
|
<ProfileButton />
|
||||||
|
@ -27,7 +27,7 @@ export default function ProfileButton() {
|
|||||||
src={`https://img.fascinated.cc/upload/w_24,h_24/https://cdn.scoresaber.com/avatars/${settings.playerId}.jpg`}
|
src={`https://img.fascinated.cc/upload/w_24,h_24/https://cdn.scoresaber.com/avatars/${settings.playerId}.jpg`}
|
||||||
/>
|
/>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<p>You</p>
|
<p className="hidden lg:block">You</p>
|
||||||
</NavbarButton>
|
</NavbarButton>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
@ -10,6 +10,7 @@ import CountryFlag from "../country-flag";
|
|||||||
import { Avatar, AvatarImage } from "../ui/avatar";
|
import { Avatar, AvatarImage } from "../ui/avatar";
|
||||||
import ScoreSaberPlayer from "@/common/model/player/impl/scoresaber-player";
|
import ScoreSaberPlayer from "@/common/model/player/impl/scoresaber-player";
|
||||||
import { scoresaberService } from "@/common/service/impl/scoresaber";
|
import { scoresaberService } from "@/common/service/impl/scoresaber";
|
||||||
|
import { PlayerRankingSkeleton } from "@/components/ranking/player-ranking-skeleton";
|
||||||
|
|
||||||
const PLAYER_NAME_MAX_LENGTH = 18;
|
const PLAYER_NAME_MAX_LENGTH = 18;
|
||||||
|
|
||||||
@ -96,6 +97,10 @@ export default function Mini({ type, player }: MiniProps) {
|
|||||||
players = players.slice(playerPosition - 3, playerPosition + 2);
|
players = players.slice(playerPosition - 3, playerPosition + 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <PlayerRankingSkeleton />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="w-full flex gap-2 sticky select-none">
|
<Card className="w-full flex gap-2 sticky select-none">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
@ -103,7 +108,6 @@ export default function Mini({ type, player }: MiniProps) {
|
|||||||
<p>{type} Ranking</p>
|
<p>{type} Ranking</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col text-sm">
|
<div className="flex flex-col text-sm">
|
||||||
{isLoading && <p className="text-gray-400">Loading...</p>}
|
|
||||||
{isError && <p className="text-red-500">Error</p>}
|
{isError && <p className="text-red-500">Error</p>}
|
||||||
{players?.map((playerRanking, index) => {
|
{players?.map((playerRanking, index) => {
|
||||||
const rank = type == "Global" ? playerRanking.rank : playerRanking.countryRank;
|
const rank = type == "Global" ? playerRanking.rank : playerRanking.countryRank;
|
||||||
@ -120,13 +124,13 @@ export default function Mini({ type, player }: MiniProps) {
|
|||||||
className="grid gap-2 grid-cols-[auto_1fr_auto] items-center bg-accent px-2 py-1.5 cursor-pointer transform-gpu transition-all hover:brightness-75 first:rounded-t last:rounded-b"
|
className="grid gap-2 grid-cols-[auto_1fr_auto] items-center bg-accent px-2 py-1.5 cursor-pointer transform-gpu transition-all hover:brightness-75 first:rounded-t last:rounded-b"
|
||||||
>
|
>
|
||||||
<p className="text-gray-400">#{formatNumberWithCommas(rank)}</p>
|
<p className="text-gray-400">#{formatNumberWithCommas(rank)}</p>
|
||||||
<div className="flex gap-1 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Avatar className="w-6 h-6 pointer-events-none">
|
<Avatar className="w-6 h-6 pointer-events-none">
|
||||||
<AvatarImage alt="Profile Picture" src={playerRanking.profilePicture} />
|
<AvatarImage alt="Profile Picture" src={playerRanking.profilePicture} />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<p className={playerRanking.id === player.id ? "text-pp font-semibold" : ""}>{playerName}</p>
|
<p className={playerRanking.id === player.id ? "text-pp font-semibold" : ""}>{playerName}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="inline-flex min-w-[10.75em] gap-1 items-center">
|
<div className="inline-flex min-w-[10.75em] gap-2 items-center">
|
||||||
<p className="text-pp text-right">{formatPp(playerRanking.pp)}pp</p>
|
<p className="text-pp text-right">{formatPp(playerRanking.pp)}pp</p>
|
||||||
{playerRanking.id !== player.id && (
|
{playerRanking.id !== player.id && (
|
||||||
<p className={`text-xs text-right ${ppDifference > 0 ? "text-green-400" : "text-red-400"}`}>
|
<p className={`text-xs text-right ${ppDifference > 0 ? "text-green-400" : "text-red-400"}`}>
|
||||||
|
34
src/components/ranking/player-ranking-skeleton.tsx
Normal file
34
src/components/ranking/player-ranking-skeleton.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import Card from "@/components/card";
|
||||||
|
import { Skeleton } from "@/app/components/ui/skeleton";
|
||||||
|
|
||||||
|
export function PlayerRankingSkeleton() {
|
||||||
|
const skeletonArray = new Array(5).fill(0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="w-full flex gap-2 sticky select-none">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Skeleton className="w-6 h-6 rounded-full animate-pulse" /> {/* Icon Skeleton */}
|
||||||
|
<Skeleton className="w-32 h-6 animate-pulse" /> {/* Text Skeleton for Ranking */}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col text-sm">
|
||||||
|
{skeletonArray.map((_, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="grid gap-2 grid-cols-[auto_1fr_auto] items-center bg-accent px-2 py-1.5 cursor-pointer transform-gpu transition-all first:rounded-t last:rounded-b"
|
||||||
|
>
|
||||||
|
<Skeleton className="w-12 h-6" /> {/* Rank Skeleton */}
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
<Skeleton className="w-6 h-6 rounded-full animate-pulse" /> {/* Avatar Skeleton */}
|
||||||
|
<Skeleton className="w-24 h-6 animate-pulse" /> {/* Player Name Skeleton */}
|
||||||
|
</div>
|
||||||
|
<div className="inline-flex min-w-[10.75em] gap-2 items-center">
|
||||||
|
<Skeleton className="w-16 h-6 animate-pulse" /> {/* PP Value Skeleton */}
|
||||||
|
<Skeleton className="w-8 h-4 animate-pulse" /> {/* PP Difference Skeleton */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
Reference in New Issue
Block a user