mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-29 06:43:17 -05:00
Import <Bracket /> component
This commit is contained in:
@@ -62,7 +62,7 @@ You can give a variation as a flag to the seeding script changing what exactly i
|
||||
- [ ] Admin can change team captain
|
||||
- [x] Admin can randomize and rerandomize maps
|
||||
- [x] Admin can change seeding
|
||||
- [ ] Admin can start the tournament
|
||||
- [x] Admin can start the tournament
|
||||
- [ ] Table design to support arbitrary brackets
|
||||
- [x] Generate SE bracket from seeds
|
||||
- [x] Generate DE bracket from seeds
|
||||
|
||||
145
app/components/tournament/EliminationBracket.tsx
Normal file
145
app/components/tournament/EliminationBracket.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import classNames from "classnames";
|
||||
|
||||
const rounds = [
|
||||
{
|
||||
title: "Round 1",
|
||||
bestOf: 3,
|
||||
status: "DONE",
|
||||
},
|
||||
{
|
||||
title: "Round 2",
|
||||
bestOf: 5,
|
||||
status: "INPROGRESS",
|
||||
},
|
||||
{
|
||||
title: "Semifinals",
|
||||
bestOf: 5,
|
||||
status: "UPCOMING",
|
||||
},
|
||||
{
|
||||
title: "Finals",
|
||||
bestOf: 7,
|
||||
status: "UPCOMING",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export function EliminationBracket() {
|
||||
return (
|
||||
<div
|
||||
className="tournament-bracket__elim__container"
|
||||
style={
|
||||
{
|
||||
"--brackets-columns": rounds.length,
|
||||
"--brackets-matches": 4,
|
||||
} as any
|
||||
}
|
||||
>
|
||||
<div className="tournament-bracket__elim__bracket">
|
||||
{rounds.map((round, i) => (
|
||||
<RoundInfo
|
||||
key={round.title}
|
||||
round={round}
|
||||
isLast={i === rounds.length - 1}
|
||||
/>
|
||||
))}
|
||||
<div
|
||||
className="tournament-bracket__elim__column"
|
||||
style={{ "--brackets-column-matches": 4 } as any}
|
||||
>
|
||||
<div className="tournament-bracket__elim__matches">
|
||||
<Match />
|
||||
<Match />
|
||||
<Match />
|
||||
<Match />
|
||||
</div>
|
||||
<div className="tournament-bracket__elim__lines">
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="tournament-bracket__elim__column"
|
||||
style={{ "--brackets-column-matches": 2 } as any}
|
||||
>
|
||||
<div className="tournament-bracket__elim__matches">
|
||||
<Match />
|
||||
<Match />
|
||||
</div>
|
||||
<div className="tournament-bracket__elim__lines">
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="tournament-bracket__elim__column"
|
||||
style={
|
||||
{
|
||||
"--brackets-column-matches": 0,
|
||||
"--brackets-bottom-border-length": 0,
|
||||
} as any
|
||||
}
|
||||
>
|
||||
<div className="tournament-bracket__elim__matches">
|
||||
<Match />
|
||||
</div>
|
||||
<div className="tournament-bracket__elim__lines">
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
<div className="tournament-bracket__elim__matches">
|
||||
<Match />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RoundInfo(props: {
|
||||
round: {
|
||||
title: string;
|
||||
bestOf: number;
|
||||
status: "DONE" | "INPROGRESS" | "UPCOMING";
|
||||
};
|
||||
isLast?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={classNames("tournament-bracket__elim__roundInfo", {
|
||||
highlighted: props.round.status === "INPROGRESS",
|
||||
last: props.isLast,
|
||||
})}
|
||||
>
|
||||
<div className="tournament-bracket__elim__roundTitle">
|
||||
{props.round.title}
|
||||
</div>
|
||||
{props.round.status !== "DONE" && (
|
||||
<div className="tournament-bracket__elim__bestOf">
|
||||
Bo{props.round.bestOf}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Match() {
|
||||
return (
|
||||
<div className="tournament-bracket__elim__match">
|
||||
<div className="tournament-bracket__elim__roundNumber">1</div>
|
||||
<div
|
||||
className={classNames(
|
||||
"tournament-bracket__elim__team",
|
||||
"tournament-bracket__elim__teamOne"
|
||||
)}
|
||||
>
|
||||
Team Olive <span className="tournament-bracket__elim__score">1</span>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
"tournament-bracket__elim__team",
|
||||
"tournament-bracket__elim__teamTwo"
|
||||
)}
|
||||
>
|
||||
Chimera <span className="tournament-bracket__elim__score">1</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
UseTournamentRoundsState,
|
||||
} from "./types";
|
||||
|
||||
// TODO: could save this to local storage but need to handle amount of rounds changing
|
||||
export function useTournamentRounds(args: UseTournamentRoundsArgs) {
|
||||
return React.useReducer(
|
||||
(oldState: UseTournamentRoundsState, action: UseTournamentRoundsAction) => {
|
||||
|
||||
15
app/routes/to/$organization.$tournament/bracket.$id.tsx
Normal file
15
app/routes/to/$organization.$tournament/bracket.$id.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { LinksFunction } from "remix";
|
||||
import { EliminationBracket } from "~/components/tournament/EliminationBracket";
|
||||
import styles from "~/styles/tournament-bracket.css";
|
||||
|
||||
export const links: LinksFunction = () => {
|
||||
return [{ rel: "stylesheet", href: styles }];
|
||||
};
|
||||
|
||||
export default function BracketTabWrapper() {
|
||||
return (
|
||||
<div className="tournament-bracket__container">
|
||||
<EliminationBracket />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
120
app/styles/tournament-bracket.css
Normal file
120
app/styles/tournament-bracket.css
Normal file
@@ -0,0 +1,120 @@
|
||||
.tournament-bracket__container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__bracket {
|
||||
display: grid;
|
||||
column-gap: 2px;
|
||||
grid-template-columns: repeat(var(--brackets-columns), 250px);
|
||||
overflow-x: auto;
|
||||
|
||||
--brackets-match-height: 100px;
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__roundInfo {
|
||||
padding: 1.5rem 1rem;
|
||||
background-color: var(--bg-lighter);
|
||||
|
||||
/* TODO: transition: background-color 0.5s; */
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__roundInfo:first-of-type {
|
||||
border-radius: var(--rounded) 0 0 var(--rounded);
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__roundInfo.highlighted {
|
||||
background-color: var(--theme);
|
||||
color: var(--button-text);
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__roundInfo.last {
|
||||
border-radius: 0 var(--rounded) var(--rounded) 0;
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__roundTitle {
|
||||
font-size: var(--fonts-sm);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__bestOf {
|
||||
font-size: var(--fonts-xs);
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__match {
|
||||
display: grid;
|
||||
margin: 1rem 0;
|
||||
column-gap: 5px;
|
||||
grid-template-areas:
|
||||
"round-number team-one"
|
||||
"round-number team-two";
|
||||
grid-template-columns: 10px 1fr;
|
||||
place-items: center;
|
||||
row-gap: 2px;
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__roundNumber {
|
||||
font-size: var(--fonts-xs);
|
||||
grid-area: round-number;
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__team {
|
||||
display: flex;
|
||||
width: 200px;
|
||||
justify-content: space-between;
|
||||
padding: 0.25rem 0.75rem;
|
||||
background-color: var(--bg-lighter);
|
||||
font-size: var(--fonts-sm);
|
||||
place-self: flex-start;
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__teamOne {
|
||||
border-radius: var(--rounded) var(--rounded) 0 0;
|
||||
grid-area: team-one;
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__teamTwo {
|
||||
border-radius: 0 0 var(--rounded) var(--rounded);
|
||||
grid-area: team-two;
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__score {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__column {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__matches {
|
||||
display: flex;
|
||||
height: calc(var(--brackets-match-height) * var(--brackets-matches));
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__lines {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
padding-left: 7px;
|
||||
}
|
||||
|
||||
.tournament-bracket__elim__lines > div {
|
||||
width: calc(var(--brackets-match-height) / 4);
|
||||
height: calc(
|
||||
2px + var(--brackets-match-height) *
|
||||
(var(--brackets-matches) / var(--brackets-column-matches))
|
||||
);
|
||||
border-top: 2px solid var(--theme-transparent);
|
||||
border-bottom: var(--brackets-bottom-border-length, 2px) solid
|
||||
var(--theme-transparent);
|
||||
border-inline-end: 2px solid var(--theme-transparent);
|
||||
border-radius: 0 var(--rounded) var(--rounded) 0;
|
||||
}
|
||||
Reference in New Issue
Block a user