Files
gridpilot.gg/apps/website/ui/StatCard.tsx
2026-01-18 17:55:04 +01:00

116 lines
3.0 KiB
TypeScript

import React, { ReactNode } from 'react';
import { Box } from './primitives/Box';
import { Stack } from './primitives/Stack';
import { Text } from './Text';
import { Card } from './Card';
import { Icon } from './Icon';
import { LucideIcon } from 'lucide-react';
interface StatCardProps {
label: string;
value: string | number;
icon?: LucideIcon;
trend?: {
value: number;
isPositive: boolean;
};
variant?: 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info';
className?: string;
onClick?: () => void;
prefix?: string;
suffix?: string;
delay?: number;
}
export function StatCard({
label,
value,
icon,
trend,
variant = 'default',
className = '',
onClick,
prefix,
suffix,
delay,
}: StatCardProps) {
const variantClasses = {
default: 'bg-panel-gray border-border-gray',
primary: 'bg-primary-accent/5 border-primary-accent/20',
success: 'bg-success-green/5 border-success-green/20',
warning: 'bg-warning-amber/5 border-warning-amber/20',
danger: 'bg-critical-red/5 border-critical-red/20',
info: 'bg-telemetry-aqua/5 border-telemetry-aqua/20',
};
const iconBgClasses = {
default: 'bg-white/5',
primary: 'bg-primary-accent/10',
success: 'bg-success-green/10',
warning: 'bg-warning-amber/10',
danger: 'bg-critical-red/10',
info: 'bg-telemetry-aqua/10',
};
const iconColorClasses = {
default: 'text-gray-400',
primary: 'text-primary-accent',
success: 'text-success-green',
warning: 'text-warning-amber',
danger: 'text-critical-red',
info: 'text-telemetry-aqua',
};
const cardContent = (
<Card variant="default" p={5} className={`${variantClasses[variant]} ${className} h-full`}>
<Stack gap={3}>
<Stack direction="row" align="center" justify="between">
<Text size="xs" weight="bold" color="text-gray-500" uppercase letterSpacing="widest">
{label}
</Text>
{icon && (
<Box
p={2}
rounded="lg"
bg={iconBgClasses[variant]}
className={iconColorClasses[variant]}
>
<Icon icon={icon} size={5} />
</Box>
)}
</Stack>
<Stack gap={1}>
<Text size="3xl" weight="bold" color="text-white">
{prefix}{value}{suffix}
</Text>
{trend && (
<Stack direction="row" align="center" gap={1}>
<Text
size="xs"
weight="bold"
color={trend.isPositive ? 'text-success-green' : 'text-critical-red'}
>
{trend.isPositive ? '+' : ''}{trend.value}%
</Text>
<Text size="xs" color="text-gray-500">
vs last period
</Text>
</Stack>
)}
</Stack>
</Stack>
</Card>
);
if (onClick) {
return (
<Box as="button" onClick={onClick} w="full" textAlign="left" className="focus:outline-none">
{cardContent}
</Box>
);
}
return cardContent;
}