- Two-column layout (My Leagues 2/3, Upcoming Events 1/3) with mobile stack - LeagueRow: square avatar, gradient draft highlight, rank/points display, progress bar - MyLeaguesCard, CreateLeagueCard with shared SectionCardHeader - UpcomingEventsCard: vertical timeline with grouped multi-league events - Shared gradient system: BracktGradients SVG defs, GradientIcon wrapper, brand.ts constants - Button default variant updated to green→cyan gradient - Navbar: plain nav links with gradient hover, support/admin icon buttons - Accessibility fixes: semantic h2 headings, aria-label on LeagueAvatar and nav elements - Storybook stories for all new components Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
52 lines
1.2 KiB
TypeScript
52 lines
1.2 KiB
TypeScript
const AVATAR_COLORS = [
|
|
"#adf661",
|
|
"#2ce1c1",
|
|
"#8b5cf6",
|
|
"#f59e0b",
|
|
"#ef4444",
|
|
"#3b82f6",
|
|
];
|
|
|
|
function hashLeagueId(id: string): number {
|
|
let hash = 0;
|
|
for (let i = 0; i < id.length; i++) {
|
|
hash = (hash * 31 + id.charCodeAt(i)) & 0xffff;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
interface LeagueAvatarProps {
|
|
leagueId: string;
|
|
leagueName: string;
|
|
/** Tailwind size classes, e.g. "h-10 w-10" (default) or "h-5 w-5" */
|
|
className?: string;
|
|
/** Font size class, e.g. "text-sm" (default) or "text-[10px]" */
|
|
textClassName?: string;
|
|
}
|
|
|
|
export function LeagueAvatar({
|
|
leagueId,
|
|
leagueName,
|
|
className = "h-10 w-10",
|
|
textClassName = "text-sm",
|
|
}: LeagueAvatarProps) {
|
|
const color = AVATAR_COLORS[hashLeagueId(leagueId) % AVATAR_COLORS.length];
|
|
const initials =
|
|
leagueName
|
|
.split(/\s+/)
|
|
.filter(Boolean)
|
|
.slice(0, 2)
|
|
.map((w) => w[0].toUpperCase())
|
|
.join("") || "?";
|
|
|
|
return (
|
|
<div
|
|
role="img"
|
|
aria-label={leagueName}
|
|
className={`flex shrink-0 items-center justify-center rounded-none font-bold ${className} ${textClassName}`}
|
|
style={{ backgroundColor: "#000", color }}
|
|
>
|
|
{initials}
|
|
</div>
|
|
);
|
|
}
|