website refactor
This commit is contained in:
@@ -1,16 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Trophy, ChevronRight } from 'lucide-react';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Trophy } from 'lucide-react';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { LeaderboardList } from '@/ui/LeaderboardList';
|
||||
import { LeaderboardPreviewShell } from '@/ui/LeaderboardPreviewShell';
|
||||
import { RankBadge } from '@/components/leaderboards/RankBadge';
|
||||
import { SkillLevelDisplay } from '@/lib/display-objects/SkillLevelDisplay';
|
||||
import { RatingDisplay } from '@/lib/display-objects/RatingDisplay';
|
||||
import { RankMedal } from './RankMedal';
|
||||
import { LeaderboardTableShell } from './LeaderboardTableShell';
|
||||
|
||||
interface DriverLeaderboardPreviewProps {
|
||||
drivers: {
|
||||
@@ -32,54 +28,18 @@ export function DriverLeaderboardPreview({ drivers, onDriverClick, onNavigateToD
|
||||
const top10 = drivers; // Already sliced in builder
|
||||
|
||||
return (
|
||||
<LeaderboardTableShell>
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="between"
|
||||
px={5}
|
||||
py={4}
|
||||
borderBottom
|
||||
borderColor="border-charcoal-outline/50"
|
||||
bg="bg-deep-charcoal/40"
|
||||
>
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Box
|
||||
display="flex"
|
||||
h="10"
|
||||
w="10"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
rounded="lg"
|
||||
bg="bg-gradient-to-br from-primary-blue/15 to-primary-blue/5"
|
||||
border
|
||||
borderColor="border-primary-blue/20"
|
||||
>
|
||||
<Icon icon={Trophy} size={5} color="text-primary-blue" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Heading level={3} fontSize="lg" weight="bold" color="text-white" letterSpacing="tight">Driver Rankings</Heading>
|
||||
<Text size="xs" color="text-gray-500" block uppercase letterSpacing="wider" weight="bold">Top Performers</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onNavigateToDrivers}
|
||||
size="sm"
|
||||
hoverBg="bg-primary-blue/10"
|
||||
transition
|
||||
>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Text size="sm" weight="medium">View All</Text>
|
||||
<Icon icon={ChevronRight} size={4} />
|
||||
</Stack>
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Stack gap={0}>
|
||||
<LeaderboardPreviewShell
|
||||
title="Driver Rankings"
|
||||
subtitle="Top Performers"
|
||||
onViewFull={onNavigateToDrivers}
|
||||
icon={Trophy}
|
||||
iconColor="var(--primary-blue)"
|
||||
iconBgGradient="linear-gradient(to bottom right, rgba(25, 140, 255, 0.2), rgba(25, 140, 255, 0.1))"
|
||||
viewFullLabel="View All"
|
||||
>
|
||||
<LeaderboardList>
|
||||
{top10.map((driver, index) => {
|
||||
const position = index + 1;
|
||||
const isLast = index === top10.length - 1;
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -97,11 +57,9 @@ export function DriverLeaderboardPreview({ drivers, onDriverClick, onNavigateToD
|
||||
transition
|
||||
hoverBg="bg-white/[0.02]"
|
||||
group
|
||||
borderBottom={!isLast}
|
||||
borderColor="border-charcoal-outline/30"
|
||||
>
|
||||
<Box w="8" display="flex" justifyContent="center">
|
||||
<RankMedal rank={position} size="sm" />
|
||||
<RankBadge rank={position} />
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
@@ -111,7 +69,6 @@ export function DriverLeaderboardPreview({ drivers, onDriverClick, onNavigateToD
|
||||
rounded="full"
|
||||
overflow="hidden"
|
||||
border
|
||||
borderWidth="1px"
|
||||
borderColor="border-charcoal-outline"
|
||||
groupHoverBorderColor="primary-blue/50"
|
||||
transition
|
||||
@@ -152,7 +109,7 @@ export function DriverLeaderboardPreview({ drivers, onDriverClick, onNavigateToD
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</LeaderboardTableShell>
|
||||
</LeaderboardList>
|
||||
</LeaderboardPreviewShell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Table, TableBody, TableHead, TableHeader, TableRow } from '@/ui/Table';
|
||||
import { RankingRow } from './RankingRow';
|
||||
import { LeaderboardTableShell } from './LeaderboardTableShell';
|
||||
import { LeaderboardTableShell } from '@/ui/LeaderboardTableShell';
|
||||
|
||||
interface LeaderboardDriver {
|
||||
id: string;
|
||||
@@ -22,28 +22,23 @@ 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 (
|
||||
<LeaderboardTableShell isEmpty={drivers.length === 0} emptyMessage="No drivers found">
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeader w="32">Rank</TableHeader>
|
||||
<TableHeader>Driver</TableHeader>
|
||||
<TableHeader textAlign="center">Races</TableHeader>
|
||||
<TableHeader textAlign="center">Rating</TableHeader>
|
||||
<TableHeader textAlign="center">Wins</TableHeader>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{drivers.map((driver) => (
|
||||
<RankingRow
|
||||
key={driver.id}
|
||||
{...driver}
|
||||
onClick={() => onDriverClick?.(driver.id)}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<LeaderboardTableShell columns={columns}>
|
||||
{drivers.map((driver) => (
|
||||
<RankingRow
|
||||
key={driver.id}
|
||||
{...driver}
|
||||
onClick={() => onDriverClick?.(driver.id)}
|
||||
/>
|
||||
))}
|
||||
</LeaderboardTableShell>
|
||||
);
|
||||
}
|
||||
|
||||
40
apps/website/components/leaderboards/MedalBadge.tsx
Normal file
40
apps/website/components/leaderboards/MedalBadge.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import { Crown } from 'lucide-react';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Text } from '@/ui/Text';
|
||||
|
||||
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 (
|
||||
<Box
|
||||
display="flex"
|
||||
h="10"
|
||||
w="10"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
rounded="full"
|
||||
bg={isMedal ? 'bg-gradient-to-br from-yellow-400/20 to-amber-600/10' : 'bg-iron-gray'}
|
||||
>
|
||||
{isMedal ? (
|
||||
<Icon icon={Crown} size={5} color={getMedalColor(position)} />
|
||||
) : (
|
||||
<Text size="lg" weight="bold" color="text-gray-400">#{position}</Text>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
52
apps/website/components/leaderboards/RankBadge.tsx
Normal file
52
apps/website/components/leaderboards/RankBadge.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
|
||||
interface RankBadgeProps {
|
||||
rank: number;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
showLabel?: boolean;
|
||||
}
|
||||
|
||||
export function RankBadge({ rank, size = 'md', showLabel = true }: RankBadgeProps) {
|
||||
const getMedalEmoji = (rank: number) => {
|
||||
switch (rank) {
|
||||
case 1: return '🥇';
|
||||
case 2: return '🥈';
|
||||
case 3: return '🥉';
|
||||
default: return null;
|
||||
}
|
||||
};
|
||||
|
||||
const medal = getMedalEmoji(rank);
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'text-sm px-2 py-1',
|
||||
md: 'text-base px-3 py-1.5',
|
||||
lg: 'text-lg px-4 py-2'
|
||||
};
|
||||
|
||||
const getRankColor = (rank: number) => {
|
||||
if (rank <= 3) return 'bg-warning-amber/20 text-warning-amber border-warning-amber/30';
|
||||
if (rank <= 10) return 'bg-primary-blue/20 text-primary-blue border-primary-blue/30';
|
||||
if (rank <= 50) return 'bg-purple-500/20 text-purple-400 border-purple-500/30';
|
||||
return 'bg-charcoal-outline/20 text-gray-300 border-charcoal-outline';
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
as="span"
|
||||
display="inline-flex"
|
||||
alignItems="center"
|
||||
gap={1.5}
|
||||
rounded="md"
|
||||
border
|
||||
className={`font-medium ${getRankColor(rank)} ${sizeClasses[size]}`}
|
||||
>
|
||||
{medal && <Text>{medal}</Text>}
|
||||
{showLabel && <Text>#{rank}</Text>}
|
||||
{!showLabel && !medal && <Text>#{rank}</Text>}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
141
apps/website/components/leaderboards/RankingsPodium.tsx
Normal file
141
apps/website/components/leaderboards/RankingsPodium.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
|
||||
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
|
||||
interface PodiumDriver {
|
||||
id: string;
|
||||
name: string;
|
||||
avatarUrl: string;
|
||||
rating: number;
|
||||
wins: number;
|
||||
podiums: number;
|
||||
}
|
||||
|
||||
interface RankingsPodiumProps {
|
||||
podium: PodiumDriver[];
|
||||
onDriverClick?: (id: string) => void;
|
||||
}
|
||||
|
||||
export function RankingsPodium({ podium, onDriverClick }: RankingsPodiumProps) {
|
||||
return (
|
||||
<Box mb={10}>
|
||||
<Box display="flex" alignItems="end" justifyContent="center" gap={4}>
|
||||
{[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' };
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={driver.id}
|
||||
as="button"
|
||||
type="button"
|
||||
onClick={() => onDriverClick?.(driver.id)}
|
||||
display="flex"
|
||||
flexDirection="col"
|
||||
alignItems="center"
|
||||
bg="transparent"
|
||||
border={false}
|
||||
cursor="pointer"
|
||||
>
|
||||
<Box position="relative" mb={4}>
|
||||
<Box
|
||||
position="relative"
|
||||
w={position === 1 ? '24' : '20'}
|
||||
h={position === 1 ? '24' : '20'}
|
||||
rounded="full"
|
||||
overflow="hidden"
|
||||
border
|
||||
borderColor={config.crown === '#facc15' ? 'border-warning-amber' : config.crown === '#d1d5db' ? 'border-gray-300' : 'border-orange-600'}
|
||||
style={{ borderWidth: '4px', boxShadow: position === 1 ? '0 0 30px rgba(250, 204, 21, 0.3)' : 'none' }}
|
||||
>
|
||||
<Image
|
||||
src={driver.avatarUrl}
|
||||
alt={driver.name}
|
||||
width={112}
|
||||
height={112}
|
||||
objectFit="cover"
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
position="absolute"
|
||||
bottom="-2"
|
||||
left="50%"
|
||||
w="8"
|
||||
h="8"
|
||||
rounded="full"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
border
|
||||
weight="bold"
|
||||
size="sm"
|
||||
borderColor={config.crown === '#facc15' ? 'border-warning-amber' : config.crown === '#d1d5db' ? 'border-gray-300' : 'border-orange-600'}
|
||||
color={config.crown === '#facc15' ? 'text-warning-amber' : config.crown === '#d1d5db' ? 'text-gray-300' : 'text-orange-600'}
|
||||
style={{
|
||||
transform: 'translateX(-50%)',
|
||||
borderWidth: '2px',
|
||||
background: `linear-gradient(to bottom right, ${config.color}, transparent)`
|
||||
}}
|
||||
>
|
||||
{position}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Text weight="semibold" color="text-white" size={position === 1 ? 'lg' : 'base'} mb={1} block>
|
||||
{driver.name}
|
||||
</Text>
|
||||
|
||||
<Text font="mono" weight="bold" size={position === 1 ? 'xl' : 'lg'} block color={position === 1 ? 'text-warning-amber' : 'text-primary-blue'}>
|
||||
{driver.rating.toString()}
|
||||
</Text>
|
||||
|
||||
<Stack direction="row" align="center" gap={2} mt={1}>
|
||||
<Stack direction="row" align="center" gap={1}>
|
||||
<Text>🏆</Text>
|
||||
<Text color="text-gray-400">{driver.wins}</Text>
|
||||
</Stack>
|
||||
<Text color="text-gray-500">•</Text>
|
||||
<Stack direction="row" align="center" gap={1}>
|
||||
<Text>🏅</Text>
|
||||
<Text color="text-gray-400">{driver.podiums}</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Box
|
||||
mt={4}
|
||||
w={position === 1 ? '28' : '24'}
|
||||
h={config.height}
|
||||
rounded="lg"
|
||||
display="flex"
|
||||
alignItems="end"
|
||||
justifyContent="center"
|
||||
pb={4}
|
||||
style={{
|
||||
borderRadius: '0.5rem 0.5rem 0 0',
|
||||
background: `linear-gradient(to top, ${config.color}, transparent)`,
|
||||
borderTop: `1px solid ${config.borderColor}`,
|
||||
borderLeft: `1px solid ${config.borderColor}`,
|
||||
borderRight: `1px solid ${config.borderColor}`
|
||||
}}
|
||||
>
|
||||
<Text weight="bold" size={position === 1 ? '4xl' : '3xl'} color={config.crown === '#facc15' ? 'text-warning-amber' : config.crown === '#d1d5db' ? 'text-gray-300' : 'text-orange-600'}>
|
||||
{position}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
116
apps/website/components/leaderboards/RankingsTable.tsx
Normal file
116
apps/website/components/leaderboards/RankingsTable.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
|
||||
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/ui/Table';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Medal } from 'lucide-react';
|
||||
|
||||
interface Driver {
|
||||
id: string;
|
||||
name: string;
|
||||
avatarUrl: string;
|
||||
rank: number;
|
||||
nationality: string;
|
||||
skillLevel: string;
|
||||
racesCompleted: number;
|
||||
rating: number;
|
||||
wins: number;
|
||||
medalBg?: string;
|
||||
medalColor?: string;
|
||||
}
|
||||
|
||||
interface RankingsTableProps {
|
||||
drivers: Driver[];
|
||||
onDriverClick?: (id: string) => void;
|
||||
}
|
||||
|
||||
export function RankingsTable({ drivers, onDriverClick }: RankingsTableProps) {
|
||||
if (drivers.length === 0) {
|
||||
return (
|
||||
<Box py={16} textAlign="center" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline" rounded="xl">
|
||||
<Text size="4xl" block mb={4}>🔍</Text>
|
||||
<Text color="text-gray-400" block mb={2}>No drivers found</Text>
|
||||
<Text size="sm" color="text-gray-500">There are no drivers in the system yet</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box rounded="xl" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline" overflow="hidden">
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeader className="text-center w-16">Rank</TableHeader>
|
||||
<TableHeader>Driver</TableHeader>
|
||||
<TableHeader className="text-center">Races</TableHeader>
|
||||
<TableHeader className="text-center">Rating</TableHeader>
|
||||
<TableHeader className="text-center">Wins</TableHeader>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{drivers.map((driver) => (
|
||||
<TableRow
|
||||
key={driver.id}
|
||||
clickable
|
||||
onClick={() => onDriverClick?.(driver.id)}
|
||||
>
|
||||
<TableCell className="text-center">
|
||||
<Box
|
||||
display="inline-flex"
|
||||
h="9"
|
||||
w="9"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
rounded="full"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
bg={driver.medalBg}
|
||||
color={driver.medalColor}
|
||||
className="text-sm font-bold"
|
||||
>
|
||||
{driver.rank <= 3 ? <Icon icon={Medal} size={4} /> : driver.rank}
|
||||
</Box>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Box position="relative" w="10" h="10" rounded="full" overflow="hidden" border borderColor="border-charcoal-outline" borderTop={false} borderBottom={false} borderLeft={false} borderRight={false}>
|
||||
<Image src={driver.avatarUrl} alt={driver.name} width={40} height={40} className="w-full h-full object-cover" />
|
||||
</Box>
|
||||
<Box minWidth="0">
|
||||
<Text weight="semibold" color="text-white" block truncate>
|
||||
{driver.name}
|
||||
</Text>
|
||||
<Stack direction="row" align="center" gap={2} mt={1}>
|
||||
<Text size="xs" color="text-gray-500">{driver.nationality}</Text>
|
||||
<Text size="xs" color="text-gray-500">{driver.skillLevel}</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="text-center">
|
||||
<Text color="text-gray-400">{driver.racesCompleted}</Text>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="text-center">
|
||||
<Text font="mono" weight="semibold" color="text-white">
|
||||
{driver.rating.toString()}
|
||||
</Text>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="text-center">
|
||||
<Text font="mono" weight="semibold" color="text-performance-green">
|
||||
{driver.wins}
|
||||
</Text>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Users, ChevronRight } from 'lucide-react';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Users } from 'lucide-react';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Image } from '@/ui/Image';
|
||||
import { LeaderboardList } from '@/ui/LeaderboardList';
|
||||
import { LeaderboardPreviewShell } from '@/ui/LeaderboardPreviewShell';
|
||||
import { RankBadge } from '@/components/leaderboards/RankBadge';
|
||||
import { getMediaUrl } from '@/lib/utilities/media';
|
||||
import { RankMedal } from './RankMedal';
|
||||
import { LeaderboardTableShell } from './LeaderboardTableShell';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
|
||||
interface TeamLeaderboardPreviewProps {
|
||||
teams: {
|
||||
@@ -30,54 +27,18 @@ export function TeamLeaderboardPreview({ teams, onTeamClick, onNavigateToTeams }
|
||||
const top5 = teams;
|
||||
|
||||
return (
|
||||
<LeaderboardTableShell>
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="between"
|
||||
px={5}
|
||||
py={4}
|
||||
borderBottom
|
||||
borderColor="border-charcoal-outline/50"
|
||||
bg="bg-deep-charcoal/40"
|
||||
>
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Box
|
||||
display="flex"
|
||||
h="10"
|
||||
w="10"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
rounded="lg"
|
||||
bg="bg-gradient-to-br from-purple-500/15 to-purple-500/5"
|
||||
border
|
||||
borderColor="border-purple-500/20"
|
||||
>
|
||||
<Icon icon={Users} size={5} color="text-purple-400" />
|
||||
</Box>
|
||||
<Box>
|
||||
<Heading level={3} fontSize="lg" weight="bold" color="text-white" letterSpacing="tight">Team Rankings</Heading>
|
||||
<Text size="xs" color="text-gray-500" block uppercase letterSpacing="wider" weight="bold">Top Performing Teams</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onNavigateToTeams}
|
||||
size="sm"
|
||||
hoverBg="bg-purple-500/10"
|
||||
transition
|
||||
>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Text size="sm" weight="medium">View All</Text>
|
||||
<Icon icon={ChevronRight} size={4} />
|
||||
</Stack>
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Stack gap={0}>
|
||||
{top5.map((team, index) => {
|
||||
<LeaderboardPreviewShell
|
||||
title="Team Rankings"
|
||||
subtitle="Top Performing Teams"
|
||||
onViewFull={onNavigateToTeams}
|
||||
icon={Users}
|
||||
iconColor="var(--neon-purple)"
|
||||
iconBgGradient="linear-gradient(to bottom right, rgba(168, 85, 247, 0.2), rgba(168, 85, 247, 0.1))"
|
||||
viewFullLabel="View All"
|
||||
>
|
||||
<LeaderboardList>
|
||||
{top5.map((team) => {
|
||||
const position = team.position;
|
||||
const isLast = index === top5.length - 1;
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -95,11 +56,9 @@ export function TeamLeaderboardPreview({ teams, onTeamClick, onNavigateToTeams }
|
||||
transition
|
||||
hoverBg="bg-white/[0.02]"
|
||||
group
|
||||
borderBottom={!isLast}
|
||||
borderColor="border-charcoal-outline/30"
|
||||
>
|
||||
<Box w="8" display="flex" justifyContent="center">
|
||||
<RankMedal rank={position} size="sm" />
|
||||
<RankBadge rank={position} />
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
@@ -166,7 +125,7 @@ export function TeamLeaderboardPreview({ teams, onTeamClick, onNavigateToTeams }
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</LeaderboardTableShell>
|
||||
</LeaderboardList>
|
||||
</LeaderboardPreviewShell>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user