Fix ProviderCard from user feedback + /critique
User feedback:
- "Trusted Partner" → "Verified" badge text
- Override Card hover bg fill (grey blended with shadow) — shadow lift only
- Logo 48px → 56px, removed white border (shadow only)
- Tightened spacing: content padding 16→12px, gap 8→4px, footer py 12→8px
/critique findings (27/40 → fixes applied):
- P1: Price promoted from footer into content area as bold primary text
- P2: Footer simplified to "View packages >" CTA with space-between
- Image fallback changed from grey to warm brand.50
- Name truncation relaxed to maxLines={2} for mobile
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -43,7 +43,7 @@ export interface ProviderCardProps {
|
|||||||
// ─── Constants ───────────────────────────────────────────────────────────────
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const LOGO_SIZE = 'var(--fa-provider-card-logo-size)';
|
const LOGO_SIZE = 'var(--fa-provider-card-logo-size)';
|
||||||
const LOGO_OVERLAP = 24; // half of 48px logo, in px
|
const LOGO_OVERLAP = 28; // half of 56px logo, in px
|
||||||
const IMAGE_HEIGHT = 'var(--fa-provider-card-image-height)';
|
const IMAGE_HEIGHT = 'var(--fa-provider-card-image-height)';
|
||||||
const CONTENT_PADDING = 'var(--fa-provider-card-content-padding)';
|
const CONTENT_PADDING = 'var(--fa-provider-card-content-padding)';
|
||||||
const CONTENT_GAP = 'var(--fa-provider-card-content-gap)';
|
const CONTENT_GAP = 'var(--fa-provider-card-content-gap)';
|
||||||
@@ -60,7 +60,7 @@ const FOOTER_PY = 'var(--fa-provider-card-footer-padding-y)';
|
|||||||
* list. Supports verified (paid partner) and unverified (scraped listing)
|
* list. Supports verified (paid partner) and unverified (scraped listing)
|
||||||
* providers with consistent text alignment for scan readability.
|
* providers with consistent text alignment for scan readability.
|
||||||
*
|
*
|
||||||
* **Verified providers** get a hero image, logo overlay, and "Trusted Partner"
|
* **Verified providers** get a hero image, logo overlay, and "Verified"
|
||||||
* badge. **Unverified providers** show text content only — no image, logo,
|
* badge. **Unverified providers** show text content only — no image, logo,
|
||||||
* or verification badge.
|
* or verification badge.
|
||||||
*
|
*
|
||||||
@@ -111,7 +111,14 @@ export const ProviderCard = React.forwardRef<HTMLDivElement, ProviderCardProps>(
|
|||||||
padding="none"
|
padding="none"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
sx={[
|
sx={[
|
||||||
{ overflow: 'hidden' },
|
{
|
||||||
|
overflow: 'hidden',
|
||||||
|
// Override Card's default hover bg fill — shadow lift is enough
|
||||||
|
// for listing cards. The grey bg fill blends into the shadow.
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'background.paper',
|
||||||
|
},
|
||||||
|
},
|
||||||
...(Array.isArray(sx) ? sx : [sx]),
|
...(Array.isArray(sx) ? sx : [sx]),
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -124,10 +131,10 @@ export const ProviderCard = React.forwardRef<HTMLDivElement, ProviderCardProps>(
|
|||||||
backgroundImage: `url(${imageUrl})`,
|
backgroundImage: `url(${imageUrl})`,
|
||||||
backgroundSize: 'cover',
|
backgroundSize: 'cover',
|
||||||
backgroundPosition: 'center',
|
backgroundPosition: 'center',
|
||||||
backgroundColor: 'action.hover', // fallback if image fails
|
backgroundColor: 'var(--fa-color-brand-50)', // warm fallback if image fails
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Trusted Partner badge */}
|
{/* Verified badge */}
|
||||||
<Box sx={{ position: 'absolute', top: 12, right: 12 }}>
|
<Box sx={{ position: 'absolute', top: 12, right: 12 }}>
|
||||||
<Badge
|
<Badge
|
||||||
variant="filled"
|
variant="filled"
|
||||||
@@ -135,7 +142,7 @@ export const ProviderCard = React.forwardRef<HTMLDivElement, ProviderCardProps>(
|
|||||||
size="small"
|
size="small"
|
||||||
icon={<VerifiedOutlinedIcon />}
|
icon={<VerifiedOutlinedIcon />}
|
||||||
>
|
>
|
||||||
Trusted Partner
|
Verified
|
||||||
</Badge>
|
</Badge>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -154,9 +161,7 @@ export const ProviderCard = React.forwardRef<HTMLDivElement, ProviderCardProps>(
|
|||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
objectFit: 'cover',
|
objectFit: 'cover',
|
||||||
backgroundColor: 'background.paper',
|
backgroundColor: 'background.paper',
|
||||||
boxShadow: 1,
|
boxShadow: '0 2px 8px rgba(0,0,0,0.12)',
|
||||||
border: '2px solid',
|
|
||||||
borderColor: 'background.paper',
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -175,10 +180,17 @@ export const ProviderCard = React.forwardRef<HTMLDivElement, ProviderCardProps>(
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Provider name */}
|
{/* Provider name */}
|
||||||
<Typography variant="h5" maxLines={1}>
|
<Typography variant="h5" maxLines={2}>
|
||||||
{name}
|
{name}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
|
{/* Price — primary comparison data, prominent position */}
|
||||||
|
{startingPrice != null && (
|
||||||
|
<Typography variant="labelLg" sx={{ fontWeight: 700 }} color="primary">
|
||||||
|
From ${startingPrice.toLocaleString('en-AU')}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Meta row: location + reviews */}
|
{/* Meta row: location + reviews */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -227,30 +239,24 @@ export const ProviderCard = React.forwardRef<HTMLDivElement, ProviderCardProps>(
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* ── Footer bar ── */}
|
{/* ── Footer bar ── */}
|
||||||
{startingPrice != null && (
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'space-between',
|
||||||
gap: 0.5,
|
|
||||||
backgroundColor: FOOTER_BG,
|
backgroundColor: FOOTER_BG,
|
||||||
px: FOOTER_PX,
|
px: FOOTER_PX,
|
||||||
py: FOOTER_PY,
|
py: FOOTER_PY,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="label" color="text.secondary">
|
<Typography variant="label" color="text.secondary">
|
||||||
Packages from
|
View packages
|
||||||
</Typography>
|
|
||||||
<Typography variant="label" sx={{ fontWeight: 700 }}>
|
|
||||||
${startingPrice.toLocaleString('en-AU')}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<ChevronRightIcon
|
<ChevronRightIcon
|
||||||
sx={{ fontSize: 20, color: 'text.secondary' }}
|
sx={{ fontSize: 20, color: 'text.secondary' }}
|
||||||
aria-hidden
|
aria-hidden
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
--fa-input-height-md: 48px; /** Medium (default) — standard forms, matches Button large for alignment */
|
--fa-input-height-md: 48px; /** Medium (default) — standard forms, matches Button large for alignment */
|
||||||
--fa-input-icon-size-default: 20px; /** 20px — icon size inside input field, matches Figma trailing icon */
|
--fa-input-icon-size-default: 20px; /** 20px — icon size inside input field, matches Figma trailing icon */
|
||||||
--fa-provider-card-image-height: 180px; /** Fixed image height for consistent card sizing in list layouts */
|
--fa-provider-card-image-height: 180px; /** Fixed image height for consistent card sizing in list layouts */
|
||||||
--fa-provider-card-logo-size: 48px; /** Logo circle diameter — positioned bottom-left of image, overlapping content area */
|
--fa-provider-card-logo-size: 56px; /** Logo circle diameter — positioned bottom-left of image, overlapping content area */
|
||||||
--fa-radio-size-default: 20px; /** Default radio size — matches Figma 16px + padding for 44px touch target area */
|
--fa-radio-size-default: 20px; /** Default radio size — matches Figma 16px + padding for 44px touch target area */
|
||||||
--fa-radio-dot-size-default: 10px; /** Selected indicator dot — 50% of outer size */
|
--fa-radio-dot-size-default: 10px; /** Selected indicator dot — 50% of outer size */
|
||||||
--fa-switch-track-width: 44px; /** Track width — slightly narrower than Figma 52px for better proportion with 44px touch target */
|
--fa-switch-track-width: 44px; /** Track width — slightly narrower than Figma 52px for better proportion with 44px touch target */
|
||||||
@@ -269,9 +269,9 @@
|
|||||||
--fa-input-gap-default: var(--fa-spacing-2); /** 8px — vertical rhythm between label/input/helper, slightly more generous than Figma's 6px for readability */
|
--fa-input-gap-default: var(--fa-spacing-2); /** 8px — vertical rhythm between label/input/helper, slightly more generous than Figma's 6px for readability */
|
||||||
--fa-provider-card-footer-background: var(--fa-color-brand-100); /** Warm beige footer — brand.100 provides subtle brand warmth */
|
--fa-provider-card-footer-background: var(--fa-color-brand-100); /** Warm beige footer — brand.100 provides subtle brand warmth */
|
||||||
--fa-provider-card-footer-padding-x: var(--fa-spacing-4); /** 16px horizontal padding — matches compact card padding */
|
--fa-provider-card-footer-padding-x: var(--fa-spacing-4); /** 16px horizontal padding — matches compact card padding */
|
||||||
--fa-provider-card-footer-padding-y: var(--fa-spacing-3); /** 12px vertical padding — slightly tighter than content area */
|
--fa-provider-card-footer-padding-y: var(--fa-spacing-2); /** 8px vertical padding — compact footer bar */
|
||||||
--fa-provider-card-content-padding: var(--fa-spacing-4); /** 16px content padding — compact to maximise text area on listing cards */
|
--fa-provider-card-content-padding: var(--fa-spacing-3); /** 12px content padding — tight to keep card compact in listing layout */
|
||||||
--fa-provider-card-content-gap: var(--fa-spacing-2); /** 8px vertical gap between content rows (name, meta, capability) */
|
--fa-provider-card-content-gap: var(--fa-spacing-1); /** 4px vertical gap between content rows — tight for compact listing cards */
|
||||||
--fa-switch-track-border-radius: var(--fa-border-radius-full); /** Pill shape */
|
--fa-switch-track-border-radius: var(--fa-border-radius-full); /** Pill shape */
|
||||||
--fa-color-text-primary: var(--fa-color-neutral-800); /** Primary text — body content, headings. Cool charcoal (#2C2E35) for comfortable extended reading */
|
--fa-color-text-primary: var(--fa-color-neutral-800); /** Primary text — body content, headings. Cool charcoal (#2C2E35) for comfortable extended reading */
|
||||||
--fa-color-text-secondary: var(--fa-color-neutral-600); /** Secondary text — helper text, descriptions, metadata, less prominent content */
|
--fa-color-text-secondary: var(--fa-color-neutral-600); /** Secondary text — helper text, descriptions, metadata, less prominent content */
|
||||||
|
|||||||
@@ -73,12 +73,12 @@ export const InputBorderRadiusDefault = "4px"; // 4px — subtle rounding, consi
|
|||||||
export const InputGapDefault = "8px"; // 8px — vertical rhythm between label/input/helper, slightly more generous than Figma's 6px for readability
|
export const InputGapDefault = "8px"; // 8px — vertical rhythm between label/input/helper, slightly more generous than Figma's 6px for readability
|
||||||
export const InputIconSizeDefault = "20px"; // 20px — icon size inside input field, matches Figma trailing icon
|
export const InputIconSizeDefault = "20px"; // 20px — icon size inside input field, matches Figma trailing icon
|
||||||
export const ProviderCardImageHeight = "180px"; // Fixed image height for consistent card sizing in list layouts
|
export const ProviderCardImageHeight = "180px"; // Fixed image height for consistent card sizing in list layouts
|
||||||
export const ProviderCardLogoSize = "48px"; // Logo circle diameter — positioned bottom-left of image, overlapping content area
|
export const ProviderCardLogoSize = "56px"; // Logo circle diameter — positioned bottom-left of image, overlapping content area
|
||||||
export const ProviderCardFooterBackground = "#f7ecdf"; // Warm beige footer — brand.100 provides subtle brand warmth
|
export const ProviderCardFooterBackground = "#f7ecdf"; // Warm beige footer — brand.100 provides subtle brand warmth
|
||||||
export const ProviderCardFooterPaddingX = "16px"; // 16px horizontal padding — matches compact card padding
|
export const ProviderCardFooterPaddingX = "16px"; // 16px horizontal padding — matches compact card padding
|
||||||
export const ProviderCardFooterPaddingY = "12px"; // 12px vertical padding — slightly tighter than content area
|
export const ProviderCardFooterPaddingY = "8px"; // 8px vertical padding — compact footer bar
|
||||||
export const ProviderCardContentPadding = "16px"; // 16px content padding — compact to maximise text area on listing cards
|
export const ProviderCardContentPadding = "12px"; // 12px content padding — tight to keep card compact in listing layout
|
||||||
export const ProviderCardContentGap = "8px"; // 8px vertical gap between content rows (name, meta, capability)
|
export const ProviderCardContentGap = "4px"; // 4px vertical gap between content rows — tight for compact listing cards
|
||||||
export const RadioSizeDefault = "20px"; // Default radio size — matches Figma 16px + padding for 44px touch target area
|
export const RadioSizeDefault = "20px"; // Default radio size — matches Figma 16px + padding for 44px touch target area
|
||||||
export const RadioDotSizeDefault = "10px"; // Selected indicator dot — 50% of outer size
|
export const RadioDotSizeDefault = "10px"; // Selected indicator dot — 50% of outer size
|
||||||
export const SwitchTrackWidth = "44px"; // Track width — slightly narrower than Figma 52px for better proportion with 44px touch target
|
export const SwitchTrackWidth = "44px"; // Track width — slightly narrower than Figma 52px for better proportion with 44px touch target
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"logo": {
|
"logo": {
|
||||||
"$type": "dimension",
|
"$type": "dimension",
|
||||||
"$description": "Provider logo overlay dimensions.",
|
"$description": "Provider logo overlay dimensions.",
|
||||||
"size": { "$value": "48px", "$description": "Logo circle diameter — positioned bottom-left of image, overlapping content area" }
|
"size": { "$value": "56px", "$description": "Logo circle diameter — positioned bottom-left of image, overlapping content area" }
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"$description": "Footer bar styling — warm beige bar with package pricing.",
|
"$description": "Footer bar styling — warm beige bar with package pricing.",
|
||||||
@@ -25,21 +25,21 @@
|
|||||||
},
|
},
|
||||||
"paddingY": {
|
"paddingY": {
|
||||||
"$type": "dimension",
|
"$type": "dimension",
|
||||||
"$value": "{spacing.3}",
|
"$value": "{spacing.2}",
|
||||||
"$description": "12px vertical padding — slightly tighter than content area"
|
"$description": "8px vertical padding — compact footer bar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"content": {
|
"content": {
|
||||||
"$description": "Content area spacing.",
|
"$description": "Content area spacing.",
|
||||||
"padding": {
|
"padding": {
|
||||||
"$type": "dimension",
|
"$type": "dimension",
|
||||||
"$value": "{spacing.4}",
|
"$value": "{spacing.3}",
|
||||||
"$description": "16px content padding — compact to maximise text area on listing cards"
|
"$description": "12px content padding — tight to keep card compact in listing layout"
|
||||||
},
|
},
|
||||||
"gap": {
|
"gap": {
|
||||||
"$type": "dimension",
|
"$type": "dimension",
|
||||||
"$value": "{spacing.2}",
|
"$value": "{spacing.1}",
|
||||||
"$description": "8px vertical gap between content rows (name, meta, capability)"
|
"$description": "4px vertical gap between content rows — tight for compact listing cards"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user