Addresses review findings on the hover-states change. StatBox's hover was a no-op. `GradientBox` always paints an opaque `linear-gradient` via the `background` shorthand, which populates background-image and covers any `backgroundColor` beneath it, so the new hover was invisible — a regression from the `opacity: 0.95` it replaced. The overlay now layers onto the same gradient, and moves into `GradientBox` where `bg` is in scope, behind an `interactive` prop. The mobile card branch gated its cursor and hover on `onRowClick` alone, so expandable-row tables below the md breakpoint rendered cards that expand on click but showed no pointer and no hover — the exact inconsistency this change set out to fix, reintroduced on mobile. Both branches now share one `isInteractive` flag. The row action gear used the same overlay as the row beneath it. Alpha overlays compose, so it was not invisible as reported, but the resulting delta was 6 levels — too faint for a nested control. Adds `HOVER.CONTROL` (0.05) and an `action.controlHover` token for controls nested inside a hoverable row: the gear and the pagination arrows. Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
97 lines
2.5 KiB
TypeScript
97 lines
2.5 KiB
TypeScript
import Stack from "@mui/material/Stack";
|
|
import Typography from "@mui/material/Typography";
|
|
import Box from "@mui/material/Box";
|
|
import { useTheme } from "@mui/material/styles";
|
|
import { lighten } from "@mui/material/styles";
|
|
import useMediaQuery from "@mui/material/useMediaQuery";
|
|
import type { PaletteKey } from "@/Utils/Theme/Theme";
|
|
import { BaseBox, TooltipWithInfo } from "@/Components/design-elements";
|
|
import type { SxProps } from "@mui/material";
|
|
|
|
type GradientBox = React.PropsWithChildren<{
|
|
palette?: PaletteKey;
|
|
sx?: SxProps;
|
|
interactive?: boolean;
|
|
}>;
|
|
|
|
export const GradientBox = ({ children, palette, sx, interactive }: GradientBox) => {
|
|
const theme = useTheme();
|
|
const isSmall = useMediaQuery(theme.breakpoints.down("md"));
|
|
const isLight = theme.palette.mode === "light";
|
|
const paper = theme.palette.background.paper;
|
|
const paperStart = lighten(paper, isLight ? 0.06 : 0.08);
|
|
const paperEnd = paper;
|
|
const bg = palette
|
|
? `linear-gradient(135deg, ${theme.palette[palette].light} 0%, ${theme.palette[palette].main} 100%)`
|
|
: `linear-gradient(135deg, ${paperStart} 0%, ${paperEnd} 100%)`;
|
|
|
|
return (
|
|
<BaseBox
|
|
sx={{
|
|
padding: `${theme.spacing(4)} ${theme.spacing(8)}`,
|
|
width: isSmall ? `100%` : `calc(25% - (3 * ${theme.spacing(8)} / 4))`,
|
|
background: bg,
|
|
...(interactive && {
|
|
cursor: "pointer",
|
|
"&:hover": {
|
|
backgroundImage: `linear-gradient(${theme.palette.action.rowHover}, ${theme.palette.action.rowHover}), ${bg}`,
|
|
},
|
|
}),
|
|
...sx,
|
|
}}
|
|
>
|
|
{children}
|
|
</BaseBox>
|
|
);
|
|
};
|
|
|
|
type StatBoxProps = React.PropsWithChildren<{
|
|
title: string;
|
|
subtitle: string;
|
|
palette?: PaletteKey;
|
|
sx?: SxProps;
|
|
tooltip?: string;
|
|
onClick?: () => void;
|
|
}>;
|
|
|
|
export const StatBox = ({
|
|
title,
|
|
subtitle,
|
|
palette,
|
|
children,
|
|
sx,
|
|
tooltip,
|
|
onClick,
|
|
}: StatBoxProps) => {
|
|
const theme = useTheme();
|
|
const textColor = palette ? theme.palette[palette].contrastText : "inherit";
|
|
|
|
return (
|
|
<GradientBox
|
|
palette={palette}
|
|
interactive={Boolean(onClick)}
|
|
sx={{ ...(sx as object) }}
|
|
>
|
|
<Stack onClick={onClick}>
|
|
<Box sx={{ display: "flex", alignItems: "center", gap: theme.spacing(2) }}>
|
|
<Typography
|
|
variant="eyebrow"
|
|
color={palette ? textColor : "text.secondary"}
|
|
>
|
|
{title}
|
|
</Typography>
|
|
{tooltip && (
|
|
<TooltipWithInfo
|
|
title={tooltip}
|
|
iconColor={textColor as string}
|
|
iconSize={14}
|
|
/>
|
|
)}
|
|
</Box>
|
|
<Typography color={textColor}>{subtitle}</Typography>
|
|
{children}
|
|
</Stack>
|
|
</GradientBox>
|
|
);
|
|
};
|