Some checks failed
CI / lint-typecheck (pull_request) Failing after 10s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped
90 lines
2.6 KiB
TypeScript
90 lines
2.6 KiB
TypeScript
import { LucideIcon } from 'lucide-react';
|
|
import { ReactNode } from 'react';
|
|
import { Box } from './Box';
|
|
import { Card } from './Card';
|
|
import { Icon } from './Icon';
|
|
import { Text } from './Text';
|
|
|
|
export interface StatCardProps {
|
|
label: string;
|
|
value: string | number;
|
|
icon?: LucideIcon;
|
|
intent?: 'primary' | 'success' | 'warning' | 'critical' | 'telemetry' | 'low' | 'high' | 'med';
|
|
variant?: 'default' | 'dark' | 'muted' | 'glass' | 'outline' | 'blue' | 'green' | 'orange';
|
|
font?: 'sans' | 'mono';
|
|
trend?: {
|
|
value: number;
|
|
isPositive: boolean;
|
|
};
|
|
footer?: ReactNode;
|
|
suffix?: string;
|
|
prefix?: string;
|
|
delay?: number;
|
|
}
|
|
|
|
export const StatCard = ({
|
|
label,
|
|
value,
|
|
icon,
|
|
intent: intentProp,
|
|
variant = 'default',
|
|
font = 'sans',
|
|
trend,
|
|
footer,
|
|
suffix,
|
|
prefix,
|
|
delay,
|
|
...props
|
|
}: StatCardProps & { [key: string]: any }) => {
|
|
const variantMap: Record<string, { variant: any, intent: any }> = {
|
|
blue: { variant: 'default', intent: 'primary' },
|
|
green: { variant: 'default', intent: 'success' },
|
|
orange: { variant: 'default', intent: 'warning' },
|
|
};
|
|
|
|
const mapped = variantMap[variant as string] || { variant, intent: intentProp || 'primary' };
|
|
const finalVariant = mapped.variant;
|
|
const finalIntent = mapped.intent;
|
|
|
|
return (
|
|
<Card variant={finalVariant} {...props}>
|
|
<Box display="flex" alignItems="start" justifyContent="between" marginBottom={4}>
|
|
<Box>
|
|
<Text data-testid={`stat-label-${label.toLowerCase().replace(/\s+/g, '-')}`} size="xs" weight="bold" variant="low" uppercase>
|
|
{label}
|
|
</Text>
|
|
<Text data-testid={`stat-value-${label.toLowerCase().replace(/\s+/g, '-')}`} size="2xl" weight="bold" variant={finalIntent as any || 'high'} font={font} block marginTop={1}>
|
|
{prefix}{value}{suffix}
|
|
</Text>
|
|
</Box>
|
|
{icon && (
|
|
<Box padding={2} rounded="lg" bg="var(--ui-color-bg-surface-muted)">
|
|
<Icon icon={icon} size={5} intent={finalIntent} />
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
|
|
{trend && (
|
|
<Box display="flex" alignItems="center" gap={1} marginBottom={footer ? 4 : 0}>
|
|
<Text
|
|
size="xs"
|
|
weight="bold"
|
|
variant={trend.isPositive ? 'success' : 'critical'}
|
|
>
|
|
{trend.isPositive ? '+' : '-'}{Math.abs(trend.value)}%
|
|
</Text>
|
|
<Text size="xs" variant="low">
|
|
vs last period
|
|
</Text>
|
|
</Box>
|
|
)}
|
|
|
|
{footer && (
|
|
<Box borderTop paddingTop={4}>
|
|
{footer}
|
|
</Box>
|
|
)}
|
|
</Card>
|
|
);
|
|
};
|