Files
app/src/admin/ui/StatCard.tsx
BOHA 54f3c414f5 fix(ui): unify icon badges — solid tile + white glyph via shared helper
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>
2026-06-07 21:34:06 +02:00

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>
);
}