fix(client): sidebar selection ordering, and clear second review pass
The sidebar regression is the real fix here. NavItem paints the selected
item with action.selected but hovers unselected items with MUI's default
action.hover. Once the sidebar moved onto `paper`, selected composited to
rgb(31,38,41) - darker than the ground - while hover came out lighter at
rgb(46,46,51), so hovering any other item made it read as more selected
than the actual selection. Both states now come from the same family
(selectedHover / rowHover), so selected always reads stronger.
Also from review:
- Dropped "Inter" from the status page SANS_STACK. Its webfont went with
the Google Fonts import, so on Windows and Linux the stack was silently
falling through to Segoe UI / Roboto on pages users publish.
- Button focus rings now use the same dark-mode green as input focus
rings. They were still on primary.main at 2.86:1, which is the exact
problem primary.light was added to solve.
- Removed 13 size={16} props made redundant by the Icon default change.
The load-bearing pins stay: size={20} on the public status page, and
the header pins that were relying on the old default.
- Documented that alpha() replaces rather than multiplies the alpha
channel in StatusBox, so the grid is coupled to `divider` being opaque.
- Noted on the isRowSelected JSDoc that the small-screen card branch does
not style selection, same limitation getRowSx already had.
Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
This commit is contained in:
1 parent
3ddc6ef1cb
commit
dbe0e2f4e3
8 files changed
+36
-79
No files matched your search
@@ -35,7 +35,10 @@ export const BGBox = ({ children, sx }: StatusBoxProps) => {
|
||||
pointerEvents: "none",
|
||||
// Deliberately far fainter than `divider` - this is texture
|
||||
// behind the number, not a rule, and it must never compete
|
||||
// with the box's own border.
|
||||
// with the box's own border. Note alpha() *replaces* the
|
||||
// alpha channel, so this relies on `divider` being an opaque
|
||||
// hex; if it ever goes translucent this gets stronger, not
|
||||
// weaker.
|
||||
backgroundImage: `
|
||||
linear-gradient(${alpha(theme.palette.divider, 0.4)} 1px, transparent 1px),
|
||||
linear-gradient(90deg, ${alpha(theme.palette.divider, 0.4)} 1px, transparent 1px)
|
||||
|
||||
@@ -52,7 +52,11 @@ type DataTableProps<T extends { id?: string | number; _id?: string | number }> =
|
||||
emptyViewText?: string;
|
||||
emptyViewPositive?: boolean;
|
||||
getRowSx?: (row: T) => SxProps<Theme>;
|
||||
/** Marks a row selected so it gets the shared selected + hover treatment. */
|
||||
/**
|
||||
* Marks a row selected so it gets the shared selected + hover treatment.
|
||||
* Desktop table only - the small-screen card branch does not style
|
||||
* selection yet, same as `getRowSx`.
|
||||
*/
|
||||
isRowSelected?: (row: T) => boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -64,12 +64,7 @@ export const HeaderMonitorControls = ({
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
loading={isPosting}
|
||||
startIcon={
|
||||
<Icon
|
||||
icon={Mail}
|
||||
size={16}
|
||||
/>
|
||||
}
|
||||
startIcon={<Icon icon={Mail} />}
|
||||
onClick={async () => {
|
||||
await post(`/notifications/test/all`, { monitorId: monitor.id });
|
||||
}}
|
||||
@@ -82,12 +77,7 @@ export const HeaderMonitorControls = ({
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
startIcon={
|
||||
<Icon
|
||||
icon={Bug}
|
||||
size={16}
|
||||
/>
|
||||
}
|
||||
startIcon={<Icon icon={Bug} />}
|
||||
onClick={() => {
|
||||
navigate(`/incidents/${monitor?.id}`);
|
||||
}}
|
||||
@@ -99,19 +89,7 @@ export const HeaderMonitorControls = ({
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
loading={isPosting}
|
||||
startIcon={
|
||||
monitor?.isActive ? (
|
||||
<Icon
|
||||
icon={Pause}
|
||||
size={16}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
icon={Play}
|
||||
size={16}
|
||||
/>
|
||||
)
|
||||
}
|
||||
startIcon={monitor?.isActive ? <Icon icon={Pause} /> : <Icon icon={Play} />}
|
||||
onClick={async () => {
|
||||
await post(`/monitors/pause/${monitor.id}`, {});
|
||||
await refetch();
|
||||
@@ -124,12 +102,7 @@ export const HeaderMonitorControls = ({
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
startIcon={
|
||||
<Icon
|
||||
icon={Settings}
|
||||
size={16}
|
||||
/>
|
||||
}
|
||||
startIcon={<Icon icon={Settings} />}
|
||||
onClick={() => navigate(`/${path}/configure/${monitor.id}`)}
|
||||
>
|
||||
{t("common.buttons.configure")}
|
||||
@@ -176,19 +149,7 @@ export const HeaderDeleteControls = ({
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
loading={isPosting}
|
||||
startIcon={
|
||||
monitor?.isActive ? (
|
||||
<Icon
|
||||
icon={Pause}
|
||||
size={16}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
icon={Play}
|
||||
size={16}
|
||||
/>
|
||||
)
|
||||
}
|
||||
startIcon={monitor?.isActive ? <Icon icon={Pause} /> : <Icon icon={Play} />}
|
||||
onClick={async () => {
|
||||
await post(`/monitors/pause/${monitor.id}`, {});
|
||||
await refetch();
|
||||
@@ -201,12 +162,7 @@ export const HeaderDeleteControls = ({
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
startIcon={
|
||||
<Icon
|
||||
icon={Trash}
|
||||
size={16}
|
||||
/>
|
||||
}
|
||||
startIcon={<Icon icon={Trash} />}
|
||||
onClick={() => {
|
||||
onDelete?.();
|
||||
}}
|
||||
|
||||
@@ -185,10 +185,7 @@ export const AuthFooter = ({ collapsed, accountMenuItems }: AuthFooterProps) =>
|
||||
}}
|
||||
sx={menuItemSx}
|
||||
>
|
||||
<Icon
|
||||
icon={mode === "light" ? Moon : Sun}
|
||||
size={16}
|
||||
/>
|
||||
<Icon icon={mode === "light" ? Moon : Sun} />
|
||||
{mode === "light"
|
||||
? t("pages.settings.form.ui.option.theme.dark")
|
||||
: t("pages.settings.form.ui.option.theme.light")}
|
||||
|
||||
@@ -26,10 +26,13 @@ export const NavItem = ({
|
||||
? theme.palette.sidebar.accent
|
||||
: theme.palette.text.secondary;
|
||||
|
||||
// Both states come from the same family so selected always reads stronger
|
||||
// than a hovered neighbour. MUI's default action.hover is lighter than
|
||||
// action.selected on a dark ground, which inverted the two.
|
||||
const buttonBgColor = selected ? theme.palette.action.selected : "transparent";
|
||||
const buttonBgHoverColor = selected
|
||||
? theme.palette.action.selected
|
||||
: theme.palette.action.hover;
|
||||
? theme.palette.action.selectedHover
|
||||
: theme.palette.action.rowHover;
|
||||
const fontWeight = selected ? 600 : 400;
|
||||
return (
|
||||
<Tooltip
|
||||
|
||||
@@ -149,7 +149,6 @@ const SummaryIncidentItem = ({ incident }: { incident: IncidentSummaryItem }) =>
|
||||
>
|
||||
<Icon
|
||||
icon={Globe}
|
||||
size={16}
|
||||
color={theme.palette.text.secondary}
|
||||
/>
|
||||
<Typography
|
||||
@@ -221,32 +220,17 @@ export const SummaryCardStats = ({ summary }: SummaryCardStatsProps) => {
|
||||
return (
|
||||
<SummaryCard title={t("pages.incidents.summaryCard.incidentStats.title")}>
|
||||
<SummaryItem
|
||||
icon={
|
||||
<Icon
|
||||
icon={Bell}
|
||||
size={16}
|
||||
/>
|
||||
}
|
||||
icon={<Icon icon={Bell} />}
|
||||
label={t("pages.incidents.summaryCard.incidentStats.totalIncidents")}
|
||||
value={summary?.total || 0}
|
||||
/>
|
||||
<SummaryItem
|
||||
icon={
|
||||
<Icon
|
||||
icon={TriangleAlert}
|
||||
size={16}
|
||||
/>
|
||||
}
|
||||
icon={<Icon icon={TriangleAlert} />}
|
||||
label={t("pages.incidents.summaryCard.incidentStats.mostAffectedMonitor")}
|
||||
value={mostAffected}
|
||||
/>
|
||||
<SummaryItem
|
||||
icon={
|
||||
<Icon
|
||||
icon={Wrench}
|
||||
size={16}
|
||||
/>
|
||||
}
|
||||
icon={<Icon icon={Wrench} />}
|
||||
label={t("pages.incidents.summaryCard.incidentStats.avgResolutionTime")}
|
||||
value={
|
||||
summary.total > 0
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// No "Inter" here: its webfont was never self-hosted, so once the Google Fonts
|
||||
// import went it resolved to a fallback anyway. Listing it only made the stack
|
||||
// look intentional on machines that happen to have Inter installed.
|
||||
export const SANS_STACK =
|
||||
'-apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, sans-serif';
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
||||
|
||||
export const BOLD_SANS_STACK = `ui-sans-serif, ${SANS_STACK}`;
|
||||
|
||||
|
||||
@@ -134,7 +134,14 @@ export const theme = (mode: string, palette: any) =>
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
"&.Mui-focusVisible": {
|
||||
outline: `1px solid ${theme.palette.primary.main}`,
|
||||
// Same green as the input focus ring - `main` is only
|
||||
// 2.86:1 on the dark ground, so keyboard focus was
|
||||
// barely visible there.
|
||||
outline: `1px solid ${
|
||||
theme.palette.mode === "dark"
|
||||
? theme.palette.primary.light
|
||||
: theme.palette.primary.main
|
||||
}`,
|
||||
outlineOffset: 2,
|
||||
},
|
||||
}),
|
||||
|
||||
Reference in new issue
Block a user