Files
gridpilot.gg/apps/website/components/mockups/StandingsTableMockup.tsx
2026-01-15 17:12:24 +01:00

300 lines
11 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { motion, useReducedMotion, useMotionValue, useSpring } from 'framer-motion';
import { useEffect, useState } from 'react';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
export function StandingsTableMockup() {
const shouldReduceMotion = useReducedMotion();
const [hoveredRow, setHoveredRow] = useState<number | null>(null);
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
setIsMobile(window.innerWidth < 768);
}, []);
if (isMobile) {
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={3} overflow="hidden">
<Box mb={3}>
<Box display="flex" alignItems="center" gap={2} pb={2} borderBottom borderColor="border-charcoal-outline">
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '12px' }}
font="mono"
color="text-gray-400"
>
#
</Text>
<Text size="xs" flexGrow={1} weight="semibold" color="text-white">Driver</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '12px' }}
font="mono"
color="text-gray-400"
>
Pts
</Text>
</Box>
</Box>
<Stack gap={2}>
{[1, 2, 3, 4, 5].map((i) => (
<Box
key={i}
display="flex"
alignItems="center"
gap={2}
py={2}
px={2}
rounded="lg"
border
bg={i <= 3 ? 'bg-gradient-to-r from-performance-green/10 to-iron-gray' : 'bg-iron-gray'}
borderColor={i <= 3 ? 'border-performance-green/20' : 'border-charcoal-outline'}
>
<Box
h="7"
w="7"
rounded="full"
display="flex"
alignItems="center"
justifyContent="center"
weight="semibold"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '12px' }}
bg={i <= 3 ? 'bg-primary-blue' : 'bg-charcoal-outline'}
color={i <= 3 ? 'text-white' : 'text-gray-400'}
shadow={i <= 3 ? '0_0_12px_rgba(25,140,255,0.3)' : undefined}
>
{i}
</Box>
<Box flexGrow={1} display="flex" alignItems="center" gap={2}>
<Box h="5" w="5" rounded="full" bg="bg-charcoal-outline" border borderColor="border-primary-blue/20" display="flex" alignItems="center" justifyContent="center">
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '12px' }}
>
🏎
</Text>
</Box>
<Box h="2.5" fullWidth maxWidth="100px" bg="bg-white/10" rounded="sm" />
</Box>
<Box position="relative" w="16" h="5" bg="bg-charcoal-outline" rounded="sm" border borderColor="border-primary-blue/20" overflow="hidden">
<Box
position="absolute"
insetY="0"
left="0"
bg={i <= 3 ? 'bg-gradient-to-r from-performance-green/40 to-performance-green/20' : 'bg-gradient-to-r from-iron-gray to-charcoal-outline'}
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ width: `${100 - (i - 1) * 15}%` }}
/>
<Box position="relative" h="full" display="flex" alignItems="center" justifyContent="center">
<Text size="xs" font="mono" weight="semibold" color="text-white">
{300 - i * 20}
</Text>
</Box>
</Box>
</Box>
))}
</Stack>
</Box>
);
}
const getRowAnimation = (i: number) => ({
hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 10 },
visible: {
opacity: 1,
y: 0,
transition: {
delay: shouldReduceMotion ? 0 : i * 0.05,
type: 'spring' as const,
stiffness: 300,
damping: 24
}
}
});
return (
<Box position="relative" fullWidth fullHeight bg="bg-gradient-to-br from-deep-graphite via-iron-gray to-deep-graphite" rounded="lg" p={{ base: 1.5, sm: 3, md: 4, lg: 6 }} overflow="hidden">
<Box mb={{ base: 1.5, sm: 2, md: 3, lg: 4 }}>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
color="text-white"
opacity={0.5}
mb={{ base: 1.5, sm: 2, md: 3 }}
block
>
Real-time standings updated after every race
</Text>
<Box display="flex" alignItems="center" gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }} pb={{ base: 1.5, sm: 2, md: 3 }} borderBottom borderColor="border-charcoal-outline">
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
font="mono"
color="text-gray-400"
>
#
</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
flexGrow={1}
weight="semibold"
color="text-white"
>
Driver
</Text>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
font="mono"
color="text-gray-400"
// eslint-disable-next-line gridpilot-rules/component-classification
className="hidden md:block"
>
Wins
</Text>
<Box display="flex" alignItems="center" gap={1}>
<Text
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
font="mono"
color="text-gray-400"
>
Points
</Text>
<Text color="text-performance-green"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '8px' }}
>
</Text>
</Box>
</Box>
</Box>
<Stack gap={0.5}>
{[1, 2, 3, 4, 5, 6, 7, 8].map((i) => (
<Box
key={i}
as={motion.div}
variants={getRowAnimation(i)}
initial="hidden"
animate="visible"
position="relative"
display="flex"
alignItems="center"
gap={{ base: 1.5, sm: 2, md: 3, lg: 4 }}
py={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }}
px={{ base: 1.5, sm: 2, md: 3 }}
rounded="lg"
border
transition
bg={i <= 3 ? 'bg-gradient-to-r from-performance-green/10 to-iron-gray' : 'bg-iron-gray'}
borderColor={i <= 3 ? 'border-performance-green/20' : 'border-charcoal-outline'}
onHoverStart={() => !shouldReduceMotion && setHoveredRow(i)}
onHoverEnd={() => setHoveredRow(null)}
whileHover={shouldReduceMotion ? {} : {
scale: 1.01,
boxShadow: '0 0 20px rgba(25,140,255,0.3)',
transition: { duration: 0.15 }
}}
>
<Box
as={motion.div}
h={{ base: 6, sm: 7 }}
w={{ base: 6, sm: 7 }}
rounded="full"
display="flex"
alignItems="center"
justifyContent="center"
weight="semibold"
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '12px' }}
bg={i <= 3 ? 'bg-primary-blue' : 'bg-charcoal-outline'}
color={i <= 3 ? 'text-white' : 'text-gray-400'}
animate={
shouldReduceMotion ? {} : i <= 3 && hoveredRow === i
? { scale: 1.15, boxShadow: '0 0 28px rgba(25,140,255,0.5)' }
: {}
}
>
{i}
</Box>
<Box flexGrow={1} display="flex" alignItems="center" gap={{ base: 1, sm: 1.5, md: 2 }}>
<Box h={{ base: 4, sm: 5, md: 6 }} w={{ base: 4, sm: 5, md: 6 }} rounded="full" bg="bg-charcoal-outline" border borderColor="border-primary-blue/20" display="flex" alignItems="center" justifyContent="center">
<Text size={{ base: 'xs', sm: 'sm' }}>🏎</Text>
</Box>
<Box h={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }} fullWidth maxWidth={{ base: '80px', sm: '100px', md: '140px' }} bg="bg-white/10" rounded="sm" />
</Box>
<Box h={{ base: 1.5, sm: 2, md: 2.5, lg: 3 }} w={{ base: 10, sm: 12, md: 16 }} bg="bg-white/5" rounded="sm" font="mono"
// eslint-disable-next-line gridpilot-rules/component-classification
className="hidden md:block"
/>
<Box position="relative">
<AnimatedPoints
points={300 - i * 20}
position={i}
shouldReduceMotion={shouldReduceMotion ?? false}
/>
</Box>
</Box>
))}
</Stack>
</Box>
);
}
function AnimatedPoints({
points,
position,
shouldReduceMotion
}: {
points: number;
position: number;
shouldReduceMotion: boolean;
}) {
const motionValue = useMotionValue(0);
const spring = useSpring(motionValue, { stiffness: 50, damping: 20 });
useEffect(() => {
if (shouldReduceMotion) {
motionValue.set(points);
} else {
setTimeout(() => motionValue.set(points), 100 + position * 50);
}
}, [points, position, shouldReduceMotion, motionValue]);
const percentage = (points / 300) * 100;
return (
<Box position="relative" w={{ base: 12, sm: 16, md: 20, lg: 24 }} h={{ base: 4, sm: 5, md: 6, lg: 7 }} bg="bg-charcoal-outline" rounded="sm" border borderColor="border-primary-blue/20" overflow="hidden">
<Box
as={motion.div}
position="absolute"
insetY="0"
left="0"
bg={position <= 3 ? 'bg-gradient-to-r from-performance-green/40 to-performance-green/20' : 'bg-gradient-to-r from-iron-gray to-charcoal-outline'}
initial={{ width: '0%' }}
animate={{ width: `${percentage}%` }}
transition={{ duration: shouldReduceMotion ? 0 : 0.8, ease: 'easeOut', delay: 0.1 + position * 0.05 }}
/>
<Box position="relative" h="full" display="flex" alignItems="center" justifyContent="center">
<Box as={motion.span}
// eslint-disable-next-line gridpilot-rules/component-classification
style={{ fontSize: '10px' }}
font="mono"
weight="semibold"
color="text-white"
>
{shouldReduceMotion ? points : <Box as={motion.span}>{spring}</Box>}
</Box>
</Box>
</Box>
);
}