Security News
Opengrep Emerges as Open Source Alternative Amid Semgrep Licensing Controversy
Opengrep forks Semgrep to preserve open source SAST in response to controversial licensing changes.
@g-loot/react-tournament-brackets
Advanced tools
A react component to visualize bracket leaderboards
A straightforward implementation of single eliminations and double eliminations brackets in react
Explore the storybook »
View Live Demo
·
Report Bug
·
Request Feature
<li><a href="#acknowledgements">Acknowledgements</a></li>
I was scouring the world wide web for a good component library for visualizing single elimination brackets or double elimination brackets but most of them had complicated data structures or didn't allow for easy styling, and so I had to build my own, and decided to share it with the world.
You only need to have react installed in your project to use this project.
Note: default browser css is reset using the minireset.css package in the storybook demos. To fully control what the match component looks like you can build and supply your own view component for it
This project is hosted on the public npm registry, here's the link to the npm page
npm install @g-loot/react-tournament-brackets
import { SingleEliminationBracket, DoubleEliminationBracket, Match, MATCH_STATES, SVGViewer } from '@g-loot/react-tournament-brackets';
Component | Description |
---|---|
SingleEliminationBracket | Component for displaying single elimination bracket |
DoubleEliminationBracket | Component for displaying double elimination bracket |
Match | Default component for rendering matches that can be overridden |
MATCH_STATES | Constant containing enum for Match states and Participants statuses |
SVGViewer | Optional component for displaying the bracket in a fixed size window with panning and zooming functionality |
This component generates an SVG of all your bracket matches, you can use the supplied optional component <SVGViewer />
like in the following example to wrap the SVG in a fixed size window with panning and zooming functionality, Note that you're also free to come up with your own solution for allowing the user to navigate giant brackets with ease.
import { SingleEliminationBracket, DoubleEliminationBracket, Match, SVGViewer } from '@g-loot/react-tournament-brackets';
export const DoubleElimination = () => (
<DoubleEliminationBracket
matches={matches}
matchComponent={Match}
svgWrapper={({ children, ...props }) => (
<SVGViewer width={500} height={500} {...props}>
{children}
</SVGViewer>
)}
/>
);
export const SingleElimination = () => (
<SingleEliminationBracket
matches={matches}
matchComponent={Match}
svgWrapper={({ children, ...props }) => (
<SVGViewer width={500} height={500} {...props}>
{children}
</SVGViewer>
)}
/>
);
SVGViewer
to fit it's container you will need some sort of hook to achieve that, like useWindowSize(), useComponentSize or your own custom solutionimport { DoubleEliminationBracket, Match, SVGViewer } from '@g-loot/react-tournament-brackets';
export const DoubleElimination = () => {
const [width, height] = useWindowSize();
const finalWidth = Math.max(width - 50, 500);
const finalHeight = Math.max(height - 100, 500);
return (
<DoubleEliminationBracket
matches={matches}
matchComponent={Match}
svgWrapper={({ children, ...props }) => (
<SVGViewer width={finalWidth} height={finalHeight} {...props}>
{children}
</SVGViewer>
)}
/>
);
};
For more examples, please refer to the [Storybook][demo-url]
matches
prop structure[
...,
{
"id": 260005,
"name": "Final - Match",
"nextMatchId": null, // Id for the nextMatch in the bracket, if it's final match it must be null OR undefined
"tournamentRoundText": "4", // Text for Round Header
"startTime": "2021-05-30",
"state": "DONE", // 'NO_SHOW' | 'WALK_OVER' | 'NO_PARTY' | 'DONE' | 'SCORE_DONE' Only needed to decide walkovers and if teamNames are TBD (to be decided)
"participants": [
{
"id": "c016cb2a-fdd9-4c40-a81f-0cc6bdf4b9cc", // Unique identifier of any kind
"resultText": "WON", // Any string works
"isWinner": false,
"status": null, // 'PLAYED' | 'NO_SHOW' | 'WALK_OVER' | 'NO_PARTY' | null
"name": "giacomo123"
},
{
"id": "9ea9ce1a-4794-4553-856c-9a3620c0531b",
"resultText": null,
"isWinner": true,
"status": null, // 'PLAYED' | 'NO_SHOW' | 'WALK_OVER' | 'NO_PARTY'
"name": "Ant"
}
]
}
...
]
matches
prop structure{
"upper": [
...,
{
"id": 260006, // Unique identifier of any kind
"name": "Semi Final - Match",
"nextMatchId": null, // Id for the next match in upper bracket, if it's final match it must be null OR undefined
"nextLooserMatchId": null, // Id for the next match in lower bracket, if it's final match or a lower bracket match it must be null OR undefined
"startTime": "2021-05-30",
"state": "DONE", // 'NO_SHOW' | 'WALK_OVER' | 'NO_PARTY' | 'DONE' | 'SCORE_DONE' Only needed to decide walkovers and if teamNames are TBD (to be decided)
"participants": [
{
"id": "c016cb2a-fdd9-4c40-a81f-0cc6bdf4b9cc", // Unique identifier of any kind
"resultText": "WON", // Any string works
"isWinner": false,
"status": null, // 'PLAYED' | 'NO_SHOW' | 'WALK_OVER' | 'NO_PARTY' | null
"name": "giacomo123"
},
{
"id": "9ea9ce1a-4794-4553-856c-9a3620c0531b",
"resultText": "LOST", // Any string works
"isWinner": true,
"status": null, // 'PLAYED' | 'NO_SHOW' | 'WALK_OVER' | 'NO_PARTY'
"name": "Ant"
}
]
}
...
],
"lower": [
...,
{
"id": 260005,
"name": "Semi Final - Match",
"nextMatchId": 260006,
"nextLooserMatchId": null,
"tournamentRoundText": "4",
"startTime": "2021-05-30",
"state": "DONE", // 'NO_SHOW' | 'WALK_OVER' | 'NO_PARTY' | 'DONE' | 'SCORE_DONE' Only needed to decide walkovers and if teamNames are TBD (to be decided)
"participants": [
{
"id": "c016cb2a-fdd9-4c40-a81f-0cc6bdf4b9cc", // Unique identifier of any kind
"resultText": "WON", // Any string works
"isWinner": false,
"status": null, // 'PLAYED' | 'NO_SHOW' | 'WALK_OVER' | 'NO_PARTY' | null
"name": "giacomo123"
},
{
"id": "9ea9ce1a-4794-4553-856c-9a3620c0531b",
"resultText": null,
"isWinner": true,
"status": null, // 'PLAYED' | 'NO_SHOW' | 'WALK_OVER' | 'NO_PARTY'
"name": "Ant"
}
]
}
...
]
}
matches
prop structure with double finals, Keep the same structure and add a nextMatchId + nextLooserMatchId pointing to a new match in the same bracket (the finals can be in upper or in lower bracket) _For more examples of valid double finals data, check out the mock data folder specifically data-double-last-game-lower.ts and data-double-last-game-upper-double-playoffs.ts _{
[upper|lower]: [
...,
{
"id": "WB R5 M1",
"name": "WB R5 M1",
"nextMatchId": "WB R6 M1",
"nextLooserMatchId": "WB R6 M1",
"startTime": null,
"tournamentRound": "R5",
"state": "SCORE_DONE",
"participants": [
{
"id": "ddfee063-adde-4192-95d2-203eb2ebb8f7",
"resultText": "",
"isWinner": false,
"status": "PLAYED",
"name": "#1"
}
]
},
{
"id": "WB R6 M1",
"name": "WB R6 M1",
"nextMatchId": null,
"nextLooserMatchId": null,
"startTime": null,
"tournamentRound": "R6",
"state": "SCORE_DONE",
"participants": []
}
...,
]
}
MATCH_STATES
import { MATCH_STATES } from '@g-loot/react-tournament-brackets';
console.log(MATCH_STATES);
// {
// PLAYED: 'PLAYED',
// NO_SHOW: 'NO_SHOW',
// WALK_OVER: 'WALK_OVER',
// NO_PARTY: 'NO_PARTY',
// DONE: 'DONE',
// SCORE_DONE: 'SCORE_DONE',
// };
For more examples of accepted data, check out the mock data folder
This component's default theme is the dark theme in the screenshot, you can use the function createTheme
which is exported from the library to create a theme and then pass it to either single or double bracket on the theme
prop
A few notes:
options
prop manually for now, In the very near future they will be tied to the theme as well!import { SingleEliminationBracket, Match, SVGViewer, createTheme } from '@g-loot/react-tournament-brackets';
const WhiteTheme = createTheme({
textColor: { main: '#000000', highlighted: '#07090D', dark: '#3E414D' },
matchBackground: { wonColor: '#daebf9', lostColor: '#96c6da' },
score: {
background: { wonColor: '#87b2c4', lostColor: '#87b2c4' },
text: { highlightedWonColor: '#7BF59D', highlightedLostColor: '#FB7E94' },
},
border: {
color: '#CED1F2',
highlightedColor: '#da96c6',
},
roundHeader: { backgroundColor: '#da96c6', fontColor: '#000' },
connectorColor: '#CED1F2',
connectorColorHighlight: '#da96c6',
svgBackground: '#FAFAFA',
});
export const WhiteThemeBracket = () => {
const [width, height] = useWindowSize();
const finalWidth = Math.max(width - 50, 500);
const finalHeight = Math.max(height - 100, 500);
return (
<SingleEliminationBracket
matches={simpleSmallBracket}
matchComponent={Match}
theme={WhiteTheme}
options={{
style: {
roundHeader: {
backgroundColor: WhiteTheme.roundHeader.backgroundColor,
fontColor: WhiteTheme.roundHeader.fontColor,
},
connectorColor: WhiteTheme.connectorColor,
connectorColorHighlight: WhiteTheme.connectorColorHighlight,
},
}}
svgWrapper={({ children, ...props }) => (
<SvgViewer
background={WhiteTheme.svgBackground}
SVGBackground={WhiteTheme.svgBackground}
width={finalWidth}
height={finalHeight}
{...props}
>
{children}
</SvgViewer>
)}
/>
);
};
options
propimport { SingleEliminationBracket, SVGViewer } from '@g-loot/react-tournament-brackets';
export const CustomMatchBracket = () => {
const [width, height] = useWindowSize();
const finalWidth = Math.max(width - 50, 500);
const finalHeight = Math.max(height - 100, 500);
return (
<SingleEliminationBracket
matches={simpleSmallBracket}
options={{
style: {
roundHeader: { backgroundColor: '#AAA' },
connectorColor: '#FF8C00',
connectorColorHighlight: '#000',
},
}}
svgWrapper={({ children, ...props }) => (
<SvgViewer
background="#FFF"
SVGBackground="#FFF"
width={finalWidth}
height={finalHeight}
{...props}
>
{children}
</SvgViewer>
)}
matchComponent={({
match,
onMatchClick,
onPartyClick,
onMouseEnter,
onMouseLeave,
topParty,
bottomParty,
topWon,
bottomWon,
topHovered,
bottomHovered,
topText,
bottomText,
connectorColor,
computedStyles,
teamNameFallback,
resultFallback,
}) => (
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-around',
color: '#000',
width: '100%',
height: '100%',
}}
>
<div
onMouseEnter={() => onMouseEnter(topParty.id)}
style={{ display: 'flex' }}
>
<div>{topParty.name || teamNameFallback}</div>
<div>{topParty.resultText ?? resultFallback(topParty)}</div>
</div>
<div
style={{ height: '1px', width: '100%', background: '#FF8C00' }}
/>
<div
onMouseEnter={() => onMouseEnter(bottomParty.id)}
style={{ display: 'flex' }}
>
<div>{bottomParty.name || teamNameFallback}</div>
<div>{bottomParty.resultText ?? resultFallback(topParty)}</div>
</div>
</div>
)}
/>
);
};
For more examples, checkout the [live storybook][demo-url]
Distributed under the GNU LGPL v2.1 License. See LICENSE
for more information.
Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated.
git checkout -b feature/AmazingFeature
)npm run dev
git commit -m 'Add some AmazingFeature'
)git push origin feature/AmazingFeature
)npm run test
npm run lint
npm run build-storybook
to check that it builds the storybook demo website correctlynpm run test-lib-build
for testing that the package builds and packs into an npm package correctlyFAQs
A react component to visualize bracket leaderboards
The npm package @g-loot/react-tournament-brackets receives a total of 577 weekly downloads. As such, @g-loot/react-tournament-brackets popularity was classified as not popular.
We found that @g-loot/react-tournament-brackets demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Opengrep forks Semgrep to preserve open source SAST in response to controversial licensing changes.
Security News
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
Security News
cURL and Go security teams are publicly rejecting CVSS as flawed for assessing vulnerabilities and are calling for more accurate, context-aware approaches.