updated bracket

This commit is contained in:
yohlo
2025-09-29 10:50:18 -05:00
parent 31e50af593
commit a4b9fe9065
4 changed files with 152 additions and 35 deletions

View File

@@ -1,6 +1,7 @@
import { Flex } from "@mantine/core"; import { Flex, Box } from "@mantine/core";
import { Match } from "@/features/matches/types"; import { Match } from "@/features/matches/types";
import { MatchCard } from "./match-card"; import { MatchCard } from "./match-card";
import { useEffect, useRef } from "react";
interface BracketProps { interface BracketProps {
rounds: Match[][]; rounds: Match[][];
@@ -13,33 +14,131 @@ export const Bracket: React.FC<BracketProps> = ({
orders, orders,
showControls, showControls,
}) => { }) => {
return ( const containerRef = useRef<HTMLDivElement>(null);
<Flex direction="row" gap={24} justify="left"> const svgRef = useRef<SVGSVGElement>(null);
{rounds.map((round, roundIndex) => (
<Flex useEffect(() => {
key={roundIndex} const updateConnectorLines = () => {
direction="column" if (!containerRef.current || !svgRef.current) return;
align="center"
pos="relative" const svg = svgRef.current;
gap={24} const container = containerRef.current;
justify="space-around" const flexContainer = container.querySelector('.bracket-flex-container') as HTMLElement;
p={24} if (!flexContainer) return;
>
{round.map((match) => svg.innerHTML = '';
match.bye ? (
<div key={match.lid}></div> const flexRect = flexContainer.getBoundingClientRect();
) : ( const containerRect = container.getBoundingClientRect();
<div key={match.lid}>
<MatchCard svg.style.width = `${flexContainer.scrollWidth}px`;
match={match} svg.style.height = `${flexContainer.scrollHeight}px`;
orders={orders}
showControls={showControls} rounds.forEach((round, roundIndex) => {
/> if (roundIndex === rounds.length - 1) return;
</div>
const nextRound = rounds[roundIndex + 1];
round.forEach((match, matchIndex) => {
if (match.bye) return;
const matchElement = container.querySelector(`[data-match-lid="${match.lid}"]`) as HTMLElement;
if (!matchElement) return;
const nextMatches = nextRound.filter(nextMatch =>
!nextMatch.bye && (
orders[nextMatch.home_from_lid] === match.order ||
orders[nextMatch.away_from_lid] === match.order
) )
)} );
</Flex>
))} nextMatches.forEach(nextMatch => {
</Flex> const nextMatchElement = container.querySelector(`[data-match-lid="${nextMatch.lid}"]`) as HTMLElement;
if (!nextMatchElement) return;
const matchRect = matchElement.getBoundingClientRect();
const nextMatchRect = nextMatchElement.getBoundingClientRect();
const startX = matchRect.right - flexRect.left;
const startY = matchRect.top + matchRect.height / 2 - flexRect.top;
const endX = nextMatchRect.left - flexRect.left;
const endY = nextMatchRect.top + nextMatchRect.height / 2 - flexRect.top;
const midX = startX + (endX - startX) * 0.5;
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
const pathData = `M ${startX} ${startY} L ${midX} ${startY} L ${midX} ${endY} L ${endX} ${endY}`;
path.setAttribute('d', pathData);
path.setAttribute('stroke', 'var(--mantine-color-default-border)');
path.setAttribute('stroke-width', '2');
path.setAttribute('fill', 'none');
path.setAttribute('stroke-linecap', 'round');
path.setAttribute('stroke-linejoin', 'round');
svg.appendChild(path);
});
});
});
};
updateConnectorLines();
const handleUpdate = () => {
requestAnimationFrame(updateConnectorLines);
};
const scrollContainer = containerRef.current?.closest('.mantine-ScrollArea-viewport');
scrollContainer?.addEventListener('scroll', handleUpdate);
window.addEventListener('resize', handleUpdate);
return () => {
scrollContainer?.removeEventListener('scroll', handleUpdate);
window.removeEventListener('resize', handleUpdate);
};
}, [rounds, orders]);
return (
<Box pos="relative" ref={containerRef}>
<svg
ref={svgRef}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
pointerEvents: 'none',
zIndex: 0,
}}
/>
<Flex direction="row" gap={24} justify="left" pos="relative" style={{ zIndex: 1 }} className="bracket-flex-container">
{rounds.map((round, roundIndex) => (
<Flex
key={roundIndex}
direction="column"
align="center"
pos="relative"
gap={24}
justify="space-around"
p={24}
>
{round.map((match) =>
match.bye ? (
<div key={match.lid}></div>
) : (
<div key={match.lid}>
<MatchCard
match={match}
orders={orders}
showControls={showControls}
/>
</div>
)
)}
</Flex>
))}
</Flex>
</Box>
); );
}; };

View File

@@ -199,7 +199,15 @@ export const MatchCard: React.FC<MatchCardProps> = ({
return ( return (
<Flex direction="row" align="center" justify="end" gap={8}> <Flex direction="row" align="center" justify="end" gap={8}>
<Text c="dimmed" fw="bolder"> <Text
c="dimmed"
fw="bolder"
px={6}
py={2}
style={{
backgroundColor: 'var(--mantine-color-body)'
}}
>
{match.order} {match.order}
</Text> </Text>
<Flex align="stretch"> <Flex align="stretch">
@@ -214,7 +222,12 @@ export const MatchCard: React.FC<MatchCardProps> = ({
w={showToolbar || showEditButton ? 200 : 220} w={showToolbar || showEditButton ? 200 : 220}
withBorder withBorder
pos="relative" pos="relative"
style={{ overflow: "visible" }} style={{
overflow: "visible",
backgroundColor: 'var(--mantine-color-body)',
borderColor: 'var(--mantine-color-default-border)',
boxShadow: 'var(--mantine-shadow-sm)',
}}
data-match-lid={match.lid} data-match-lid={match.lid}
> >
<Card.Section withBorder p={0}> <Card.Section withBorder p={0}>

View File

@@ -21,9 +21,16 @@ export const MatchSlot: React.FC<MatchSlotProps> = ({
cups, cups,
isWinner isWinner
}) => ( }) => (
<Flex align="stretch"> <Flex
align="stretch"
style={{
backgroundColor: isWinner ? 'var(--mantine-color-green-light)' : 'transparent',
borderRadius: 'var(--mantine-radius-sm)',
transition: 'background-color 200ms ease',
}}
>
{(seed && seed > 0) ? <SeedBadge seed={seed} /> : undefined} {(seed && seed > 0) ? <SeedBadge seed={seed} /> : undefined}
<Flex p="4px 8px" w='100%' align="center"> <Flex p="6px 10px" w='100%' align="center">
<Flex align="center" gap={4} flex={1}> <Flex align="center" gap={4} flex={1}>
{team ? ( {team ? (
<> <>

View File

@@ -18,9 +18,7 @@ export const SeedBadge: React.FC<SeedBadgeProps> = ({ seed }) => {
color: "var(--mantine-color-text)", color: "var(--mantine-color-text)",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center"
borderTopLeftRadius: "var(--mantine-radius-default)",
borderBottomLeftRadius: "var(--mantine-radius-default)",
}} }}
> >
{seed} {seed}