9a31e9f4db
- Shared domain model (types, team metadata/flags/aliases, FIFA-tiebreak standings) - buildFixtures.ts: openfootball 2026 → normalized fixtures.json (104 matches, ISO-UTC kickoffs, pretty knockout placeholders) - Fastify live layer: seed load, football-data.org poller + SofaScore enhancement (feature-flagged, fallback), dynamic live-window polling, WS snapshot push, REST /api/snapshot, dev /api/dev/score injection - Client: tournament store (REST + WS + static-file fallback), MatchCard, TeamLabel, GroupTable, Live/Groups/Bracket pages, live connection indicator - Verified: WS pushes a dev-injected result; standings recompute live in-browser Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
40 lines
979 B
TypeScript
40 lines
979 B
TypeScript
import { teamFlag } from '@/lib/teams';
|
|
import { cn } from '@/lib/cn';
|
|
import type { TeamSlot } from '@/lib/types';
|
|
|
|
/** A team identity (flag + name) or, for an unresolved knockout slot, a muted
|
|
* placeholder label ("Runner-up Group A"). */
|
|
export function TeamLabel({
|
|
slot,
|
|
align = 'left',
|
|
className,
|
|
}: {
|
|
slot: TeamSlot;
|
|
align?: 'left' | 'right';
|
|
className?: string;
|
|
}) {
|
|
const right = align === 'right';
|
|
if (!slot.team) {
|
|
return (
|
|
<span
|
|
className={cn('truncate text-sm italic text-faint', right && 'text-right', className)}
|
|
title={slot.label}
|
|
>
|
|
{slot.label}
|
|
</span>
|
|
);
|
|
}
|
|
return (
|
|
<span
|
|
className={cn(
|
|
'flex min-w-0 items-center gap-2',
|
|
right && 'flex-row-reverse',
|
|
className,
|
|
)}
|
|
>
|
|
<span aria-hidden className="text-lg leading-none">{teamFlag(slot.team)}</span>
|
|
<span className="truncate font-medium text-ink">{slot.team}</span>
|
|
</span>
|
|
);
|
|
}
|