The labelled icon-badge tiles were inconsistent: some used a same-hue light tile + colored glyph (e.g. the Settings 2FA row: success.light tile + success.main glyph — low contrast, the reported "badly visible" icon), and the solid-tile ones used the bright .main fill in dark mode without darkening, so a white glyph on bright green/amber was low-contrast too (DashProfile's success badge was ~1.9:1). Add a single `iconBadgeSx(color)` helper (theme.ts): solid `<color>.main` fill + white glyph, darkened in dark mode (FILLED_DARK_BG) so white stays legible; `default` = neutral grey. Applied to every badge so they can't drift: StatCard, DashActivityFeed, DashProfile (2FA badge), Dashboard (2FA banner icon), AttendanceHistory (month tile), and the Settings 2FA row tile (now solid green when required / grey when optional + white lock, instead of the low-contrast light-green tile). Also switched DashProfile's backup-codes warning callout from warning.light to a subtle warning wash so its amber text stays readable in dark mode. tsc -b --noEmit, npm run build, vitest 152/152 all clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
85 lines
1.9 KiB
TypeScript
85 lines
1.9 KiB
TypeScript
import type { ReactNode } from "react";
|
|
import Box from "@mui/material/Box";
|
|
import Typography from "@mui/material/Typography";
|
|
import Card from "./Card";
|
|
import { iconBadgeSx } from "../theme";
|
|
|
|
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) {
|
|
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,
|
|
flexShrink: 0,
|
|
},
|
|
iconBadgeSx(color),
|
|
]}
|
|
>
|
|
{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>
|
|
);
|
|
}
|