# Finals Board

> Finals day on one board: two semi-finals feed the cup final and the bronze final, each pitch's matches sit on a shared timeline, and a hold moves the later matches on the final's pitch.

- Name: `finals-board`
- Status: beta
- Categories: sport, display
- File: the install writes `finals-board.tsx` to your `ui` alias, `@/components/ui` by default.
- npm packages: `@base-ui/react`, `lucide-react`. The install adds them.
- Other registry items: `@arbokui/avatar`, `@arbokui/card`, `@arbokui/motion-timing`, `@arbokui/switch`, `@arbokui/team-flag`, `@arbokui/tool-button`. The install adds them.
- Gallery: https://arbokui.com/components/finals-board
- Registry item: https://arbokui.com/r/finals-board.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/finals-board
   ```

## Use it for

- Running a cup's finals day: record each winner as it happens, and hold the final's pitch when it runs late.
- Pass the four sides as the semi-finals pair them, and where and when each match is played. The slot and the hold's length come from the competition.
- Leave out schedule.bronze when there is no bronze final.
- Give each team its kit colors. A team without them shows its initials.
- Pass value and onChange to keep results in your data, or defaultValue to let the board keep its own. The same goes for held, onHold, and defaultHeld.

## Avoid it for

- A draw bigger than four. Use KnockoutBracket, which takes any number of rounds.
- Showing a past finals day. Nothing here should be pressed then: use KnockoutBracket or MatchCard.
- A league day. Nothing feeds anything, so there is no bracket to fill in.

## Props

- `semis`: `[[FinalsTeam, FinalsTeam], [FinalsTeam, FinalsTeam]]`. The four sides, as the semi-finals pair them. Each is { id, name, colors?, pattern? }.
- `schedule`: `{ sf1, sf2, final, bronze? }`. Each match's { pitch, kickoff, title? }, with kickoff like "13:00". Without bronze, there is no bronze final.
- `minutes`: `number`. Default `40`. How long each match holds its pitch: the competition's slot.
- `value`: `FinalsResults`. Winners by match, as team ids, controlled. Like { sf1: "rovers", final: null }.
- `defaultValue`: `FinalsResults`. Winners at the start, when the board keeps its own.
- `onChange`: `(results: FinalsResults) => void`. Hears every result, and the old results back on ⌘Z.
- `held`: `boolean`. Whether the final's pitch is held now, controlled.
- `defaultHeld`: `boolean`. Default `false`. Whether it starts held, when the board keeps its own.
- `onHold`: `(held: boolean) => void`. Hears the hold switch.
- `holdMinutes`: `number`. Default `15`. How far a hold moves the matches on the final's pitch that have no result when it starts. A moved match keeps its new kickoff once recorded.
- `className`: `string`. Extra classes. Later classes win.

## Accessibility

- Cup bracket and Day control are groups named by their titles.
- Each match's two teams are a radio group, named by the match and described by its pitch and kickoff. Tab reaches the group, the arrow keys move between the teams, and the chosen team is the winner.
- A recorded match also names its winner in text, like "Rovers won.", which the group's description reads with its pitch and kickoff.
- A final waits as a plain group that names who it waits for, like "Semi-final 1 winner", until both semi-finals are in.
- One live line says each result and each hold once, like "Semi-final 1: Rovers won. Cup final and Bronze final cleared." ⌘Z says the undo in the page's own region.
- ⌘Z works with focus on a team, since the teams are not text fields.
- A held match's new kickoff has "+15 min" beside it, in words as well as amber, so it shows in forced colors.
- The timeline is a named region with a list per pitch. It takes a Tab stop only when it scrolls sideways.
- The champion types itself in only when the final is recorded on the page, never on load. Screen readers hear the name whole. Under reduced motion or with motion off, it shows at once.

## Examples

### Four teams on two pitches, with a bronze final

```tsx
<FinalsBoard
  semis={[[rovers, lions], [hawks, wolves]]}
  schedule={{
    sf1: { pitch: "Pitch 1", kickoff: "13:00" },
    sf2: { pitch: "Pitch 2", kickoff: "13:00" },
    final: { pitch: "Pitch 1", kickoff: "14:30" },
    bronze: { pitch: "Pitch 2", kickoff: "14:00" },
  }}
/>
```

### Results in your data

```tsx
const [results, setResults] = useState<FinalsResults>({});
const [held, setHeld] = useState(false);

<FinalsBoard semis={semis} schedule={schedule} value={results} onChange={setResults} held={held} onHold={setHeld} />
```
