diff --git a/client/src/Pages/StatusPage/Status/themes/refined/RefinedStatusPage.tsx b/client/src/Pages/StatusPage/Status/themes/refined/RefinedStatusPage.tsx index ef4f73596..332e263bc 100644 --- a/client/src/Pages/StatusPage/Status/themes/refined/RefinedStatusPage.tsx +++ b/client/src/Pages/StatusPage/Status/themes/refined/RefinedStatusPage.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useMemo, useState } from "react"; import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; import { useTranslation } from "react-i18next"; @@ -27,7 +27,7 @@ interface Props { export const RefinedStatusPage = ({ statusPage, monitors }: Props) => { const { t } = useTranslation(); const { tokens, mode } = useStatusPageTheme(); - const styles = refinedStyles(tokens, mode === "dark"); + const styles = useMemo(() => refinedStyles(tokens, mode === "dark"), [tokens, mode]); const [chartMode, setChartMode] = useState<"heatmap" | "histogram">("heatmap"); const overall = resolveOverallStatus(monitors, t); @@ -62,7 +62,7 @@ export const RefinedStatusPage = ({ statusPage, monitors }: Props) => { - + { })} - {overall.icon} + {overall.icon} {statusPage.showCharts && ( @@ -94,7 +94,7 @@ export const RefinedStatusPage = ({ statusPage, monitors }: Props) => { role="radio" aria-checked={chartMode === "heatmap"} onClick={() => setChartMode("heatmap")} - sx={(styles.chartSwitchButton as Function)(chartMode === "heatmap")} + sx={styles.chartSwitchButton(chartMode === "heatmap")} > {t("pages.statusPages.monitorsList.chartTypeHeatmap")} @@ -104,7 +104,7 @@ export const RefinedStatusPage = ({ statusPage, monitors }: Props) => { role="radio" aria-checked={chartMode === "histogram"} onClick={() => setChartMode("histogram")} - sx={(styles.chartSwitchButton as Function)(chartMode === "histogram")} + sx={styles.chartSwitchButton(chartMode === "histogram")} > {t("pages.statusPages.monitorsList.chartTypeHistogram")} @@ -151,7 +151,7 @@ export const RefinedStatusPage = ({ statusPage, monitors }: Props) => { {t(statusBadgeKey[monitor.status])} @@ -167,10 +167,7 @@ export const RefinedStatusPage = ({ statusPage, monitors }: Props) => { gaugeLabelSx: styles.gaugeLabel, gaugeValueSx: styles.gaugeValue, gaugeBarSx: styles.gaugeBar, - gaugeFillSx: styles.gaugeFill as ( - level: "ok" | "warm" | "hot", - width: number - ) => any, + gaugeFillSx: styles.gaugeFill, gaugeSubSx: styles.gaugeSub, }} /> @@ -180,13 +177,13 @@ export const RefinedStatusPage = ({ statusPage, monitors }: Props) => { any} + cellSx={styles.heatmapCell} /> ) : ( any} + barSx={styles.bar} statsSx={styles.chartStats} /> ))} diff --git a/client/src/Pages/StatusPage/Status/themes/refined/styles.ts b/client/src/Pages/StatusPage/Status/themes/refined/styles.ts index 39bc9bf56..0789efd87 100644 --- a/client/src/Pages/StatusPage/Status/themes/refined/styles.ts +++ b/client/src/Pages/StatusPage/Status/themes/refined/styles.ts @@ -1,10 +1,53 @@ +import type { SxProps, Theme } from "@mui/material/styles"; import type { StatusPageThemeTokens } from "../tokens"; +import { type OverallTone, toneColor, toneSoft } from "../shared/overallStatus"; -export type RefinedTone = "up" | "warn" | "down"; export type RefinedHeatCell = "fast" | "med" | "slow" | "down" | "empty"; export type RefinedBarKind = "up" | "down" | "empty"; export type RefinedGaugeFill = "ok" | "warm" | "hot"; +export interface RefinedStyles { + page: SxProps; + top: SxProps; + brand: SxProps; + logoMono: SxProps; + logoImg: SxProps; + company: SxProps; + hero: SxProps; + statusDot: (tone: OverallTone) => SxProps; + statusCopy: SxProps; + heroTitle: SxProps; + heroSub: SxProps; + heroIcon: (tone: OverallTone) => SxProps; + chartSwitchWrap: SxProps; + chartSwitch: SxProps; + chartSwitchButton: (active: boolean) => SxProps; + monitorList: SxProps; + card: SxProps; + cardRow: SxProps; + cardLeft: SxProps; + monitorName: SxProps; + monitorMeta: SxProps; + pill: SxProps; + pillHardware: SxProps; + monitorUrl: SxProps; + badge: (tone: OverallTone) => SxProps; + heatmap: SxProps; + heatmapCell: (kind: RefinedHeatCell) => SxProps; + histogram: SxProps; + bar: (kind: RefinedBarKind, heightPct: number) => SxProps; + chartStats: SxProps; + infra: SxProps; + infraEmpty: SxProps; + gauge: SxProps; + gaugeLabel: SxProps; + gaugeValue: SxProps; + gaugeBar: SxProps; + gaugeFill: (level: RefinedGaugeFill, widthPct: number) => SxProps; + gaugeSub: SxProps; + footer: SxProps; +} + const sansFontStack = '-apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, sans-serif'; @@ -12,13 +55,10 @@ const cardShadow = "0 1px 2px rgba(10, 16, 32, 0.06), 0 6px 18px rgba(10, 16, 32 const cardShadowHover = "0 2px 4px rgba(16, 24, 40, 0.06), 0 10px 24px rgba(16, 24, 40, 0.06)"; -const toneColor = (tone: RefinedTone, t: StatusPageThemeTokens) => - tone === "down" ? t.down : tone === "warn" ? t.warn : t.up; - -const toneSoft = (tone: RefinedTone, t: StatusPageThemeTokens) => - tone === "down" ? t.downSoft : tone === "warn" ? t.warnSoft : t.upSoft; - -export const refinedStyles = (tokens: StatusPageThemeTokens, isDark: boolean) => { +export const refinedStyles = ( + tokens: StatusPageThemeTokens, + isDark: boolean +): RefinedStyles => { const heatCellBg: Record = { fast: tokens.up, med: `color-mix(in srgb, ${tokens.up} 60%, #ffffff 40%)`, @@ -39,7 +79,7 @@ export const refinedStyles = (tokens: StatusPageThemeTokens, isDark: boolean) => hot: tokens.down, }; - const sx: Record = { + return { page: { flex: "1 0 auto", maxWidth: 960, @@ -78,11 +118,7 @@ export const refinedStyles = (tokens: StatusPageThemeTokens, isDark: boolean) => fontWeight: 700, fontSize: 13, }, - logoImg: { - maxHeight: 32, - maxWidth: 120, - objectFit: "contain", - }, + logoImg: { maxHeight: 32, maxWidth: 120, objectFit: "contain" }, company: { fontSize: 14 }, hero: { @@ -96,7 +132,7 @@ export const refinedStyles = (tokens: StatusPageThemeTokens, isDark: boolean) => boxShadow: cardShadow, mb: "20px", }, - statusDot: (tone: RefinedTone) => ({ + statusDot: (tone) => ({ width: 10, height: 10, borderRadius: "50%", @@ -114,17 +150,13 @@ export const refinedStyles = (tokens: StatusPageThemeTokens, isDark: boolean) => color: tokens.text, }, heroSub: { m: 0, color: tokens.textMuted, fontSize: 13 }, - heroIcon: (tone: RefinedTone) => ({ + heroIcon: (tone) => ({ color: toneColor(tone, tokens), display: "flex", alignItems: "center", }), - chartSwitchWrap: { - display: "flex", - justifyContent: "flex-end", - mb: "12px", - }, + chartSwitchWrap: { display: "flex", justifyContent: "flex-end", mb: "12px" }, chartSwitch: { display: "inline-flex", border: `1px solid ${tokens.border}`, @@ -133,7 +165,7 @@ export const refinedStyles = (tokens: StatusPageThemeTokens, isDark: boolean) => p: "3px", gap: "2px", }, - chartSwitchButton: (active: boolean) => ({ + chartSwitchButton: (active) => ({ border: 0, background: active ? tokens.upSoft : "transparent", fontFamily: "inherit", @@ -199,29 +231,19 @@ export const refinedStyles = (tokens: StatusPageThemeTokens, isDark: boolean) => borderRadius: "999px", fontWeight: 600, }, - pillHardware: isDark - ? { - fontSize: 10, - textTransform: "uppercase", - letterSpacing: "0.08em", - padding: "2px 8px", - borderRadius: "999px", - fontWeight: 600, - color: "#60a5fa", - border: "1px solid rgba(96, 165, 250, 0.3)", - background: "rgba(96, 165, 250, 0.1)", - } - : { - fontSize: 10, - textTransform: "uppercase", - letterSpacing: "0.08em", - padding: "2px 8px", - borderRadius: "999px", - fontWeight: 600, - color: "#1d4ed8", - border: "1px solid rgba(29, 78, 216, 0.3)", - background: "rgba(29, 78, 216, 0.06)", - }, + pillHardware: { + fontSize: 10, + textTransform: "uppercase", + letterSpacing: "0.08em", + padding: "2px 8px", + borderRadius: "999px", + fontWeight: 600, + color: isDark ? "#60a5fa" : "#1d4ed8", + border: `1px solid ${ + isDark ? "rgba(96, 165, 250, 0.3)" : "rgba(29, 78, 216, 0.3)" + }`, + background: isDark ? "rgba(96, 165, 250, 0.1)" : "rgba(29, 78, 216, 0.06)", + }, monitorUrl: { fontSize: 12, color: tokens.textMuted, @@ -232,7 +254,7 @@ export const refinedStyles = (tokens: StatusPageThemeTokens, isDark: boolean) => maxWidth: 280, }, - badge: (tone: RefinedTone) => ({ + badge: (tone) => ({ fontSize: 11, fontWeight: 600, padding: "4px 10px", @@ -249,7 +271,7 @@ export const refinedStyles = (tokens: StatusPageThemeTokens, isDark: boolean) => gap: "3px", height: 42, }, - heatmapCell: (kind: RefinedHeatCell) => ({ + heatmapCell: (kind) => ({ borderRadius: "2px", background: heatCellBg[kind], opacity: kind === "empty" ? 0.4 : 1, @@ -265,7 +287,7 @@ export const refinedStyles = (tokens: StatusPageThemeTokens, isDark: boolean) => alignItems: "flex-end", height: 42, }, - bar: (kind: RefinedBarKind, heightPct: number) => ({ + bar: (kind, heightPct) => ({ background: barBg[kind], borderRadius: "2px", minHeight: 3, @@ -318,7 +340,7 @@ export const refinedStyles = (tokens: StatusPageThemeTokens, isDark: boolean) => overflow: "hidden", mt: "8px", }, - gaugeFill: (level: RefinedGaugeFill, widthPct: number) => ({ + gaugeFill: (level, widthPct) => ({ display: "block", height: "100%", background: gaugeFillBg[level], @@ -347,6 +369,4 @@ export const refinedStyles = (tokens: StatusPageThemeTokens, isDark: boolean) => }, }, }; - - return sx; }; diff --git a/client/src/Pages/StatusPage/Status/themes/shared/ThemedHeatmap.tsx b/client/src/Pages/StatusPage/Status/themes/shared/ThemedHeatmap.tsx index 2a442405c..ebd275ddc 100644 --- a/client/src/Pages/StatusPage/Status/themes/shared/ThemedHeatmap.tsx +++ b/client/src/Pages/StatusPage/Status/themes/shared/ThemedHeatmap.tsx @@ -30,7 +30,7 @@ const classify = (check: CheckSnapshot): Exclude => { export const ThemedHeatmap = ({ checks, containerSx, cellSx, classPrefix }: Props) => { const { t } = useTranslation(); - const useSxApi = Boolean(cellSx); + const useSxApi = Boolean(containerSx && cellSx); const source = checks.slice(0, CELLS).reverse(); const padded: (CheckSnapshot | null)[] = [ diff --git a/client/src/Pages/StatusPage/Status/themes/shared/ThemedHistogram.tsx b/client/src/Pages/StatusPage/Status/themes/shared/ThemedHistogram.tsx index 65027731b..af73095df 100644 --- a/client/src/Pages/StatusPage/Status/themes/shared/ThemedHistogram.tsx +++ b/client/src/Pages/StatusPage/Status/themes/shared/ThemedHistogram.tsx @@ -36,7 +36,7 @@ export const ThemedHistogram = ({ statsGap = 1, }: Props) => { const { t } = useTranslation(); - const useSxApi = Boolean(barSx); + const useSxApi = Boolean(containerSx && barSx && statsSx); const { padded, max, avg, peak } = useMemo(() => { const source = checks.slice(0, CELLS).reverse(); diff --git a/client/src/Pages/StatusPage/Status/themes/shared/ThemedInfrastructure.tsx b/client/src/Pages/StatusPage/Status/themes/shared/ThemedInfrastructure.tsx index d4334e959..8c642a344 100644 --- a/client/src/Pages/StatusPage/Status/themes/shared/ThemedInfrastructure.tsx +++ b/client/src/Pages/StatusPage/Status/themes/shared/ThemedInfrastructure.tsx @@ -118,8 +118,8 @@ export const ThemedInfrastructure = ({ monitor, sxApi, classPrefix }: Props) => return ( {gauges.map((g) => { - const heatClass = - g.value > 85 ? `${classPrefix}-hot` : g.value > 70 ? `${classPrefix}-warm` : ""; + const level = heatLevel(g.value); + const heatClass = level === "ok" ? "" : `${classPrefix}-${level}`; return ( + tone === "down" ? t.down : tone === "warn" ? t.warn : t.up; + +export const toneSoft = (tone: OverallTone, t: StatusPageThemeTokens): string => + tone === "down" ? t.downSoft : tone === "warn" ? t.warnSoft : t.upSoft; + export interface OverallStatus { tone: OverallTone; message: string;