Phase 2 of the "fully MUI" cleanup — eliminates two more stylesheets.
- base.css -> src/admin/GlobalStyles.tsx: the reset, typography,
scrollbar/::selection, theme cross-fade timing and all utility
classes now live in a single theme-aware MUI <GlobalStyles>, rendered
inside MuiProvider so every rule resolves against theme.vars (verified
reactive in both schemes). base.css deleted.
- attendance.css -> a styled("div") LocationMap in AttendanceLocation;
attendance.css deleted.
- App.tsx bootstrap loader is now fully self-contained (inline spinner
+ keyframes, theme-aware background read from the data-theme attr),
since it renders before MuiProvider/GlobalStyles mount — it no longer
depends on the removed .admin-spinner / var(--bg-primary).
tsc -b --noEmit and npm run build clean; verified in Chrome (light +
dark): body bg, text, fonts, .text-warning/.link-accent all correct.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
163 lines
5.9 KiB
TypeScript
163 lines
5.9 KiB
TypeScript
import GlobalStyles from "@mui/material/GlobalStyles";
|
|
import { fonts } from "./theme";
|
|
|
|
const EASE = "cubic-bezier(0.4, 0, 0.2, 1)";
|
|
|
|
/**
|
|
* App-wide global styles, theme-aware via MUI's CSS variables.
|
|
*
|
|
* This replaces the former hand-written `base.css` (reset, typography,
|
|
* scrollbar, ::selection, the theme cross-fade timing, utility classes).
|
|
* It is rendered once inside MuiProvider so every rule resolves against the
|
|
* active color scheme (`theme.vars.*`). The pre-React bootstrap spinner lives
|
|
* inline in App.tsx instead, because it renders before MUI mounts.
|
|
*/
|
|
export default function AppGlobalStyles() {
|
|
return (
|
|
<GlobalStyles
|
|
styles={(theme) => ({
|
|
// ---- Reset ----
|
|
"*": { margin: 0, padding: 0, boxSizing: "border-box" },
|
|
html: { scrollBehavior: "smooth", overflowX: "hidden" },
|
|
"html, body, #root": { minHeight: "100dvh", maxWidth: "100vw" },
|
|
body: {
|
|
fontFamily: fonts.body,
|
|
fontSize: "16px",
|
|
lineHeight: 1.6,
|
|
color: theme.vars!.palette.text.primary,
|
|
background: theme.vars!.palette.background.default,
|
|
overflowX: "hidden",
|
|
overscrollBehaviorX: "none",
|
|
WebkitFontSmoothing: "antialiased",
|
|
MozOsxFontSmoothing: "grayscale",
|
|
transition: "background-color 0.3s ease, color 0.3s ease",
|
|
},
|
|
"#root": { overflowX: "hidden", touchAction: "pan-y pinch-zoom" },
|
|
|
|
// ---- Theme cross-fade (View Transitions API, see ThemeContext) ----
|
|
"::view-transition-old(root), ::view-transition-new(root)": {
|
|
animationDuration: "0.3s",
|
|
animationTimingFunction: EASE,
|
|
},
|
|
|
|
// ---- Typography ----
|
|
"h1, h2, h3, h4, h5, h6": {
|
|
fontFamily: fonts.heading,
|
|
fontWeight: 700,
|
|
lineHeight: 1.2,
|
|
color: theme.vars!.palette.text.primary,
|
|
},
|
|
h1: { fontSize: "clamp(2.5rem, 5vw, 4rem)" },
|
|
h2: { fontSize: "clamp(2rem, 4vw, 3rem)" },
|
|
h3: { fontSize: "clamp(1.25rem, 2vw, 1.5rem)" },
|
|
p: { color: theme.vars!.palette.text.secondary, lineHeight: 1.6 },
|
|
a: {
|
|
color: "inherit",
|
|
textDecoration: "none",
|
|
transition: `all 0.3s ${EASE}`,
|
|
},
|
|
img: { maxWidth: "100%", height: "auto" },
|
|
|
|
// ---- Scrollbar & selection ----
|
|
"::-webkit-scrollbar": { width: "8px" },
|
|
"::-webkit-scrollbar-track": {
|
|
background: theme.vars!.palette.background.paper,
|
|
},
|
|
"::-webkit-scrollbar-thumb": {
|
|
background: theme.vars!.palette.divider,
|
|
borderRadius: "4px",
|
|
},
|
|
"::-webkit-scrollbar-thumb:hover": {
|
|
background: theme.vars!.palette.text.secondary,
|
|
},
|
|
"::selection": {
|
|
background: theme.vars!.palette.primary.main,
|
|
color: "#fff",
|
|
},
|
|
|
|
// ---- Utility classes (legacy, still used across pages) ----
|
|
".text-warning": {
|
|
color: `${theme.vars!.palette.warning.main} !important`,
|
|
},
|
|
".text-danger": {
|
|
color: `${theme.vars!.palette.error.main} !important`,
|
|
},
|
|
".text-success": {
|
|
color: `${theme.vars!.palette.success.main} !important`,
|
|
},
|
|
".text-muted": {
|
|
color: `${theme.vars!.palette.text.secondary} !important`,
|
|
},
|
|
".text-secondary": {
|
|
color: `${theme.vars!.palette.text.secondary} !important`,
|
|
},
|
|
".text-tertiary": {
|
|
color: `${theme.vars!.palette.text.disabled} !important`,
|
|
},
|
|
".text-accent": {
|
|
color: `${theme.vars!.palette.primary.main} !important`,
|
|
},
|
|
".fw-500": { fontWeight: 500 },
|
|
".fw-600": { fontWeight: "600 !important" },
|
|
".link-accent": {
|
|
color: theme.vars!.palette.primary.main,
|
|
fontWeight: 500,
|
|
textDecoration: "none",
|
|
},
|
|
".link-accent:hover": { textDecoration: "underline" },
|
|
".flex-1": { flex: 1 },
|
|
".flex-row": { display: "flex", alignItems: "center" },
|
|
".flex-row-gap": {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: "0.75rem",
|
|
},
|
|
".flex-between": {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
},
|
|
".inline-flex": { display: "inline-flex", alignItems: "center" },
|
|
".mb-1": { marginBottom: "0.25rem" },
|
|
".mb-2": { marginBottom: "0.5rem" },
|
|
".mb-3": { marginBottom: "0.75rem" },
|
|
".mb-4": { marginBottom: "1rem" },
|
|
".mb-6": { marginBottom: "1.5rem" },
|
|
".mt-1": { marginTop: "0.25rem" },
|
|
".mt-2": { marginTop: "0.5rem" },
|
|
".mt-3": { marginTop: "0.75rem" },
|
|
".mt-6": { marginTop: "1.5rem" },
|
|
".gap-1": { gap: "0.25rem" },
|
|
".gap-2": { gap: "0.5rem" },
|
|
".gap-3": { gap: "0.75rem" },
|
|
".gap-4": { gap: "1rem" },
|
|
".gap-5": { gap: "1.25rem" },
|
|
".gap-6": { gap: "1.5rem" },
|
|
".text-right": { textAlign: "right" },
|
|
".text-center": { textAlign: "center" },
|
|
".text-xs": { fontSize: "0.75rem" },
|
|
".text-sm": { fontSize: "0.8125rem" },
|
|
".text-md": { fontSize: "0.875rem" },
|
|
".text-base": { fontSize: "1rem" },
|
|
".w-full": { width: "100%" },
|
|
".max-w-xs": { maxWidth: "120px" },
|
|
".max-w-sm": { maxWidth: "200px" },
|
|
".whitespace-nowrap": { whiteSpace: "nowrap" },
|
|
|
|
// ---- Reduced motion ----
|
|
"@media (prefers-reduced-motion: reduce)": {
|
|
"::view-transition-old(root), ::view-transition-new(root)": {
|
|
animation: "none",
|
|
},
|
|
"*, *::before, *::after": {
|
|
animationDuration: "0.01ms !important",
|
|
animationIterationCount: "1 !important",
|
|
transitionDuration: "0.01ms !important",
|
|
scrollBehavior: "auto !important",
|
|
},
|
|
},
|
|
})}
|
|
/>
|
|
);
|
|
}
|