Refine ComparisonColumnCard recommended state and CTA alignment

- Replace recommended banner with floating badge (star + primary fill)
  so CTA buttons align across recommended and non-recommended columns.
- Inline verified icon on recommended cards only (left of provider name,
  brand-600). Non-recommended verified providers keep the top badge alone.
- Override recommended card border to brand-600 for consistency with
  the rest of the primary system (token default is brand-500).
- Show dash in rating slot when provider has no rating — keeps heights
  consistent across the row.
- Invisible Remove placeholder on recommended card so primary CTAs align
  with cards that have a visible Remove link.
- Remove link dropped from body2 to caption (12px).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-16 21:36:22 +10:00
parent 9ac8e31516
commit 356d22da4c

View File

@@ -36,10 +36,11 @@ function formatPrice(amount: number): string {
/**
* Desktop column header card for the ComparisonTable.
*
* Shows provider info (verified badge, name, location, rating), package name,
* total price, CTA button, and optional Remove link. The verified badge floats
* above the card's top edge. Recommended packages get a copper banner and warm
* selected card state.
* Shows provider info (verified/recommended badge, name, location, rating),
* package name, total price, CTA button, and optional Remove link. The badge
* floats above the card's top edge — "Recommended" (primary fill) replaces
* "Verified" (soft) when the package is recommended. Recommended packages
* also get a warm selected card state with a brand-600 border.
*
* Used as the sticky header for each column in the desktop comparison grid.
* Mobile comparison uses ComparisonPackageCard instead.
@@ -61,13 +62,19 @@ export const ComparisonColumnCard = React.forwardRef<HTMLDivElement, ComparisonC
...(Array.isArray(sx) ? sx : [sx]),
]}
>
{/* Floating verified badge — overlaps card top edge */}
{pkg.provider.verified && (
{/* Floating badge — Recommended (primary fill) takes priority over Verified (soft) */}
{(pkg.isRecommended || pkg.provider.verified) && (
<Badge
color="brand"
variant="soft"
variant={pkg.isRecommended ? 'filled' : 'soft'}
size="small"
icon={<VerifiedOutlinedIcon sx={{ fontSize: 14 }} />}
icon={
pkg.isRecommended ? (
<StarRoundedIcon sx={{ fontSize: 14 }} />
) : (
<VerifiedOutlinedIcon sx={{ fontSize: 14 }} />
)
}
sx={{
position: 'absolute',
top: -12,
@@ -77,7 +84,7 @@ export const ComparisonColumnCard = React.forwardRef<HTMLDivElement, ComparisonC
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
}}
>
Verified
{pkg.isRecommended ? 'Recommended' : 'Verified'}
</Badge>
)}
@@ -85,24 +92,16 @@ export const ComparisonColumnCard = React.forwardRef<HTMLDivElement, ComparisonC
variant="outlined"
selected={pkg.isRecommended}
padding="none"
sx={{ overflow: 'hidden', flex: 1, display: 'flex', flexDirection: 'column' }}
sx={{
overflow: 'hidden',
flex: 1,
display: 'flex',
flexDirection: 'column',
...(pkg.isRecommended && {
borderColor: 'var(--fa-color-brand-600)',
}),
}}
>
{pkg.isRecommended && (
<Box sx={{ bgcolor: 'var(--fa-color-brand-600)', py: 0.75, textAlign: 'center' }}>
<Typography
variant="labelSm"
sx={{
color: 'var(--fa-color-white)',
fontWeight: 600,
letterSpacing: '0.05em',
textTransform: 'uppercase',
}}
>
Recommended
</Typography>
</Box>
)}
<Box
sx={{
display: 'flex',
@@ -111,39 +110,59 @@ export const ComparisonColumnCard = React.forwardRef<HTMLDivElement, ComparisonC
textAlign: 'center',
px: 2.5,
py: 2.5,
pt: pkg.provider.verified ? 3 : 2.5,
pt: pkg.provider.verified || pkg.isRecommended ? 3 : 2.5,
gap: 0.5,
flex: 1,
}}
>
{/* Provider name (truncated with tooltip) */}
<Tooltip
title={pkg.provider.name}
arrow
placement="top"
disableHoverListener={pkg.provider.name.length < 24}
{/* Provider name with optional verified icon (truncated with tooltip) */}
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 0.75,
maxWidth: '100%',
}}
>
<Typography
variant="label"
sx={{
fontWeight: 600,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
maxWidth: '100%',
}}
{pkg.isRecommended && (
<VerifiedOutlinedIcon
sx={{
fontSize: 16,
color: 'var(--fa-color-brand-600)',
flexShrink: 0,
}}
aria-label="Verified provider"
/>
)}
<Tooltip
title={pkg.provider.name}
arrow
placement="top"
disableHoverListener={pkg.provider.name.length < 24}
>
{pkg.provider.name}
</Typography>
</Tooltip>
<Typography
variant="label"
sx={{
fontWeight: 600,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
minWidth: 0,
}}
>
{pkg.provider.name}
</Typography>
</Tooltip>
</Box>
{/* Location */}
<Typography variant="caption" color="text.secondary">
{pkg.provider.location}
</Typography>
{/* Rating */}
{pkg.provider.rating != null && (
{/* Rating (or dash placeholder to keep card heights consistent) */}
{pkg.provider.rating != null ? (
<Box sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.5 }}>
<StarRoundedIcon
sx={{ fontSize: 16, color: 'var(--fa-color-brand-500)' }}
@@ -154,6 +173,10 @@ export const ComparisonColumnCard = React.forwardRef<HTMLDivElement, ComparisonC
{pkg.provider.reviewCount != null && ` (${pkg.provider.reviewCount})`}
</Typography>
</Box>
) : (
<Typography variant="body2" color="text.secondary" aria-label="No reviews yet">
</Typography>
)}
<Divider sx={{ width: '100%', my: 1 }} />
@@ -182,10 +205,10 @@ export const ComparisonColumnCard = React.forwardRef<HTMLDivElement, ComparisonC
{pkg.provider.verified ? 'Make Arrangement' : 'Make Enquiry'}
</Button>
{!pkg.isRecommended && onRemove && (
{!pkg.isRecommended && onRemove ? (
<Link
component="button"
variant="body2"
variant="caption"
color="text.secondary"
underline="hover"
onClick={() => onRemove(pkg.id)}
@@ -193,6 +216,11 @@ export const ComparisonColumnCard = React.forwardRef<HTMLDivElement, ComparisonC
>
Remove
</Link>
) : (
/* Invisible spacer keeps CTA aligned with cards that show Remove */
<Box sx={{ mt: 0.5, visibility: 'hidden' }} aria-hidden>
<Typography variant="caption">Remove</Typography>
</Box>
)}
</Box>
</Card>