From de8eb53dd4973acab738c0e512db6aee3b8f5beb Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Thu, 3 Sep 2026 01:38:51 -0400 Subject: [PATCH] feat(client): one control height of 32px Buttons, inputs, selects, pickers, toggles and tabs all declared 34 while IconButton and the PageHeader action sat at 32, so anything mixing them in a row was 2px out of alignment. Everything now reads CONTROL_HEIGHT (32) from constants, so the number lives in one place instead of nine. Two content-box fixes were needed to make 32 safe, both measured in the browser rather than assumed: - MUI's button line-height is 1.75 - 22.75px at our 13px base - which with 6px padding needs 34.75px. It was already over its own box at 34 and would have clipped at 32. Now 1.2, which leaves headroom for the taller scripts we ship (ar, th, vi, ja, zh). - MUI pads the inner input 8.5px top and bottom, so an input's content needed 35.7px inside a 32px shell and was being cut off by overflow:hidden. Padding is zeroed in the theme for both the input and the Select's div, which centres instead. Verified in the DOM: button, select and input all render at exactly 32 with scrollHeight equal to height, so nothing is clipped. Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd --- .../Components/design-elements/PageHeader.tsx | 3 ++- .../Components/design-elements/SkeletonCard.tsx | 3 ++- client/src/Components/design-elements/Tabs.tsx | 5 +++-- client/src/Components/inputs/Button.tsx | 8 ++++++-- client/src/Components/inputs/ColorPicker.tsx | 3 ++- client/src/Components/inputs/DatePicker.tsx | 3 ++- client/src/Components/inputs/Select.tsx | 3 ++- client/src/Components/inputs/TextInput.tsx | 4 ++-- client/src/Components/inputs/TimePicker.tsx | 3 ++- client/src/Components/inputs/ToggleButton.tsx | 3 ++- client/src/Utils/Theme/Theme.ts | 17 +++++++++++++++++ client/src/Utils/Theme/constants.ts | 7 +++++++ 12 files changed, 49 insertions(+), 13 deletions(-) diff --git a/client/src/Components/design-elements/PageHeader.tsx b/client/src/Components/design-elements/PageHeader.tsx index eccf69b46..28b447bc7 100644 --- a/client/src/Components/design-elements/PageHeader.tsx +++ b/client/src/Components/design-elements/PageHeader.tsx @@ -6,6 +6,7 @@ import { Link as RouterLink } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { useTheme } from "@mui/material/styles"; import { ChevronLeft, HelpCircle, FileText, Code } from "lucide-react"; +import { CONTROL_HEIGHT } from "@/Utils/Theme/constants"; import type { ReactNode } from "react"; @@ -145,7 +146,7 @@ export const PageHeader = ({ alignItems: "center", justifyContent: "center", width: 36, - height: 32, + height: CONTROL_HEIGHT, color: theme.palette.text.secondary, borderRight: idx < linkItems.length - 1 diff --git a/client/src/Components/design-elements/SkeletonCard.tsx b/client/src/Components/design-elements/SkeletonCard.tsx index 05f0c462a..66bb0b560 100644 --- a/client/src/Components/design-elements/SkeletonCard.tsx +++ b/client/src/Components/design-elements/SkeletonCard.tsx @@ -1,4 +1,5 @@ import { Box, Stack, useTheme } from "@mui/material"; +import { CONTROL_HEIGHT } from "@/Utils/Theme/constants"; interface SkeletonCardProps { width?: number | string | { xs?: number | string; md?: number | string }; @@ -44,7 +45,7 @@ export const SkeletonCard = ({ const blockStyle = { width: 55, - height: 34, + height: CONTROL_HEIGHT, borderRadius: theme.shape.borderRadius, background: theme.palette.secondary.main, border: `1px solid ${theme.palette.divider}`, diff --git a/client/src/Components/design-elements/Tabs.tsx b/client/src/Components/design-elements/Tabs.tsx index 4084b6510..13ccaae77 100644 --- a/client/src/Components/design-elements/Tabs.tsx +++ b/client/src/Components/design-elements/Tabs.tsx @@ -2,6 +2,7 @@ import MuiTabs from "@mui/material/Tabs"; import type { TabsProps } from "@mui/material/Tabs"; import { useTheme } from "@mui/material/styles"; import { typographyLevels } from "@/Utils/Theme/Palette"; +import { CONTROL_HEIGHT } from "@/Utils/Theme/constants"; interface CustomTabsProps extends TabsProps {} export const Tabs = (props: CustomTabsProps) => { @@ -9,7 +10,7 @@ export const Tabs = (props: CustomTabsProps) => { return ( { textTransform: "none", fontSize: typographyLevels.m, fontWeight: 500, - minHeight: 34, + minHeight: CONTROL_HEIGHT, padding: theme.spacing(1, 0), paddingBottom: 0, minWidth: "auto", diff --git a/client/src/Components/inputs/Button.tsx b/client/src/Components/inputs/Button.tsx index 4eae654b2..65540b6c4 100644 --- a/client/src/Components/inputs/Button.tsx +++ b/client/src/Components/inputs/Button.tsx @@ -1,7 +1,7 @@ import Button from "@mui/material/Button"; import type { ButtonProps } from "@mui/material/Button"; import { useTheme, darken } from "@mui/material/styles"; -import { HOVER } from "@/Utils/Theme/constants"; +import { CONTROL_HEIGHT, HOVER } from "@/Utils/Theme/constants"; const PALETTE_COLORS = [ "primary", @@ -43,7 +43,11 @@ export const ButtonInput = ({ sx, ...props }: ButtonProps) => { {...props} sx={{ textTransform: "none", - height: 34, + height: CONTROL_HEIGHT, + // MUI's button line-height is 1.75 (22.75px at 13px), which with the + // 6px padding needs 34.75px - already over its own box at 34, and + // clipping at 32. 1.2 leaves room for tall scripts too. + lineHeight: 1.2, fontWeight: 400, borderRadius: 2, whiteSpace: "nowrap", diff --git a/client/src/Components/inputs/ColorPicker.tsx b/client/src/Components/inputs/ColorPicker.tsx index de736c85d..79c4d63fc 100644 --- a/client/src/Components/inputs/ColorPicker.tsx +++ b/client/src/Components/inputs/ColorPicker.tsx @@ -4,6 +4,7 @@ import { typographyLevels } from "@/Utils/Theme/Palette"; import { useTheme } from "@mui/material"; import Stack from "@mui/material/Stack"; import { FieldLabel } from "@/Components/inputs/FieldLabel"; +import { CONTROL_HEIGHT } from "@/Utils/Theme/constants"; interface ColorPickerProps extends MuiColorInputProps { fieldLabel?: string; @@ -18,7 +19,7 @@ export const ColorInput = ({ fieldLabel, required, ...props }: ColorPickerProps) sx={{ "& .MuiOutlinedInput-root": { borderRadius: theme.shape.borderRadius, - height: 34, + height: CONTROL_HEIGHT, fontSize: typographyLevels.base, }, "& .MuiOutlinedInput-notchedOutline": { diff --git a/client/src/Components/inputs/DatePicker.tsx b/client/src/Components/inputs/DatePicker.tsx index 74dec5adf..1cb9f7738 100644 --- a/client/src/Components/inputs/DatePicker.tsx +++ b/client/src/Components/inputs/DatePicker.tsx @@ -8,6 +8,7 @@ import { FieldLabel } from "./FieldLabel"; import { Calendar } from "lucide-react"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { LocalizationProvider } from "@mui/x-date-pickers"; +import { CONTROL_HEIGHT } from "@/Utils/Theme/constants"; interface DatePickerComponentProps extends Omit, "label"> { fieldLabel?: string; @@ -55,7 +56,7 @@ export const DatePickerComponent = ({ flexDirection: "row-reverse", }, "& input": { - minHeight: 34, + minHeight: CONTROL_HEIGHT, p: 0, pl: theme.spacing(3), pr: theme.spacing(5), diff --git a/client/src/Components/inputs/Select.tsx b/client/src/Components/inputs/Select.tsx index f5670887d..8481ad4f4 100644 --- a/client/src/Components/inputs/Select.tsx +++ b/client/src/Components/inputs/Select.tsx @@ -6,6 +6,7 @@ import Stack from "@mui/material/Stack"; import { FieldLabel } from "./FieldLabel"; import { ChevronDown } from "lucide-react"; import Typography from "@mui/material/Typography"; +import { CONTROL_HEIGHT } from "@/Utils/Theme/constants"; interface SelectInputProps extends Omit, "label"> { fieldLabel?: string; @@ -71,7 +72,7 @@ const SelectInputInner = ( /> )} sx={{ - height: "34px", + height: `${CONTROL_HEIGHT}px`, "& .MuiSelect-select": { display: "flex", alignItems: "center", diff --git a/client/src/Components/inputs/TextInput.tsx b/client/src/Components/inputs/TextInput.tsx index 8810136ac..392bfccd9 100644 --- a/client/src/Components/inputs/TextInput.tsx +++ b/client/src/Components/inputs/TextInput.tsx @@ -5,7 +5,7 @@ import { typographyLevels } from "@/Utils/Theme/Palette"; import { useTheme } from "@mui/material/styles"; import Stack from "@mui/material/Stack"; import { FieldLabel } from "./FieldLabel"; -import { LAYOUT } from "@/Utils/Theme/constants"; +import { CONTROL_HEIGHT, LAYOUT } from "@/Utils/Theme/constants"; interface TextInputProps extends Omit { fieldLabel?: string; @@ -22,7 +22,7 @@ export const TextInput = forwardRef(function T width: "100%", "& .MuiOutlinedInput-root": { borderRadius: theme.shape.borderRadius, - height: props.multiline ? "auto" : 34, + height: props.multiline ? "auto" : CONTROL_HEIGHT, fontSize: typographyLevels.base, overflow: "hidden", }, diff --git a/client/src/Components/inputs/TimePicker.tsx b/client/src/Components/inputs/TimePicker.tsx index b82df1f15..dfefc2f3c 100644 --- a/client/src/Components/inputs/TimePicker.tsx +++ b/client/src/Components/inputs/TimePicker.tsx @@ -7,6 +7,7 @@ import Typography from "@mui/material/Typography"; import { FieldLabel } from "./FieldLabel"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { LocalizationProvider } from "@mui/x-date-pickers"; +import { CONTROL_HEIGHT } from "@/Utils/Theme/constants"; interface TimePickerComponentProps extends Omit, "label"> { fieldLabel?: string; @@ -33,7 +34,7 @@ export const TimePickerComponent = ({ sx: { width: "fit-content", "& input": { - minHeight: 34, + minHeight: CONTROL_HEIGHT, p: 0, px: theme.spacing(5), }, diff --git a/client/src/Components/inputs/ToggleButton.tsx b/client/src/Components/inputs/ToggleButton.tsx index 467c232b6..0319578ae 100644 --- a/client/src/Components/inputs/ToggleButton.tsx +++ b/client/src/Components/inputs/ToggleButton.tsx @@ -4,6 +4,7 @@ import type { ToggleButtonProps } from "@mui/material/ToggleButton"; import type { ToggleButtonGroupProps } from "@mui/material/ToggleButtonGroup"; import { useTheme } from "@mui/material/styles"; import { alpha } from "@mui/material/styles"; +import { CONTROL_HEIGHT } from "@/Utils/Theme/constants"; export const ToggleButtonInput = ({ sx, ...props }: ToggleButtonProps) => { const theme = useTheme(); @@ -20,7 +21,7 @@ export const ToggleButtonInput = ({ sx, ...props }: ToggleButtonProps) => { sx={{ px: 8, textTransform: "none", - height: 34, + height: CONTROL_HEIGHT, fontWeight: 400, whiteSpace: "nowrap", textOverflow: "ellipsis", diff --git a/client/src/Utils/Theme/Theme.ts b/client/src/Utils/Theme/Theme.ts index bda2c3806..a480686a7 100644 --- a/client/src/Utils/Theme/Theme.ts +++ b/client/src/Utils/Theme/Theme.ts @@ -202,7 +202,24 @@ export const theme = (mode: string, palette: any) => // jumping to near-black. Focus: the brand, at 2px. MuiOutlinedInput: { styleOverrides: { + // MUI pads the inner input 8.5px top and bottom, which with an + // 18.7px line-height needs 35.7px - taller than the control + // itself, so the text was being clipped by overflow:hidden. + input: { + paddingTop: 0, + paddingBottom: 0, + height: "100%", + }, root: ({ theme }) => ({ + // The Select renders a div rather than an input, so it needs + // the same treatment to sit inside the control height. + "& .MuiSelect-select": { + paddingTop: 0, + paddingBottom: 0, + display: "flex", + alignItems: "center", + minHeight: "unset", + }, "& .MuiOutlinedInput-notchedOutline": { borderColor: theme.palette.divider, transition: "border-color 0.15s ease", diff --git a/client/src/Utils/Theme/constants.ts b/client/src/Utils/Theme/constants.ts index 70ddcd407..1d5ed4991 100644 --- a/client/src/Utils/Theme/constants.ts +++ b/client/src/Utils/Theme/constants.ts @@ -18,6 +18,13 @@ export const LAYOUT = { XXL: 16, } as const; +/** + * Shared height for anything that can sit in a row with something else - + * buttons, inputs, selects, pickers, toggles, tabs, icon buttons. One number so + * they share a baseline instead of drifting 2px apart. + */ +export const CONTROL_HEIGHT = 32; + export const HOVER = { DARKEN: 0.06, // This is a coefficient for darkening function ROW: 0.025, // Overlay alpha for hoverable rows and cards