88 lines
2.1 KiB
TypeScript
88 lines
2.1 KiB
TypeScript
import type { ReactNode } from "react";
|
|
import Box from "@mui/material/Box";
|
|
import Typography from "@mui/material/Typography";
|
|
import Card from "./Card";
|
|
|
|
export type StatCardColor =
|
|
| "default"
|
|
| "primary"
|
|
| "success"
|
|
| "warning"
|
|
| "error"
|
|
| "info";
|
|
|
|
export interface StatCardProps {
|
|
label: string;
|
|
value: ReactNode;
|
|
icon?: ReactNode;
|
|
color?: StatCardColor;
|
|
footer?: ReactNode;
|
|
}
|
|
|
|
/**
|
|
* KPI / metric tile. Dense Soft-SaaS look: tinted icon square, big value,
|
|
* small uppercase label, optional footer caption.
|
|
*/
|
|
export default function StatCard({
|
|
label,
|
|
value,
|
|
icon,
|
|
color = "default",
|
|
footer,
|
|
}: StatCardProps) {
|
|
// Solid tile + white glyph: high-contrast in both themes (the old light tint
|
|
// left the icon dark/low-visibility, esp. for the default variant).
|
|
const iconBg = color === "default" ? "grey.600" : `${color}.main`;
|
|
const iconColor = "common.white";
|
|
|
|
return (
|
|
<Card>
|
|
<Box sx={{ display: "flex", alignItems: "flex-start", gap: 1.5 }}>
|
|
{icon && (
|
|
<Box
|
|
sx={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: 2,
|
|
bgcolor: iconBg,
|
|
color: iconColor,
|
|
flexShrink: 0,
|
|
}}
|
|
>
|
|
{icon}
|
|
</Box>
|
|
)}
|
|
<Box sx={{ minWidth: 0 }}>
|
|
<Typography
|
|
variant="h5"
|
|
sx={{ fontFamily: "'DM Mono', Menlo, monospace", lineHeight: 1.2 }}
|
|
>
|
|
{value}
|
|
</Typography>
|
|
<Typography
|
|
variant="caption"
|
|
color="text.secondary"
|
|
sx={{
|
|
textTransform: "uppercase",
|
|
letterSpacing: ".06em",
|
|
fontWeight: 600,
|
|
}}
|
|
>
|
|
{label}
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
{footer && (
|
|
<Box sx={{ mt: 1.5, pt: 1.5, borderTop: 1, borderColor: "divider" }}>
|
|
<Typography variant="caption" color="text.secondary">
|
|
{footer}
|
|
</Typography>
|
|
</Box>
|
|
)}
|
|
</Card>
|
|
);
|
|
}
|