Files
gridpilot.gg/apps/website/ui/RaceCard.tsx
2026-01-15 17:12:24 +01:00

168 lines
5.1 KiB
TypeScript

import { ArrowRight, Car, ChevronRight, LucideIcon, Trophy, Zap } from 'lucide-react';
import { Box } from './Box';
import { Heading } from './Heading';
import { Icon } from './Icon';
import { Link } from './Link';
import { Stack } from './Stack';
import { Surface } from './Surface';
import { Text } from './Text';
interface RaceCardProps {
track: string;
car: string;
scheduledAt: string;
status: string;
leagueName: string;
leagueId?: string;
strengthOfField?: number | null;
onClick?: () => void;
statusConfig: {
border: string;
bg: string;
color: string;
icon: LucideIcon | null;
label: string;
};
}
export function RaceCard({
track,
car,
scheduledAt,
status,
leagueName,
leagueId,
strengthOfField,
onClick,
statusConfig,
}: RaceCardProps) {
const scheduledAtDate = new Date(scheduledAt);
return (
<Surface
variant="muted"
rounded="xl"
border
padding={4}
onClick={onClick}
style={{
cursor: onClick ? 'pointer' : 'default',
borderColor: statusConfig.border.includes('/') ? statusConfig.border : undefined, // Handle custom colors if needed
}}
className={`group relative overflow-hidden transition-all duration-200 hover:scale-[1.01] hover:border-primary-blue`}
>
{/* Live indicator */}
{status === 'running' && (
<Box
position="absolute"
top="0"
left="0"
right="0"
h="1"
style={{
background: 'linear-gradient(to right, var(--performance-green), rgba(16, 185, 129, 0.5), var(--performance-green))',
}}
animate="pulse"
/>
)}
<Stack direction="row" align="start" gap={4}>
{/* Time Column */}
<Box textAlign="center" style={{ minWidth: '60px' }} flexShrink={0}>
<Text size="lg" weight="bold" color="text-white" block>
{scheduledAtDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</Text>
<Text size="xs" color={statusConfig.color as any} block>
{status === 'running' ? 'LIVE' : scheduledAtDate.toLocaleDateString()}
</Text>
</Box>
{/* Divider */}
<Box
w="px"
style={{ alignSelf: 'stretch', backgroundColor: 'rgba(38, 38, 38, 0.8)' }}
/>
{/* Main Content */}
<Box style={{ flex: 1, minWidth: 0 }}>
<Stack direction="row" align="start" justify="between" gap={4}>
<Box style={{ minWidth: 0 }}>
<Heading
level={3}
className="truncate group-hover:text-primary-blue transition-colors"
>
{track}
</Heading>
<Stack direction="row" align="center" gap={3} mt={1}>
<Stack direction="row" align="center" gap={1}>
<Icon icon={Car} size={3.5} color="var(--text-gray-400)" />
<Text size="sm" color="text-gray-400">
{car}
</Text>
</Stack>
{strengthOfField && (
<Stack direction="row" align="center" gap={1}>
<Icon icon={Zap} size={3.5} color="var(--warning-amber)" />
<Text size="sm" color="text-gray-400">
SOF {strengthOfField}
</Text>
</Stack>
)}
</Stack>
</Box>
{/* Status Badge */}
<Box
display="flex"
alignItems="center"
gap={1.5}
px={2.5}
py={1}
rounded="full"
border
style={{
backgroundColor: 'rgba(38, 38, 38, 0.5)',
borderColor: 'rgba(38, 38, 38, 0.8)',
}}
>
{statusConfig.icon && (
<Icon icon={statusConfig.icon} size={3.5} color={statusConfig.color as any} />
)}
<Text size="xs" weight="medium" color={statusConfig.color as any}>
{statusConfig.label}
</Text>
</Box>
</Stack>
{/* League Link */}
<Box mt={3} pt={3} borderTop style={{ borderColor: 'rgba(38, 38, 38, 0.3)' }}>
<Link
href={`/leagues/${leagueId ?? ''}`}
onClick={(e) => e.stopPropagation()}
>
<Stack direction="row" align="center" gap={2}>
<Icon icon={Trophy} size={3.5} color="var(--primary-blue)" />
<Text size="sm" color="text-primary-blue" className="hover:underline">
{leagueName}
</Text>
<Icon icon={ArrowRight} size={3} color="var(--primary-blue)" />
</Stack>
</Link>
</Box>
</Box>
{/* Arrow */}
<Icon
icon={ChevronRight}
size={5}
color="var(--text-gray-500)"
className="group-hover:text-primary-blue transition-colors"
flexShrink={0}
/>
</Stack>
</Surface>
);
}