skeleton for h2h
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
import { Box, Container, Flex, Loader, Title, useComputedColorScheme } from "@mantine/core";
|
import {
|
||||||
import { PropsWithChildren, Suspense, useEffect, useRef } from "react";
|
Box,
|
||||||
|
Container,
|
||||||
|
Title,
|
||||||
|
useComputedColorScheme,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { PropsWithChildren, useEffect, useRef } from "react";
|
||||||
import { Drawer as VaulDrawer } from "vaul";
|
import { Drawer as VaulDrawer } from "vaul";
|
||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
|
|
||||||
@@ -17,6 +22,11 @@ const Drawer: React.FC<DrawerProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const colorScheme = useComputedColorScheme("light");
|
const colorScheme = useComputedColorScheme("light");
|
||||||
const contentRef = useRef<HTMLDivElement>(null);
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
|
const openedRef = useRef(opened);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
openedRef.current = opened;
|
||||||
|
}, [opened]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const appElement = document.querySelector(".app") as HTMLElement;
|
const appElement = document.querySelector(".app") as HTMLElement;
|
||||||
@@ -57,7 +67,7 @@ const Drawer: React.FC<DrawerProps> = ({
|
|||||||
appElement.classList.remove("drawer-scaling");
|
appElement.classList.remove("drawer-scaling");
|
||||||
themeColorMeta.content = currentColors.normal;
|
themeColorMeta.content = currentColors.normal;
|
||||||
};
|
};
|
||||||
}, [opened, colorScheme]);
|
}, [opened]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!opened || !contentRef.current) return;
|
if (!opened || !contentRef.current) return;
|
||||||
@@ -69,46 +79,44 @@ const Drawer: React.FC<DrawerProps> = ({
|
|||||||
|
|
||||||
if (visualViewport) {
|
if (visualViewport) {
|
||||||
const availableHeight = visualViewport.height;
|
const availableHeight = visualViewport.height;
|
||||||
const maxDrawerHeight = Math.min(availableHeight * 0.75, window.innerHeight * 0.75);
|
const maxDrawerHeight = Math.min(
|
||||||
|
availableHeight * 0.75,
|
||||||
|
window.innerHeight * 0.75
|
||||||
|
);
|
||||||
|
|
||||||
drawerContent.style.maxHeight = `${maxDrawerHeight}px`;
|
drawerContent.style.maxHeight = `${maxDrawerHeight}px`;
|
||||||
} else {
|
} else {
|
||||||
drawerContent.style.maxHeight = '75vh';
|
drawerContent.style.maxHeight = "75vh";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserver(() => {
|
|
||||||
if (contentRef.current) {
|
|
||||||
const drawerContent = contentRef.current.closest('[data-vaul-drawer-wrapper]');
|
|
||||||
if (drawerContent) {
|
|
||||||
(drawerContent as HTMLElement).style.height = 'auto';
|
|
||||||
(drawerContent as HTMLElement).offsetHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
updateDrawerHeight();
|
updateDrawerHeight();
|
||||||
|
|
||||||
if (window.visualViewport) {
|
if (window.visualViewport) {
|
||||||
window.visualViewport.addEventListener('resize', updateDrawerHeight);
|
window.visualViewport.addEventListener("resize", updateDrawerHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
resizeObserver.observe(contentRef.current);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
resizeObserver.disconnect();
|
|
||||||
if (window.visualViewport) {
|
if (window.visualViewport) {
|
||||||
window.visualViewport.removeEventListener('resize', updateDrawerHeight);
|
window.visualViewport.removeEventListener("resize", updateDrawerHeight);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [opened, children]);
|
}, [opened]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VaulDrawer.Root repositionInputs={false} open={opened} onOpenChange={onChange}>
|
<VaulDrawer.Root
|
||||||
|
repositionInputs={false}
|
||||||
|
open={opened}
|
||||||
|
onOpenChange={onChange}
|
||||||
|
>
|
||||||
<VaulDrawer.Portal>
|
<VaulDrawer.Portal>
|
||||||
<VaulDrawer.Overlay className={styles.drawerOverlay} />
|
<VaulDrawer.Overlay className={styles.drawerOverlay} />
|
||||||
<VaulDrawer.Content className={styles.drawerContent} aria-describedby="drawer" ref={contentRef}>
|
<VaulDrawer.Content
|
||||||
|
className={styles.drawerContent}
|
||||||
|
aria-describedby="drawer"
|
||||||
|
ref={contentRef}
|
||||||
|
>
|
||||||
<Container flex={1} p="md">
|
<Container flex={1} p="md">
|
||||||
<Box
|
<Box
|
||||||
mb="sm"
|
mb="sm"
|
||||||
@@ -120,14 +128,10 @@ const Drawer: React.FC<DrawerProps> = ({
|
|||||||
style={{ borderRadius: "9999px" }}
|
style={{ borderRadius: "9999px" }}
|
||||||
/>
|
/>
|
||||||
<Container mx="auto" maw="28rem" px={0}>
|
<Container mx="auto" maw="28rem" px={0}>
|
||||||
<VaulDrawer.Title><Title order={2}>{title}</Title></VaulDrawer.Title>
|
<VaulDrawer.Title>
|
||||||
<Suspense fallback={
|
<Title order={2}>{title}</Title>
|
||||||
<Flex justify='center' align='center' w='100%' h={400}>
|
</VaulDrawer.Title>
|
||||||
<Loader size='lg' />
|
{children}
|
||||||
</Flex>
|
|
||||||
}>
|
|
||||||
{children}
|
|
||||||
</Suspense>
|
|
||||||
</Container>
|
</Container>
|
||||||
</Container>
|
</Container>
|
||||||
</VaulDrawer.Content>
|
</VaulDrawer.Content>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { PropsWithChildren, useCallback } from "react";
|
import { PropsWithChildren, Suspense, useCallback } from "react";
|
||||||
import { useIsMobile } from "@/hooks/use-is-mobile";
|
import { useIsMobile } from "@/hooks/use-is-mobile";
|
||||||
import Drawer from "./drawer";
|
import Drawer from "./drawer";
|
||||||
import Modal from "./modal";
|
import Modal from "./modal";
|
||||||
import { ScrollArea } from "@mantine/core";
|
import { ScrollArea, Flex, Loader } from "@mantine/core";
|
||||||
|
|
||||||
interface SheetProps extends PropsWithChildren {
|
interface SheetProps extends PropsWithChildren {
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -16,6 +16,8 @@ const Sheet: React.FC<SheetProps> = ({ title, children, opened, onChange }) => {
|
|||||||
|
|
||||||
const SheetComponent = isMobile ? Drawer : Modal;
|
const SheetComponent = isMobile ? Drawer : Modal;
|
||||||
|
|
||||||
|
if (!opened) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SheetComponent
|
<SheetComponent
|
||||||
title={title}
|
title={title}
|
||||||
@@ -23,14 +25,20 @@ const Sheet: React.FC<SheetProps> = ({ title, children, opened, onChange }) => {
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
>
|
>
|
||||||
<ScrollArea.Autosize
|
<Suspense fallback={
|
||||||
style={{ flex: 1, maxHeight: '75dvh' }}
|
<Flex justify='center' align='center' w='100%' style={{ minHeight: '25vh' }}>
|
||||||
scrollbarSize={8}
|
<Loader size='lg' />
|
||||||
scrollbars="y"
|
</Flex>
|
||||||
type="scroll"
|
}>
|
||||||
>
|
<ScrollArea.Autosize
|
||||||
{children}
|
style={{ flex: 1, maxHeight: '75dvh' }}
|
||||||
</ScrollArea.Autosize>
|
scrollbarSize={8}
|
||||||
|
scrollbars="y"
|
||||||
|
type="scroll"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ScrollArea.Autosize>
|
||||||
|
</Suspense>
|
||||||
</SheetComponent>
|
</SheetComponent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ const MatchCard = ({ match, hideH2H = false }: MatchCardProps) => {
|
|||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{match.home && match.away && (
|
{match.home && match.away && !hideH2H && h2hSheet.isOpen && (
|
||||||
<Sheet
|
<Sheet
|
||||||
title="Head to Head"
|
title="Head to Head"
|
||||||
{...h2hSheet.props}
|
{...h2hSheet.props}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Stack, Text, Group, Box, Divider, Paper } from "@mantine/core";
|
import { Stack, Text, Group, Box, Divider, Paper } from "@mantine/core";
|
||||||
import { TeamInfo } from "@/features/teams/types";
|
import { TeamInfo } from "@/features/teams/types";
|
||||||
import { useTeamHeadToHead } from "../queries";
|
import { useTeamHeadToHead } from "../queries";
|
||||||
import { useMemo, useEffect, useState } from "react";
|
import { useMemo, useEffect, useState, Suspense } from "react";
|
||||||
import { CrownIcon, TrophyIcon } from "@phosphor-icons/react";
|
import { CrownIcon } from "@phosphor-icons/react";
|
||||||
import MatchList from "./match-list";
|
import MatchList from "./match-list";
|
||||||
|
import TeamHeadToHeadSkeleton from "./team-head-to-head-skeleton";
|
||||||
|
|
||||||
interface TeamHeadToHeadSheetProps {
|
interface TeamHeadToHeadSheetProps {
|
||||||
team1: TeamInfo;
|
team1: TeamInfo;
|
||||||
@@ -11,7 +12,7 @@ interface TeamHeadToHeadSheetProps {
|
|||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TeamHeadToHeadSheet = ({ team1, team2, isOpen = true }: TeamHeadToHeadSheetProps) => {
|
const TeamHeadToHeadContent = ({ team1, team2, isOpen = true }: TeamHeadToHeadSheetProps) => {
|
||||||
const [shouldFetch, setShouldFetch] = useState(false);
|
const [shouldFetch, setShouldFetch] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -205,4 +206,12 @@ const TeamHeadToHeadSheet = ({ team1, team2, isOpen = true }: TeamHeadToHeadShee
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const TeamHeadToHeadSheet = (props: TeamHeadToHeadSheetProps) => {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<TeamHeadToHeadSkeleton />}>
|
||||||
|
<TeamHeadToHeadContent {...props} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default TeamHeadToHeadSheet;
|
export default TeamHeadToHeadSheet;
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { Stack, Skeleton, Group, Paper, Divider } from "@mantine/core";
|
||||||
|
|
||||||
|
const TeamHeadToHeadSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<Stack gap="md">
|
||||||
|
<Paper p="md" withBorder radius="md">
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Group justify="center" gap="xs">
|
||||||
|
<Skeleton height={28} width={140} />
|
||||||
|
<Skeleton height={20} width={20} />
|
||||||
|
<Skeleton height={28} width={140} />
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Group justify="center" gap="lg">
|
||||||
|
<Stack gap={0} align="center">
|
||||||
|
<Skeleton height={32} width={40} />
|
||||||
|
<Skeleton height={16} width={100} mt={4} />
|
||||||
|
</Stack>
|
||||||
|
<Skeleton height={24} width={10} />
|
||||||
|
<Stack gap={0} align="center">
|
||||||
|
<Skeleton height={32} width={40} />
|
||||||
|
<Skeleton height={16} width={100} mt={4} />
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Group justify="center">
|
||||||
|
<Skeleton height={16} width={150} />
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Skeleton height={18} width={130} ml="md" mb="xs" />
|
||||||
|
|
||||||
|
<Paper withBorder>
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Group justify="space-between" px="md" py="sm">
|
||||||
|
<Skeleton height={20} width={60} />
|
||||||
|
<Skeleton height={16} width={80} />
|
||||||
|
<Skeleton height={20} width={60} />
|
||||||
|
</Group>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Group justify="space-between" px="md" py="sm">
|
||||||
|
<Skeleton height={20} width={60} />
|
||||||
|
<Skeleton height={16} width={100} />
|
||||||
|
<Skeleton height={20} width={60} />
|
||||||
|
</Group>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Group justify="space-between" px="md" py="sm">
|
||||||
|
<Skeleton height={20} width={60} />
|
||||||
|
<Skeleton height={16} width={110} />
|
||||||
|
<Skeleton height={20} width={60} />
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Skeleton height={18} width={150} ml="md" />
|
||||||
|
<Stack gap="sm" p="md">
|
||||||
|
<Skeleton height={100} />
|
||||||
|
<Skeleton height={100} />
|
||||||
|
<Skeleton height={100} />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TeamHeadToHeadSkeleton;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useServerQuery } from "@/lib/tanstack-query/hooks";
|
import { useServerSuspenseQuery } from "@/lib/tanstack-query/hooks";
|
||||||
import { getMatchesBetweenTeams, getMatchesBetweenPlayers } from "./server";
|
import { getMatchesBetweenTeams, getMatchesBetweenPlayers } from "./server";
|
||||||
|
|
||||||
export const matchKeys = {
|
export const matchKeys = {
|
||||||
@@ -18,13 +18,13 @@ export const matchQueries = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useTeamHeadToHead = (team1Id: string, team2Id: string, enabled = true) =>
|
export const useTeamHeadToHead = (team1Id: string, team2Id: string, enabled = true) =>
|
||||||
useServerQuery({
|
useServerSuspenseQuery({
|
||||||
...matchQueries.headToHeadTeams(team1Id, team2Id),
|
...matchQueries.headToHeadTeams(team1Id, team2Id),
|
||||||
enabled,
|
enabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const usePlayerHeadToHead = (player1Id: string, player2Id: string, enabled = true) =>
|
export const usePlayerHeadToHead = (player1Id: string, player2Id: string, enabled = true) =>
|
||||||
useServerQuery({
|
useServerSuspenseQuery({
|
||||||
...matchQueries.headToHeadPlayers(player1Id, player2Id),
|
...matchQueries.headToHeadPlayers(player1Id, player2Id),
|
||||||
enabled,
|
enabled,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Stack, Text, Group, Box, Divider, Paper } from "@mantine/core";
|
import { Stack, Text, Group, Box, Divider, Paper } from "@mantine/core";
|
||||||
import { usePlayerHeadToHead } from "@/features/matches/queries";
|
import { usePlayerHeadToHead } from "@/features/matches/queries";
|
||||||
import { useMemo, useEffect, useState } from "react";
|
import { useMemo, useEffect, useState, Suspense } from "react";
|
||||||
import { CrownIcon } from "@phosphor-icons/react";
|
import { CrownIcon } from "@phosphor-icons/react";
|
||||||
import MatchList from "@/features/matches/components/match-list";
|
import MatchList from "@/features/matches/components/match-list";
|
||||||
|
import PlayerHeadToHeadSkeleton from "./player-head-to-head-skeleton";
|
||||||
|
|
||||||
interface PlayerHeadToHeadSheetProps {
|
interface PlayerHeadToHeadSheetProps {
|
||||||
player1Id: string;
|
player1Id: string;
|
||||||
@@ -12,7 +13,7 @@ interface PlayerHeadToHeadSheetProps {
|
|||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PlayerHeadToHeadSheet = ({
|
const PlayerHeadToHeadContent = ({
|
||||||
player1Id,
|
player1Id,
|
||||||
player1Name,
|
player1Name,
|
||||||
player2Id,
|
player2Id,
|
||||||
@@ -267,4 +268,12 @@ const PlayerHeadToHeadSheet = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PlayerHeadToHeadSheet = (props: PlayerHeadToHeadSheetProps) => {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<PlayerHeadToHeadSkeleton />}>
|
||||||
|
<PlayerHeadToHeadContent {...props} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default PlayerHeadToHeadSheet;
|
export default PlayerHeadToHeadSheet;
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { Stack, Skeleton, Group, Paper, Divider } from "@mantine/core";
|
||||||
|
|
||||||
|
const PlayerHeadToHeadSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<Stack gap="md">
|
||||||
|
<Paper p="md" withBorder radius="md">
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Group justify="center" gap="xs">
|
||||||
|
<Skeleton height={28} width={120} />
|
||||||
|
<Skeleton height={20} width={20} />
|
||||||
|
<Skeleton height={28} width={120} />
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Group justify="center" gap="lg">
|
||||||
|
<Stack gap={0} align="center">
|
||||||
|
<Skeleton height={32} width={40} />
|
||||||
|
<Skeleton height={16} width={80} mt={4} />
|
||||||
|
</Stack>
|
||||||
|
<Skeleton height={24} width={10} />
|
||||||
|
<Stack gap={0} align="center">
|
||||||
|
<Skeleton height={32} width={40} />
|
||||||
|
<Skeleton height={16} width={80} mt={4} />
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Group justify="center">
|
||||||
|
<Skeleton height={16} width={150} />
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Skeleton height={18} width={130} ml="md" mb="xs" />
|
||||||
|
|
||||||
|
<Paper withBorder>
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Group justify="space-between" px="md" py="sm">
|
||||||
|
<Skeleton height={20} width={60} />
|
||||||
|
<Skeleton height={16} width={80} />
|
||||||
|
<Skeleton height={20} width={60} />
|
||||||
|
</Group>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Group justify="space-between" px="md" py="sm">
|
||||||
|
<Skeleton height={20} width={60} />
|
||||||
|
<Skeleton height={16} width={100} />
|
||||||
|
<Skeleton height={20} width={60} />
|
||||||
|
</Group>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Group justify="space-between" px="md" py="sm">
|
||||||
|
<Skeleton height={20} width={60} />
|
||||||
|
<Skeleton height={16} width={110} />
|
||||||
|
<Skeleton height={20} width={60} />
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Skeleton height={18} width={130} ml="md" />
|
||||||
|
<Stack gap="sm" p="md">
|
||||||
|
<Skeleton height={100} />
|
||||||
|
<Skeleton height={100} />
|
||||||
|
<Skeleton height={100} />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlayerHeadToHeadSkeleton;
|
||||||
@@ -8,6 +8,7 @@ export function useServerSuspenseQuery<TData>(
|
|||||||
queryFn: () => Promise<ServerResult<TData>>;
|
queryFn: () => Promise<ServerResult<TData>>;
|
||||||
options?: Omit<UseQueryOptions<TData, Error, TData>, 'queryFn' | 'queryKey'>
|
options?: Omit<UseQueryOptions<TData, Error, TData>, 'queryFn' | 'queryKey'>
|
||||||
showErrorToast?: boolean;
|
showErrorToast?: boolean;
|
||||||
|
enabled?: boolean;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const { queryKey, queryFn, showErrorToast = true, options: queryOptions } = options;
|
const { queryKey, queryFn, showErrorToast = true, options: queryOptions } = options;
|
||||||
|
|||||||
Reference in New Issue
Block a user