116 lines
3.0 KiB
TypeScript
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;
|
|
}
|