diff --git a/apps/website/components/leaderboards/DriverLeaderboardPreview.tsx b/apps/website/components/leaderboards/DriverLeaderboardPreview.tsx index 248ff0966..ae5ae214a 100644 --- a/apps/website/components/leaderboards/DriverLeaderboardPreview.tsx +++ b/apps/website/components/leaderboards/DriverLeaderboardPreview.tsx @@ -1,14 +1,17 @@ import { RankBadge } from '@/components/leaderboards/RankBadge'; import { RatingDisplay } from '@/lib/display-objects/RatingDisplay'; import { SkillLevelDisplay } from '@/lib/display-objects/SkillLevelDisplay'; -import { Box } from '@/ui/Box'; -import { Image } from '@/ui/Image'; +import { Avatar } from '@/ui/Avatar'; import { LeaderboardList } from '@/ui/LeaderboardList'; import { LeaderboardPreviewShell } from '@/ui/LeaderboardPreviewShell'; +import { LeaderboardRow } from '@/ui/LeaderboardRow'; +import { Group } from '@/ui/Group'; import { Text } from '@/ui/Text'; import { Trophy } from 'lucide-react'; interface DriverLeaderboardPreviewProps { + title?: string; + subtitle?: string; drivers: { id: string; name: string; @@ -24,16 +27,22 @@ interface DriverLeaderboardPreviewProps { onNavigateToDrivers: () => void; } -export function DriverLeaderboardPreview({ drivers, onDriverClick, onNavigateToDrivers }: DriverLeaderboardPreviewProps) { +export function DriverLeaderboardPreview({ + title = "Driver Rankings", + subtitle = "Top Performers", + drivers, + onDriverClick, + onNavigateToDrivers +}: DriverLeaderboardPreviewProps) { const top10 = drivers; // Already sliced in builder return ( @@ -42,71 +51,52 @@ export function DriverLeaderboardPreview({ drivers, onDriverClick, onNavigateToD const position = index + 1; return ( - onDriverClick(driver.id)} - display="flex" - alignItems="center" - gap={4} - px={5} - py={3} - w="full" - textAlign="left" - transition - hoverBg="bg-white/[0.02]" - group - > - - - - - - {driver.name} - - - - - {driver.name} - - - {driver.nationality} - - - {SkillLevelDisplay.getLabel(driver.skillLevel)} - - - - - - - {RatingDisplay.format(driver.rating)} - Rating - - - {driver.wins} - Wins - - - + rank={} + identity={ + + + + + {driver.name} + + + {driver.nationality} + + {SkillLevelDisplay.getLabel(driver.skillLevel)} + + + + + } + stats={ + + + + {RatingDisplay.format(driver.rating)} + + + Rating + + + + + {driver.wins} + + + Wins + + + + } + /> ); })} diff --git a/apps/website/components/leaderboards/LeaderboardFiltersBar.tsx b/apps/website/components/leaderboards/LeaderboardFiltersBar.tsx index 4f9bbc26a..18c3ce36d 100644 --- a/apps/website/components/leaderboards/LeaderboardFiltersBar.tsx +++ b/apps/website/components/leaderboards/LeaderboardFiltersBar.tsx @@ -2,7 +2,6 @@ import { Icon } from '@/ui/Icon'; import { Input } from '@/ui/Input'; import { ControlBar } from '@/ui/ControlBar'; import { Button } from '@/ui/Button'; -import { Box } from '@/ui/Box'; import { Group } from '@/ui/Group'; import { Filter, Search } from 'lucide-react'; import React from 'react'; @@ -21,32 +20,30 @@ export function LeaderboardFiltersBar({ children, }: LeaderboardFiltersBarProps) { return ( - - - onSearchChange?.(e.target.value)} - placeholder={placeholder} - icon={} - fullWidth - /> - - } - > - - {children} - + + onSearchChange?.(e.target.value)} + placeholder={placeholder} + icon={} + fullWidth + /> - - + } + > + + {children} + + + ); } diff --git a/apps/website/components/leaderboards/LeaderboardHeader.tsx b/apps/website/components/leaderboards/LeaderboardHeader.tsx index e1c974987..2c5893287 100644 --- a/apps/website/components/leaderboards/LeaderboardHeader.tsx +++ b/apps/website/components/leaderboards/LeaderboardHeader.tsx @@ -1,7 +1,7 @@ import { Button } from '@/ui/Button'; import { Heading } from '@/ui/Heading'; import { Icon } from '@/ui/Icon'; -import { Stack } from '@/ui/Stack'; +import { Group } from '@/ui/Group'; import { Text } from '@/ui/Text'; import { ArrowLeft, LucideIcon } from 'lucide-react'; import React from 'react'; @@ -24,9 +24,9 @@ export function LeaderboardHeader({ children, }: LeaderboardHeaderProps) { return ( - + {onBack && ( - + - + )} - - + + {icon && ( - - - + + )} - - {title} + + {title} {description && ( - + {description} )} - - - + + + {children} - - - + + + ); } diff --git a/apps/website/components/leaderboards/LeaderboardHeaderPanel.tsx b/apps/website/components/leaderboards/LeaderboardHeaderPanel.tsx index 70fdfe0e9..1311e17e1 100644 --- a/apps/website/components/leaderboards/LeaderboardHeaderPanel.tsx +++ b/apps/website/components/leaderboards/LeaderboardHeaderPanel.tsx @@ -1,8 +1,7 @@ import { Button } from '@/ui/Button'; import { Heading } from '@/ui/Heading'; import { Icon } from '@/ui/Icon'; -import { Stack } from '@/ui/Stack'; -import { Surface } from '@/ui/Surface'; +import { Group } from '@/ui/Group'; import { Text } from '@/ui/Text'; import { ArrowLeft, LucideIcon } from 'lucide-react'; import React from 'react'; @@ -25,9 +24,9 @@ export function LeaderboardHeaderPanel({ children, }: LeaderboardHeaderPanelProps) { return ( - + {onBack && ( - + - + )} - - + + {icon && ( - - - + + )} - + {title} {description && ( - + {description} )} - - + + {children} - - + + ); } diff --git a/apps/website/components/leaderboards/LeaderboardTable.tsx b/apps/website/components/leaderboards/LeaderboardTable.tsx index 8e6e8f60f..f871636a6 100644 --- a/apps/website/components/leaderboards/LeaderboardTable.tsx +++ b/apps/website/components/leaderboards/LeaderboardTable.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Table, TableBody, TableHead, TableHeader, TableRow } from '@/ui/Table'; import { RankingRow } from './RankingRow'; +import { LeaderboardList } from '@/ui/LeaderboardList'; import { LeaderboardTableShell } from '@/ui/LeaderboardTableShell'; interface LeaderboardDriver { @@ -22,23 +22,17 @@ interface LeaderboardTableProps { } export function LeaderboardTable({ drivers, onDriverClick }: LeaderboardTableProps) { - const columns = [ - { key: 'rank', label: 'Rank', width: '8rem' }, - { key: 'driver', label: 'Driver' }, - { key: 'races', label: 'Races', align: 'center' as const }, - { key: 'rating', label: 'Rating', align: 'center' as const }, - { key: 'wins', label: 'Wins', align: 'center' as const }, - ]; - return ( - - {drivers.map((driver) => ( - onDriverClick?.(driver.id)} - /> - ))} + + + {drivers.map((driver) => ( + onDriverClick?.(driver.id)} + /> + ))} + ); } diff --git a/apps/website/components/leaderboards/MedalBadge.tsx b/apps/website/components/leaderboards/MedalBadge.tsx index e01758c96..7761d068b 100644 --- a/apps/website/components/leaderboards/MedalBadge.tsx +++ b/apps/website/components/leaderboards/MedalBadge.tsx @@ -1,38 +1,10 @@ -import { Icon } from '@/ui/Icon'; -import { Stack } from '@/ui/Stack'; -import { Text } from '@/ui/Text'; -import { Crown } from 'lucide-react'; +import { RankMedal } from '@/ui/RankMedal'; +import React from 'react'; interface MedalBadgeProps { position: number; } export function MedalBadge({ position }: MedalBadgeProps) { - const getMedalColor = (pos: number) => { - switch (pos) { - case 1: return 'var(--warning-amber)'; - case 2: return 'var(--iron-gray)'; - case 3: return 'var(--amber-600)'; - default: return 'var(--charcoal-outline)'; - } - }; - - const isMedal = position <= 3; - - return ( - - {isMedal ? ( - - ) : ( - #{position} - )} - - ); + return ; } diff --git a/apps/website/components/leaderboards/RankBadge.tsx b/apps/website/components/leaderboards/RankBadge.tsx index 1a01c61bd..578f19d26 100644 --- a/apps/website/components/leaderboards/RankBadge.tsx +++ b/apps/website/components/leaderboards/RankBadge.tsx @@ -1,4 +1,4 @@ -import { Badge } from '@/ui/Badge'; +import { RankMedal } from '@/ui/RankMedal'; import { Text } from '@/ui/Text'; import { Group } from '@/ui/Group'; import React from 'react'; @@ -9,32 +9,15 @@ interface RankBadgeProps { } export function RankBadge({ rank, size = 'md' }: RankBadgeProps) { - const badgeSize = size === 'lg' ? 'md' : size; - - const getVariant = (rank: number): 'warning' | 'primary' | 'info' | 'default' => { - if (rank <= 3) return 'warning'; - if (rank <= 10) return 'primary'; - if (rank <= 50) return 'info'; - return 'default'; - }; - - const getMedalEmoji = (rank: number) => { - switch (rank) { - case 1: return '🥇'; - case 2: return '🥈'; - case 3: return '🥉'; - default: return null; - } - }; - - const medal = getMedalEmoji(rank); + if (rank <= 3) { + return ; + } return ( - - - {medal && {medal}} - #{rank} - - + + + #{rank} + + ); } diff --git a/apps/website/components/leaderboards/RankMedal.tsx b/apps/website/components/leaderboards/RankMedal.tsx index 6f285ea00..a4588a70b 100644 --- a/apps/website/components/leaderboards/RankMedal.tsx +++ b/apps/website/components/leaderboards/RankMedal.tsx @@ -1,54 +1,18 @@ import { MedalDisplay } from '@/lib/display-objects/MedalDisplay'; -import { Icon } from '@/ui/Icon'; -import { Text } from '@/ui/Text'; -import { Surface } from '@/ui/Surface'; -import { Crown, Medal } from 'lucide-react'; +import { RankMedal as UiRankMedal, RankMedalProps } from '@/ui/RankMedal'; import React from 'react'; -interface RankMedalProps { - rank: number; - size?: 'sm' | 'md' | 'lg'; - showIcon?: boolean; -} - -export function RankMedal({ rank, size = 'md', showIcon = true }: RankMedalProps) { - const isTop3 = rank <= 3; - const variant = MedalDisplay.getVariant(rank); - - const sizePx = { - sm: '1.75rem', - md: '2rem', - lg: '2.5rem', - }; - - const textSizeMap = { - sm: 'xs', - md: 'xs', - lg: 'sm', - } as const; - - const iconSize = { - sm: 3, - md: 3.5, - lg: 4.5, - }; +export function RankMedal(props: RankMedalProps) { + const variant = MedalDisplay.getVariant(props.rank); + const bg = MedalDisplay.getBg(props.rank); + const color = MedalDisplay.getColor(props.rank); return ( - - {isTop3 && showIcon ? ( - - ) : ( - {rank} - )} - + ); } diff --git a/apps/website/components/leaderboards/RankingRow.tsx b/apps/website/components/leaderboards/RankingRow.tsx index 954bb27ae..0bab065be 100644 --- a/apps/website/components/leaderboards/RankingRow.tsx +++ b/apps/website/components/leaderboards/RankingRow.tsx @@ -1,10 +1,12 @@ import { RatingDisplay } from '@/lib/display-objects/RatingDisplay'; -import { Image } from '@/ui/Image'; -import { Stack } from '@/ui/Stack'; -import { TableCell, TableRow } from '@/ui/Table'; +import { Avatar } from '@/ui/Avatar'; +import { Group } from '@/ui/Group'; import { Text } from '@/ui/Text'; import { DeltaChip } from './DeltaChip'; -import { RankMedal } from './RankMedal'; +import { RankBadge } from './RankBadge'; +import { LeaderboardRow } from '@/ui/LeaderboardRow'; +import { SkillLevelDisplay } from '@/lib/display-objects/SkillLevelDisplay'; +import React from 'react'; interface RankingRowProps { id: string; @@ -33,82 +35,69 @@ export function RankingRow({ onClick, }: RankingRowProps) { return ( - - - - - - + rank={ + + {rankDelta !== undefined && ( - - - + )} - - - - - - - {name} - - + + } + identity={ + + + {name} - - {nationality} - - {skillLevel} - - - - - - - {racesCompleted} - - - - - {RatingDisplay.format(rating)} - - - - - - {wins} - - - + + {nationality} + + {SkillLevelDisplay.getLabel(skillLevel)} + + + + + } + stats={ + + + + {racesCompleted} + + + Races + + + + + {RatingDisplay.format(rating)} + + + Rating + + + + + {wins} + + + Wins + + + + } + /> ); } diff --git a/apps/website/components/leaderboards/RankingsPodium.tsx b/apps/website/components/leaderboards/RankingsPodium.tsx index a6660e8fb..bc5853a98 100644 --- a/apps/website/components/leaderboards/RankingsPodium.tsx +++ b/apps/website/components/leaderboards/RankingsPodium.tsx @@ -1,8 +1,10 @@ - - -import { Image } from '@/ui/Image'; -import { Stack } from '@/ui/Stack'; +import { Avatar } from '@/ui/Avatar'; +import { Group } from '@/ui/Group'; import { Text } from '@/ui/Text'; +import { RatingDisplay } from '@/lib/display-objects/RatingDisplay'; +import { MedalDisplay } from '@/lib/display-objects/MedalDisplay'; +import { Surface } from '@/ui/Surface'; +import React from 'react'; interface PodiumDriver { id: string; @@ -20,121 +22,67 @@ interface RankingsPodiumProps { export function RankingsPodium({ podium, onDriverClick }: RankingsPodiumProps) { return ( - - - {[1, 0, 2].map((index) => { - const driver = podium[index]; - if (!driver) return null; - - const position = index === 1 ? 1 : index === 0 ? 2 : 3; - const config = { - 1: { height: '10rem', color: 'rgba(250, 204, 21, 0.2)', borderColor: 'rgba(250, 204, 21, 0.4)', crown: '#facc15' }, - 2: { height: '8rem', color: 'rgba(209, 213, 219, 0.2)', borderColor: 'rgba(209, 213, 219, 0.4)', crown: '#d1d5db' }, - 3: { height: '6rem', color: 'rgba(217, 119, 6, 0.2)', borderColor: 'rgba(217, 119, 6, 0.4)', crown: '#d97706' }, - }[position] || { height: '6rem', color: 'rgba(217, 119, 6, 0.2)', borderColor: 'rgba(217, 119, 6, 0.4)', crown: '#d97706' }; + + {[1, 0, 2].map((index) => { + const driver = podium[index]; + if (!driver) return null; + + const position = index === 1 ? 1 : index === 0 ? 2 : 3; + const isFirst = position === 1; + + const config = { + 1: { height: '10rem', variant: 'precision' }, + 2: { height: '8rem', variant: 'muted' }, + 3: { height: '6rem', variant: 'muted' }, + }[position as 1 | 2 | 3]; - return ( - onDriverClick?.(driver.id)} - display="flex" - flexDirection="col" - alignItems="center" - bg="transparent" - border={false} - cursor="pointer" - > - - - {driver.name} - - - {position} - - - - - {driver.name} - - - - {driver.rating.toString()} - - - - - 🏆 - {driver.wins} - - - - 🏅 - {driver.podiums} - - - - + + - - {position} - - - - ); - })} - - + + + + {driver.name} + + {RatingDisplay.format(driver.rating)} + + + + + + {position} + + + + ); + })} + ); } diff --git a/apps/website/components/leaderboards/RankingsTable.tsx b/apps/website/components/leaderboards/RankingsTable.tsx index fbc688e0c..0b5b41063 100644 --- a/apps/website/components/leaderboards/RankingsTable.tsx +++ b/apps/website/components/leaderboards/RankingsTable.tsx @@ -1,11 +1,9 @@ - - -import { Icon } from '@/ui/Icon'; -import { Image } from '@/ui/Image'; -import { Stack } from '@/ui/Stack'; +import { Group } from '@/ui/Group'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/ui/Table'; import { Text } from '@/ui/Text'; -import { Medal } from 'lucide-react'; +import { RankingRow } from './RankingRow'; +import { EmptyState } from '@/ui/EmptyState'; +import { Trophy } from 'lucide-react'; interface Driver { id: string; @@ -29,88 +27,34 @@ interface RankingsTableProps { export function RankingsTable({ drivers, onDriverClick }: RankingsTableProps) { if (drivers.length === 0) { return ( - - No drivers found - There are no drivers in the system yet - + ); } return ( - - - - - Rank - Driver - Races - Rating - Wins - - - - {drivers.map((driver) => ( - onDriverClick?.(driver.id)} - > - - - {driver.rank <= 3 ? : ( - {driver.rank} - )} - - - - - - - - - - - {driver.name} - - - {driver.nationality} - {driver.skillLevel} - - - - - - - {driver.racesCompleted} - - - - - {driver.rating.toString()} - - - - - - {driver.wins} - - - - ))} - -
-
+ + + + Rank + Driver + Races + Rating + Wins + + + + {drivers.map((driver) => ( + onDriverClick?.(driver.id)} + /> + ))} + +
); } diff --git a/apps/website/components/leaderboards/SeasonSelector.tsx b/apps/website/components/leaderboards/SeasonSelector.tsx index f03c9c581..b5afad31f 100644 --- a/apps/website/components/leaderboards/SeasonSelector.tsx +++ b/apps/website/components/leaderboards/SeasonSelector.tsx @@ -1,4 +1,4 @@ -import { Box } from '@/ui/Box'; +import { Group } from '@/ui/Group'; import { Icon } from '@/ui/Icon'; import { Select } from '@/ui/Select'; import { Text } from '@/ui/Text'; @@ -23,19 +23,18 @@ export function SeasonSelector({ seasons, selectedSeasonId, onSeasonChange }: Se })); return ( - - - - Season - - + + + + Season + +