From 54f3c414f522fd7326347df5b27f181561d9dba4 Mon Sep 17 00:00:00 2001 From: BOHA Date: Sun, 7 Jun 2026 21:34:06 +0200 Subject: [PATCH] =?UTF-8?q?fix(ui):=20unify=20icon=20badges=20=E2=80=94=20?= =?UTF-8?q?solid=20tile=20+=20white=20glyph=20via=20shared=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 `.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) --- .../components/dashboard/DashActivityFeed.tsx | 26 ++++++++------ .../components/dashboard/DashProfile.tsx | 27 +++++++------- src/admin/pages/AttendanceHistory.tsx | 24 +++++++------ src/admin/pages/Dashboard.tsx | 24 +++++++------ src/admin/pages/Settings.tsx | 24 +++++++------ src/admin/theme.ts | 28 ++++++++++++++- src/admin/ui/StatCard.tsx | 36 +++++++------------ 7 files changed, 109 insertions(+), 80 deletions(-) diff --git a/src/admin/components/dashboard/DashActivityFeed.tsx b/src/admin/components/dashboard/DashActivityFeed.tsx index b677b9a..01bce67 100644 --- a/src/admin/components/dashboard/DashActivityFeed.tsx +++ b/src/admin/components/dashboard/DashActivityFeed.tsx @@ -2,6 +2,7 @@ import { Link as RouterLink } from "react-router-dom"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import { Card, Button, EmptyState } from "../../ui"; +import { iconBadgeSx, type IconBadgeColor } from "../../theme"; import { ENTITY_TYPE_LABELS, getActivityIconClass, @@ -159,17 +160,20 @@ export default function DashActivityFeed({ }} > {getActivityIcon(act.action)} diff --git a/src/admin/components/dashboard/DashProfile.tsx b/src/admin/components/dashboard/DashProfile.tsx index f0ef4e4..e8df687 100644 --- a/src/admin/components/dashboard/DashProfile.tsx +++ b/src/admin/components/dashboard/DashProfile.tsx @@ -10,6 +10,7 @@ import DialogActions from "@mui/material/DialogActions"; import { useAuth } from "../../context/AuthContext"; import { useAlert } from "../../context/AlertContext"; import apiFetch from "../../utils/api"; +import { iconBadgeSx } from "../../theme"; import { Card, Button, Modal, Field, TextField } from "../../ui"; import useDialogScrollLock from "../../ui/useDialogScrollLock"; @@ -251,17 +252,18 @@ export default function DashProfile({ > .main` fill with a WHITE + * glyph, darkened in dark mode (FILLED_DARK_BG) so white stays legible on the + * otherwise-bright success/warning/info/error fills. `default` = neutral grey. + * Single source of truth for every labelled icon badge — use in an sx array + * next to the tile's size/shape so they can never drift apart: + * sx={[{ width: 40, height: 40, borderRadius: 2, display: "flex", … }, iconBadgeSx(color)]} + */ +export function iconBadgeSx(color: IconBadgeColor) { + const bg = color === "default" ? "grey.600" : `${color}.main`; + const darkBg = (FILLED_DARK_BG as Record)[color]?.bg; + return (theme: Theme) => ({ + bgcolor: bg, + color: "common.white", + ...(darkBg ? theme.applyStyles("dark", { backgroundColor: darkBg }) : {}), + }); +} diff --git a/src/admin/ui/StatCard.tsx b/src/admin/ui/StatCard.tsx index ff5d55a..1b608ef 100644 --- a/src/admin/ui/StatCard.tsx +++ b/src/admin/ui/StatCard.tsx @@ -2,7 +2,7 @@ import type { ReactNode } from "react"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Card from "./Card"; -import { FILLED_DARK_BG } from "../theme"; +import { iconBadgeSx } from "../theme"; export type StatCardColor = | "default" @@ -31,33 +31,23 @@ export default function StatCard({ 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). In dark - // mode the bright .main fill is darkened (same rule as filled buttons/chips) - // so the white glyph stays legible on success/warning/info/error tiles. - const iconBg = color === "default" ? "grey.600" : `${color}.main`; - const iconDarkBg = (FILLED_DARK_BG as Record)[color] - ?.bg; - return ( {icon && ( ({ - display: "flex", - alignItems: "center", - justifyContent: "center", - width: 40, - height: 40, - borderRadius: 2, - bgcolor: iconBg, - color: "common.white", - flexShrink: 0, - ...(iconDarkBg - ? theme.applyStyles("dark", { backgroundColor: iconDarkBg }) - : {}), - })} + sx={[ + { + display: "flex", + alignItems: "center", + justifyContent: "center", + width: 40, + height: 40, + borderRadius: 2, + flexShrink: 0, + }, + iconBadgeSx(color), + ]} > {icon}