styling
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
import { Suspense } from "react";
|
||||
import {
|
||||
ActionIcon,
|
||||
Box,
|
||||
Center,
|
||||
CloseButton,
|
||||
Group,
|
||||
Indicator,
|
||||
Loader,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
||||
import { FootballHelmetIcon } from "@phosphor-icons/react";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { Match } from "@/features/matches/types";
|
||||
import TeamAvatar from "@/components/team-avatar";
|
||||
import AnimatedScore from "@/features/matches/components/animated-score";
|
||||
import EmojiBar from "@/features/reactions/components/emoji-bar";
|
||||
import TeamHeadToHeadSheet from "@/features/matches/components/team-head-to-head-sheet";
|
||||
import Sheet from "@/components/sheet/sheet";
|
||||
import { useSheet } from "@/hooks/use-sheet";
|
||||
|
||||
const EASE: [number, number, number, number] = [0.32, 0.72, 0, 1];
|
||||
|
||||
interface MatchDockProps {
|
||||
match: Match | null;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const TeamRow = ({
|
||||
team,
|
||||
cups,
|
||||
isWinner,
|
||||
isRegional,
|
||||
ended,
|
||||
}: {
|
||||
team: NonNullable<Match["home"]>;
|
||||
cups: number;
|
||||
isWinner: boolean;
|
||||
isRegional: boolean;
|
||||
ended: boolean;
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Group justify="space-between" wrap="nowrap" gap="sm">
|
||||
<Group
|
||||
gap="sm"
|
||||
wrap="nowrap"
|
||||
style={{ flex: 1, minWidth: 0, cursor: "pointer" }}
|
||||
onClick={() => navigate({ to: `/teams/${team.id}` })}
|
||||
>
|
||||
<TeamAvatar
|
||||
team={team}
|
||||
size={32}
|
||||
radius="sm"
|
||||
winner={ended && isWinner}
|
||||
isRegional={isRegional}
|
||||
/>
|
||||
<Text size="sm" fw={ended && isWinner ? 700 : 500} lineClamp={1}>
|
||||
{team.name}
|
||||
</Text>
|
||||
</Group>
|
||||
{ended && (
|
||||
<AnimatedScore
|
||||
value={cups}
|
||||
size="lg"
|
||||
fw={700}
|
||||
c={isWinner ? "green" : "dimmed"}
|
||||
style={{ minWidth: 28, textAlign: "center" }}
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
const MatchDock = ({ match, onClose }: MatchDockProps) => {
|
||||
const reduceMotion = useReducedMotion();
|
||||
const h2hSheet = useSheet();
|
||||
const hasPrivate = match?.home?.private || match?.away?.private;
|
||||
const ended = match?.status === "ended";
|
||||
const started = match?.status === "started";
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnimatePresence>
|
||||
{match && match.home && match.away && (
|
||||
<motion.div
|
||||
key="match-dock"
|
||||
initial={reduceMotion ? { opacity: 0 } : { opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={reduceMotion ? { opacity: 0 } : { opacity: 0, y: 16 }}
|
||||
transition={{ duration: 0.2, ease: EASE }}
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
zIndex: 5,
|
||||
padding: "0 12px 12px",
|
||||
}}
|
||||
>
|
||||
<Paper withBorder shadow="md" radius="lg" px="md" py="sm">
|
||||
<Stack gap="xs">
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
{started && (
|
||||
<Indicator
|
||||
size={8}
|
||||
color="red"
|
||||
processing
|
||||
position="middle-start"
|
||||
offset={0}
|
||||
/>
|
||||
)}
|
||||
<Text size="xs" fw={600} c="dimmed" lineClamp={1}>
|
||||
Match {match.order} · Round {match.round + 1}
|
||||
{match.is_losers_bracket && " (Losers)"}
|
||||
</Text>
|
||||
</Group>
|
||||
<CloseButton
|
||||
size="sm"
|
||||
onClick={onClose}
|
||||
aria-label="Close match actions"
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<TeamRow
|
||||
team={match.home}
|
||||
cups={match.home_cups}
|
||||
isWinner={match.home_cups > match.away_cups}
|
||||
isRegional={match.tournament.regional === true}
|
||||
ended={ended}
|
||||
/>
|
||||
<Box
|
||||
style={{
|
||||
height: 1,
|
||||
backgroundColor: "var(--mantine-color-default-border)",
|
||||
}}
|
||||
/>
|
||||
<TeamRow
|
||||
team={match.away}
|
||||
cups={match.away_cups}
|
||||
isWinner={match.away_cups > match.home_cups}
|
||||
isRegional={match.tournament.regional === true}
|
||||
ended={ended}
|
||||
/>
|
||||
|
||||
<Group justify="space-between" wrap="nowrap" gap="sm">
|
||||
<Box style={{ flex: 1, minWidth: 0 }}>
|
||||
<Suspense
|
||||
fallback={
|
||||
<Center py={4}>
|
||||
<Loader size="xs" />
|
||||
</Center>
|
||||
}
|
||||
>
|
||||
<EmojiBar matchId={match.id} />
|
||||
</Suspense>
|
||||
</Box>
|
||||
{!hasPrivate && (
|
||||
<Tooltip label="Head to Head" withArrow position="top">
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={h2hSheet.open}
|
||||
aria-label="View head-to-head"
|
||||
w={40}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
<Group
|
||||
style={{ position: "relative", width: 27.5, height: 16 }}
|
||||
>
|
||||
<FootballHelmetIcon
|
||||
size={14}
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
top: 0,
|
||||
transform: "rotate(25deg)",
|
||||
}}
|
||||
/>
|
||||
<FootballHelmetIcon
|
||||
size={14}
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
transform: "scaleX(-1) rotate(25deg)",
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{match?.home && match?.away && h2hSheet.isOpen && (
|
||||
<Sheet title="Head to Head" {...h2hSheet.props}>
|
||||
<TeamHeadToHeadSheet
|
||||
team1={match.home}
|
||||
team2={match.away}
|
||||
isOpen={h2hSheet.props.opened}
|
||||
/>
|
||||
</Sheet>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MatchDock;
|
||||
Reference in New Issue
Block a user