feat: draw any tournament in fixtures - #132
Conversation
`Team.code` was required, so every team loaded a country flag from flagcdn.com and nothing else could be drawn. `logo` now takes any image URL and wins over `code`; a team with neither falls back to its initials, and an empty slot keeps the TBD shield. Flags stay cropped to fill their slot. A logo is fitted whole instead, since a crest cropped to a circle loses its shape. Initials use text-foreground (14.6:1 light, 11.5:1 dark; muted-foreground was under AA on both) and hold a floor of 15 viewBox units in the wheel, which is ~10px once the 32rem stage scales the 760-unit box.
A draw that isn't football shouldn't have to invent a kick-off date or
label its results "FT". `date`, `time` and `status` are now optional: a
match with a `winner` counts as finished without one, and the card
header holds its height so a dateless draw doesn't sit top-heavy inside
the fixed card height. A per-match `badge` replaces the derived chip
("AET", "BO5", "Forfeit"), and `thirdPlaceLabel` renames the play-off.
The `rounds` contract is now spelled out on the prop itself, so the API
reference states how the tree pairs up.
Every later match sits at the midpoint of its two feeders, so a round that doesn't hold exactly half the matches of the one before it read past the end of the feeder array and positioned its cards at NaN, which renders nothing. Feeder lookups now fall back to the fixed rhythm, so a malformed or partial draw degrades into a readable column instead of disappearing.
The bracket had no test at all. It now renders a three-round esports draw with logos, initials, no dates and a custom play-off label, and asserts no card lands at a NaN offset. The wheel gains a two-round, artwork-free case: it takes the smallest nodes of any draw, so it's where the initials size floor and the fixed stage width have to hold.
The descriptions repeated the same "any single-elimination draw" line three times and leaned on em dashes. Each one now says something concrete instead: the parent notes both styles read the same array of rounds, the wheel covers flags, logos and initials, the bracket lists what comes from the data. The Usage blocks show how to build a `Round[]` for a tournament that isn't the bundled World Cup sample, and the sample data headers say they're a starting shape rather than fixed content. One stale claim goes with them: the wheel holds a 32rem stage at every size, it does not size itself to the rim.
Requested by the author: they contributed the component rather than authoring the page, so the "Built by" block shouldn't name them.
|
Great work making both fixture styles reusable across different tournament data. I found three areas worth addressing before merge:
|
|
Thanks for the review @starc007 |
An unfed match in an uneven round fell back to a fixed rhythm measured from the top of the stage, which could land on top of a card already placed at a feeder midpoint — a 2 → 2 draw put two 124px cards 72px apart. Unfed matches now stack a full row under the last card placed in their own round, and the stage height is measured from the lowest visible card instead of derived from the base round's count, so a stacked fallback isn't clipped. TeamCrest and TeamMark stored a permanent `failed` boolean, so a logo that was temporarily broken stayed as initials for the life of the component even after the URL was corrected. They now hold the URL that failed and retry whenever the resolved source changes. matchLabel only announced the kick-off when a date was present, so a match carrying just a time showed it on the card and omitted it from the aria-label. It now joins the same date/time pair the card renders.
|
Thanks for flagging these issues, @starc007. I've addressed all the feedback. |
Both fixture styles were built around one World Cup draw:
Team.codewas required, so every team loaded a country flag from flagcdn.com, and matches had to carry a date, a status and a football-shaped "FT" chip. This makes both take any single-elimination tournament as data, with no change to how the sample renders.What changed
Team artwork.
logotakes any image URL and wins overcode. A team with neither falls back to its initials; an empty slot keeps the TBD shield. Flags stay cropped to fill their slot, a logo is fitted whole inside it.Optional match metadata.
date,timeandstatusare optional now: a match with awinnercounts as finished without one. A per-matchbadgereplaces the derived chip ("AET", "BO5", "Forfeit"), andthirdPlaceLabelrenames the play-off.Layout guard. A round that doesn't hold exactly half the matches of the one before it used to read past the end of the feeder array and position its cards at NaN, rendering nothing. Feeder lookups fall back to the fixed rhythm instead.
Contrast. The initials fallback uses
text-foreground: 14.6:1 light and 11.5:1 dark, against 4.44:1 and 3.55:1 fortext-muted-foregroundat 9px, which was under AA in both themes. In the wheel, initials hold a floor of 15 viewBox units, about 10px once the 32rem stage scales the 760-unit box. Below that they were rendering at 7.6px on the inner ring.Both components still take the same
roundsarray, so one dataset feeds either style.Not addressed
Depth-1 nodes in the wheel are 19px at the 32rem floor, under the 24px WCAG 2.5.8 target minimum. That predates this branch and applies to the shipping 32-team sample too. Raising
NODE_MINto ~17.8 clears it but reflows the whole wheel, so it's left out.Notes
The
creditentry on the Fixtures registry item is removed in its own commit at the contributor's request. That leavesComponentEntry.creditand the "Built by" branch inapp/components/[category]/[slug]/page.tsxunused; that branch also hardcodes "Fixtures was created by" instead of the component name, which will misname any future credit.Testing
bun run check(typecheck, biome, registry) andbun testpass. Each of the six commits typechecks and passes its tests on its own. New coverage: a bracket test with a three-round esports draw (logos, initials, no dates, custom play-off label, no NaN offsets) and a wheel case with a two-round artwork-free draw, which has the smallest nodes of any draw and is where the size floor has to hold.