From 3ddc6ef1cb17ba05a2fb2c4950f755e4dc406fe9 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Thu, 3 Sep 2026 01:27:42 -0400 Subject: [PATCH] fix(client): one border story for every input Inputs disagreed on both hover and focus. The theme's MuiOutlinedInput hovered to text.primary - near-black in light mode - while Select, TextInput and DatePicker each overrode hover back to `divider`, which is the resting colour, so those had no hover cue at all. Four behaviours across four components. Now defined once in the theme and inherited by every outlined input: rest divider 1.55:1 light / 1.73:1 dark hover text.disabled 3.64:1 light / 4.66:1 dark focus primary, 2px 5.93:1 light / 6.36:1 dark error error.main The per-component overrides in Select, TextInput and DatePicker are gone, so there is one place to change this next time. Focus in dark mode takes primary.light. The brand green is only 2.86:1 on the dark ground, which made a focused input dimmer than a hovered one. `main` is deliberately left alone: it is the fill for contained buttons, and lightening it would flip their label to dark text across 31 sites. Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd --- client/src/Components/inputs/DatePicker.tsx | 4 +++- client/src/Components/inputs/Select.tsx | 4 ---- client/src/Components/inputs/TextInput.tsx | 6 ------ client/src/Utils/Theme/Palette.ts | 5 +++++ client/src/Utils/Theme/Theme.ts | 23 ++++++++++++++++----- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/client/src/Components/inputs/DatePicker.tsx b/client/src/Components/inputs/DatePicker.tsx index c63c5ce08..74dec5adf 100644 --- a/client/src/Components/inputs/DatePicker.tsx +++ b/client/src/Components/inputs/DatePicker.tsx @@ -66,7 +66,9 @@ export const DatePickerComponent = ({ }, "&:not(:has(.Mui-disabled)):not(:has(.Mui-error)) .MuiOutlinedInput-root:not(:has(input:focus)):hover fieldset": { - borderColor: error ? theme.palette.error.main : theme.palette.divider, + borderColor: error + ? theme.palette.error.main + : theme.palette.text.disabled, }, }, }, diff --git a/client/src/Components/inputs/Select.tsx b/client/src/Components/inputs/Select.tsx index 0077d2be6..f5670887d 100644 --- a/client/src/Components/inputs/Select.tsx +++ b/client/src/Components/inputs/Select.tsx @@ -83,10 +83,6 @@ const SelectInputInner = ( }, "& .MuiOutlinedInput-notchedOutline": { borderRadius: theme.shape.borderRadius, - borderColor: theme.palette.divider, - }, - "&:hover .MuiOutlinedInput-notchedOutline": { - borderColor: theme.palette.divider, }, ...props.sx, }} diff --git a/client/src/Components/inputs/TextInput.tsx b/client/src/Components/inputs/TextInput.tsx index a246aaf04..8810136ac 100644 --- a/client/src/Components/inputs/TextInput.tsx +++ b/client/src/Components/inputs/TextInput.tsx @@ -26,12 +26,6 @@ export const TextInput = forwardRef(function T fontSize: typographyLevels.base, overflow: "hidden", }, - "& .MuiOutlinedInput-notchedOutline": { - borderColor: theme.palette.divider, - }, - "&:hover .MuiOutlinedInput-notchedOutline": { - borderColor: theme.palette.divider, - }, "& .MuiFormHelperText-root": { marginLeft: 0, marginRight: 0, diff --git a/client/src/Utils/Theme/Palette.ts b/client/src/Utils/Theme/Palette.ts index c64dbaf8f..19ec22c85 100644 --- a/client/src/Utils/Theme/Palette.ts +++ b/client/src/Utils/Theme/Palette.ts @@ -112,6 +112,11 @@ export const lightPalette = { export const darkPalette = { primary: { main: colors.brandGreen, + // The dark ground needs a lighter green for anything drawn as a line + // rather than a fill - focus rings especially, which were dimmer than + // their own hover state. Leaving `main` alone keeps contained buttons + // white-on-green instead of flipping them to dark text. + light: colors.brandGreenLight, }, action: { rowHover: alpha("#FFFFFF", HOVER.ROW), diff --git a/client/src/Utils/Theme/Theme.ts b/client/src/Utils/Theme/Theme.ts index 46dd97f48..8c3ef9e76 100644 --- a/client/src/Utils/Theme/Theme.ts +++ b/client/src/Utils/Theme/Theme.ts @@ -189,17 +189,30 @@ export const theme = (mode: string, palette: any) => }, }, + // One border story for every outlined input - text fields, selects, + // autocompletes, date and colour pickers all route through this. + // Resting: divider. Hover: a step darker, so there is a cue without + // jumping to near-black. Focus: the brand, at 2px. MuiOutlinedInput: { styleOverrides: { root: ({ theme }) => ({ - "&:hover .MuiOutlinedInput-notchedOutline": { + "& .MuiOutlinedInput-notchedOutline": { + borderColor: theme.palette.divider, + transition: "border-color 0.15s ease", + }, + "&:hover:not(.Mui-focused):not(.Mui-disabled) .MuiOutlinedInput-notchedOutline": + { + borderColor: theme.palette.text.disabled, + }, + "&.Mui-focused .MuiOutlinedInput-notchedOutline": { borderColor: theme.palette.mode === "dark" - ? "rgba(255, 255, 255, 0.23)" - : theme.palette.text.primary, + ? theme.palette.primary.light + : theme.palette.primary.main, + borderWidth: 2, }, - "&.Mui-focused .MuiOutlinedInput-notchedOutline": { - borderColor: palette.primary.main, + "&.Mui-error .MuiOutlinedInput-notchedOutline": { + borderColor: theme.palette.error.main, }, }), },