fix(client): dashboard hover controls, card widths and font sizes
Reorder controls stayed visible after a click. The reveal used `:focus-within`, and clicking a button leaves it focused, so that card's controls stayed pinned open long after the pointer left — Failed checks being wherever focus happened to land. Now `:has(:focus-visible)`, which matches keyboard focus only, so a mouse click no longer pins them. Widths: Currently down was full-width and Recent incidents 8-wide, both with more whitespace than content. Currently down carries six columns per row so it is the densest card, but it does not need all twelve; it is now 8. Recent incidents carries four and is now 4, as are Recent errors. Declaring a narrower width is not enough on its own: a card left alone in a row gets stretched to fill it, so Currently down at 8 still rendered at 12 while it was the only card in its row. Registry and default order are now sequenced so widths pair into full rows — 8 beside 4, 6 beside 6 — which is what actually makes the narrow cards render narrow. Verified across the full catalogue, the non-admin subset and the defaults: every mid-page row fills exactly 12 with no gaps. Fonts: every secondary line used typographyLevels.s, which is 11px — body2, a de-emphasis size. The theme's body1 is 13px, so all 24 uses are now typographyLevels.m. Nothing in a card renders below body size any more; only the large figures differ, at xxl. Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
This commit is contained in:
1 parent
49af07009d
commit
c46ead515b
15 files changed
+52
-39
No files matched your search
@@ -79,7 +79,7 @@ export const CardPicker = ({
|
||||
>
|
||||
<Typography
|
||||
variant="eyebrow"
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.text.disabled}
|
||||
>
|
||||
{t(`components.sidebar.menu.${group}`, { defaultValue: group })}
|
||||
|
||||
@@ -97,7 +97,7 @@ export const CardRowLabel = ({
|
||||
</Typography>
|
||||
{secondary && (
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.text.secondary}
|
||||
noWrap
|
||||
>
|
||||
@@ -132,7 +132,7 @@ export const CardFigure = ({
|
||||
</Typography>
|
||||
{caption && (
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{caption}
|
||||
|
||||
@@ -37,7 +37,7 @@ const StatLine = ({
|
||||
gap={theme.spacing(LAYOUT.SM)}
|
||||
>
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{label}
|
||||
@@ -96,7 +96,7 @@ export const ChecksOnScheduleCard = () => {
|
||||
secondary={job.failReason}
|
||||
/>
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.error.main}
|
||||
flexShrink={0}
|
||||
>
|
||||
@@ -159,7 +159,7 @@ export const RecentErrorsCard = () => {
|
||||
action={
|
||||
errors > 0 ? (
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.error.main}
|
||||
>
|
||||
{t("pages.dashboard.cards.recentErrors.count", { count: errors })}
|
||||
@@ -183,7 +183,7 @@ export const RecentErrorsCard = () => {
|
||||
secondary={[entry.service, entry.method].filter(Boolean).join(" · ")}
|
||||
/>
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={
|
||||
entry.level === "error"
|
||||
? theme.palette.error.main
|
||||
|
||||
@@ -48,13 +48,13 @@ const Metric = ({ label, value }: { label: string; value: number | null }) => {
|
||||
gap={theme.spacing(LAYOUT.XS)}
|
||||
>
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={color}
|
||||
>
|
||||
{/* A down server reports nothing — show a dash, never 0%. */}
|
||||
|
||||
@@ -40,7 +40,7 @@ const MetricLine = ({
|
||||
gap={theme.spacing(LAYOUT.SM)}
|
||||
>
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{label}
|
||||
|
||||
@@ -92,7 +92,7 @@ export const CurrentlyDownCard = () => {
|
||||
action={
|
||||
problems.length > 0 ? (
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.error.main}
|
||||
>
|
||||
{t("pages.dashboard.cards.currentlyDown.count", {
|
||||
@@ -120,7 +120,7 @@ export const CurrentlyDownCard = () => {
|
||||
secondary={monitor.url}
|
||||
/>
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.text.secondary}
|
||||
flexShrink={0}
|
||||
>
|
||||
@@ -128,7 +128,7 @@ export const CurrentlyDownCard = () => {
|
||||
</Typography>
|
||||
{since && (
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.error.main}
|
||||
flexShrink={0}
|
||||
minWidth={DURATION_COLUMN_WIDTH}
|
||||
@@ -154,7 +154,7 @@ export const CurrentlyDownCard = () => {
|
||||
* the link and activating it navigates away.
|
||||
*/}
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.text.secondary}
|
||||
flexShrink={0}
|
||||
minWidth={STATUS_CODE_COLUMN_WIDTH}
|
||||
|
||||
@@ -79,7 +79,7 @@ export const MonitorStatusCard = () => {
|
||||
size="md"
|
||||
/>
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{t(`pages.common.monitors.status.${key}`)}
|
||||
|
||||
@@ -85,7 +85,7 @@ export const MonitorsByGroupCard = () => {
|
||||
>
|
||||
{bucket.down > 0 && (
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.error.main}
|
||||
>
|
||||
{t("pages.dashboard.cards.monitorsByType.down", {
|
||||
|
||||
@@ -85,7 +85,7 @@ export const MonitorsByTypeCard = () => {
|
||||
>
|
||||
{bucket.down > 0 && (
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.error.main}
|
||||
>
|
||||
{t("pages.dashboard.cards.monitorsByType.down", {
|
||||
|
||||
@@ -33,7 +33,7 @@ const RankingCount = ({ shown, total }: { shown: number; total: number }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{t("pages.dashboard.cards.ranking.topOf", { shown, total })}
|
||||
|
||||
@@ -44,7 +44,7 @@ export const RecentIncidentsCard = () => {
|
||||
action={
|
||||
data && data.totalActive > 0 ? (
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.error.main}
|
||||
>
|
||||
{t("pages.dashboard.cards.recentIncidents.active", {
|
||||
@@ -78,7 +78,7 @@ export const RecentIncidentsCard = () => {
|
||||
secondary={formatTimestamp(incident.startTime)}
|
||||
/>
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.text.secondary}
|
||||
sx={{ flexShrink: 0 }}
|
||||
>
|
||||
@@ -88,7 +88,7 @@ export const RecentIncidentsCard = () => {
|
||||
</Typography>
|
||||
{incident.resolutionType === "manual" && (
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.warning.main}
|
||||
sx={{ flexShrink: 0 }}
|
||||
>
|
||||
|
||||
@@ -27,7 +27,7 @@ const StatLine = ({ label, value }: { label: string; value: string }) => {
|
||||
gap={theme.spacing(LAYOUT.SM)}
|
||||
>
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{label}
|
||||
@@ -218,7 +218,7 @@ export const StatusPagesCard = () => {
|
||||
})}
|
||||
/>
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.text.secondary}
|
||||
flexShrink={0}
|
||||
>
|
||||
|
||||
@@ -52,9 +52,15 @@ export const DashboardCard = ({
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
overflow: "hidden",
|
||||
// The header controls stay invisible until the card is hovered or
|
||||
// something inside it takes focus, so a settled dashboard is quiet.
|
||||
"&:hover .dashboard-card-slot, &:focus-within .dashboard-card-slot": {
|
||||
// The header controls stay invisible until the card is hovered, so a
|
||||
// settled dashboard is quiet.
|
||||
//
|
||||
// `:has(:focus-visible)` rather than `:focus-within`: a mouse click
|
||||
// leaves the button focused, and :focus-within would then pin that
|
||||
// card's controls open long after the pointer had left. focus-visible
|
||||
// only matches keyboard focus, which is the case that actually needs
|
||||
// the controls revealed.
|
||||
"&:hover .dashboard-card-slot, &:has(:focus-visible) .dashboard-card-slot": {
|
||||
opacity: 1,
|
||||
pointerEvents: "auto",
|
||||
},
|
||||
@@ -158,7 +164,7 @@ export const CardMessage = ({ text, action }: { text: string; action?: ReactNode
|
||||
gap={theme.spacing(LAYOUT.SM)}
|
||||
>
|
||||
<Typography
|
||||
fontSize={typographyLevels.s}
|
||||
fontSize={typographyLevels.m}
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
{text}
|
||||
|
||||
@@ -61,12 +61,14 @@ export interface CardDefinition {
|
||||
|
||||
// The cards a fresh dashboard starts with. Chosen so a brand new install still
|
||||
// shows something real: Checkmate server works with zero monitors.
|
||||
// Ordered so the declared widths pair into full rows: a card left alone in a
|
||||
// row is stretched to fill it, so 8- and 4-wide cards are kept adjacent.
|
||||
export const DEFAULT_CARD_IDS: CardId[] = [
|
||||
"monitorStatus",
|
||||
"currentlyDown",
|
||||
"failedChecks",
|
||||
"recentIncidents",
|
||||
"monitorsByType",
|
||||
"recentIncidents",
|
||||
"checkmateServer",
|
||||
];
|
||||
|
||||
|
||||
@@ -24,7 +24,10 @@ import type { CardDefinition, CardId } from "./cards";
|
||||
// Declared width is the minimum number of columns a card needs. The grid may
|
||||
// stretch the last card of a short row; nothing here is a user setting.
|
||||
//
|
||||
// Order is the order the picker lists them in, grouped by sidebar section.
|
||||
// Order is the render order, and the order the picker lists them in. Cards are
|
||||
// sequenced so their declared widths pair into full rows: a card left alone in
|
||||
// a row gets stretched to fill it, so an 8-wide card is kept next to a 4-wide
|
||||
// one rather than being widened back to 12.
|
||||
export const CARD_REGISTRY: CardDefinition[] = [
|
||||
{
|
||||
id: "monitorStatus",
|
||||
@@ -37,9 +40,18 @@ export const CARD_REGISTRY: CardDefinition[] = [
|
||||
id: "currentlyDown",
|
||||
key: "currentlyDown",
|
||||
group: "uptime",
|
||||
width: 12,
|
||||
// Six columns per row — the densest card — but it does not need the
|
||||
// full width, and 8 leaves room for a 4-wide card beside it.
|
||||
width: 8,
|
||||
component: CurrentlyDownCard,
|
||||
},
|
||||
{
|
||||
id: "failedChecks",
|
||||
key: "failedChecks",
|
||||
group: "checks",
|
||||
width: 4,
|
||||
component: FailedChecksCard,
|
||||
},
|
||||
{
|
||||
id: "slowestMonitors",
|
||||
key: "slowestMonitors",
|
||||
@@ -79,7 +91,7 @@ export const CARD_REGISTRY: CardDefinition[] = [
|
||||
id: "recentIncidents",
|
||||
key: "recentIncidents",
|
||||
group: "incidents",
|
||||
width: 8,
|
||||
width: 4,
|
||||
component: RecentIncidentsCard,
|
||||
},
|
||||
{
|
||||
@@ -110,13 +122,6 @@ export const CARD_REGISTRY: CardDefinition[] = [
|
||||
width: 4,
|
||||
component: StatusPagesCard,
|
||||
},
|
||||
{
|
||||
id: "failedChecks",
|
||||
key: "failedChecks",
|
||||
group: "checks",
|
||||
width: 4,
|
||||
component: FailedChecksCard,
|
||||
},
|
||||
{
|
||||
id: "checkmateServer",
|
||||
key: "checkmateServer",
|
||||
@@ -137,7 +142,7 @@ export const CARD_REGISTRY: CardDefinition[] = [
|
||||
id: "recentErrors",
|
||||
key: "recentErrors",
|
||||
group: "logs",
|
||||
width: 6,
|
||||
width: 4,
|
||||
adminOnly: true,
|
||||
component: RecentErrorsCard,
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user