Extract ComparisonColumnCard + ComparisonTabCard molecules, refine comparison UI
- New molecule: ComparisonColumnCard — desktop column header card extracted from ComparisonTable (~150 lines removed from organism) - New molecule: ComparisonTabCard — mobile tab rail card extracted from ComparisonPage (shared by V1 and V2) - CellValue "unknown" restyled: icon+text in neutral grey (was Badge), InfoOutlinedIcon on right at 14px matching item info icons - Unverified provider story data: all items set to unknown across all story files (no dashes in essentials) - Mobile tab rail: recommended badge (replaces star), package price, shadow/glow, center-on-select scroll, overflow clipping fixed - ComparisonPackageCard: added shadow, reduced CTA button to medium - ComparisonTable first column: inline info icon pattern (non-breaking space + nowrap span) prevents icon orphaning on line wrap Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,15 +3,10 @@ import Box from '@mui/material/Box';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
||||
import StarRoundedIcon from '@mui/icons-material/StarRounded';
|
||||
import VerifiedOutlinedIcon from '@mui/icons-material/VerifiedOutlined';
|
||||
import type { SxProps, Theme } from '@mui/material/styles';
|
||||
import { Typography } from '../../atoms/Typography';
|
||||
import { Button } from '../../atoms/Button';
|
||||
import { Badge } from '../../atoms/Badge';
|
||||
import { Card } from '../../atoms/Card';
|
||||
import { Link } from '../../atoms/Link';
|
||||
import { Divider } from '../../atoms/Divider';
|
||||
import { ComparisonColumnCard } from '../../molecules/ComparisonColumnCard';
|
||||
|
||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -120,9 +115,18 @@ function CellValue({ value }: { value: ComparisonCellValue }) {
|
||||
);
|
||||
case 'unknown':
|
||||
return (
|
||||
<Badge color="default" variant="soft" size="small">
|
||||
Unknown
|
||||
</Badge>
|
||||
<Box sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.5 }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ color: 'var(--fa-color-neutral-500)', fontWeight: 500 }}
|
||||
>
|
||||
Unknown
|
||||
</Typography>
|
||||
<InfoOutlinedIcon
|
||||
sx={{ fontSize: 14, color: 'var(--fa-color-neutral-500)' }}
|
||||
aria-hidden
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
case 'unavailable':
|
||||
return (
|
||||
@@ -273,157 +277,14 @@ export const ComparisonTable = React.forwardRef<HTMLDivElement, ComparisonTableP
|
||||
</Typography>
|
||||
</Card>
|
||||
|
||||
{/* Package cards */}
|
||||
{/* Package column header cards */}
|
||||
{packages.map((pkg) => (
|
||||
<Box
|
||||
<ComparisonColumnCard
|
||||
key={pkg.id}
|
||||
role="columnheader"
|
||||
aria-label={pkg.isRecommended ? `${pkg.name} (Recommended)` : pkg.name}
|
||||
sx={{
|
||||
position: 'relative',
|
||||
overflow: 'visible',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
{/* Floating verified badge — overlaps card top edge */}
|
||||
{pkg.provider.verified && (
|
||||
<Badge
|
||||
color="brand"
|
||||
variant="soft"
|
||||
size="small"
|
||||
icon={<VerifiedOutlinedIcon sx={{ fontSize: 14 }} />}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: -12,
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
zIndex: 1,
|
||||
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
|
||||
}}
|
||||
>
|
||||
Verified
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
<Card
|
||||
variant="outlined"
|
||||
selected={pkg.isRecommended}
|
||||
padding="none"
|
||||
sx={{ overflow: 'hidden', flex: 1, display: 'flex', flexDirection: 'column' }}
|
||||
>
|
||||
{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',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center',
|
||||
px: 2.5,
|
||||
py: 2.5,
|
||||
pt: pkg.provider.verified ? 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}
|
||||
>
|
||||
<Typography
|
||||
variant="label"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
maxWidth: '100%',
|
||||
}}
|
||||
>
|
||||
{pkg.provider.name}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
|
||||
{/* Location */}
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{pkg.provider.location}
|
||||
</Typography>
|
||||
|
||||
{/* Rating */}
|
||||
{pkg.provider.rating != null && (
|
||||
<Box sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.5 }}>
|
||||
<StarRoundedIcon
|
||||
sx={{ fontSize: 16, color: 'var(--fa-color-brand-500)' }}
|
||||
aria-hidden
|
||||
/>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{pkg.provider.rating}
|
||||
{pkg.provider.reviewCount != null && ` (${pkg.provider.reviewCount})`}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Divider sx={{ width: '100%', my: 1 }} />
|
||||
|
||||
<Typography variant="h6" component="p">
|
||||
{pkg.name}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5 }}>
|
||||
Total package price
|
||||
</Typography>
|
||||
<Typography variant="h5" sx={{ color: 'primary.main', fontWeight: 700 }}>
|
||||
{formatPrice(pkg.price)}
|
||||
</Typography>
|
||||
|
||||
{/* Spacer pushes CTA to bottom across all cards */}
|
||||
<Box sx={{ flex: 1 }} />
|
||||
|
||||
<Button
|
||||
variant={pkg.provider.verified ? 'contained' : 'soft'}
|
||||
color={pkg.provider.verified ? 'primary' : 'secondary'}
|
||||
size="medium"
|
||||
onClick={() => onArrange(pkg.id)}
|
||||
sx={{ mt: 1.5, px: 4 }}
|
||||
>
|
||||
{pkg.provider.verified ? 'Make Arrangement' : 'Make Enquiry'}
|
||||
</Button>
|
||||
|
||||
{!pkg.isRecommended && (
|
||||
<Link
|
||||
component="button"
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
underline="hover"
|
||||
onClick={() => onRemove(pkg.id)}
|
||||
sx={{ mt: 0.5 }}
|
||||
>
|
||||
Remove
|
||||
</Link>
|
||||
)}
|
||||
</Box>
|
||||
</Card>
|
||||
</Box>
|
||||
pkg={pkg}
|
||||
onArrange={onArrange}
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
@@ -449,30 +310,30 @@ export const ComparisonTable = React.forwardRef<HTMLDivElement, ComparisonTableP
|
||||
<Box
|
||||
role="cell"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.5,
|
||||
px: 3,
|
||||
py: 2,
|
||||
borderTop: '1px solid',
|
||||
borderColor: 'divider',
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ minWidth: 0 }}>
|
||||
<Typography variant="body2" color="text.secondary" component="span">
|
||||
{item.name}
|
||||
</Typography>
|
||||
{item.info && (
|
||||
<Tooltip title={item.info} arrow placement="top">
|
||||
<InfoOutlinedIcon
|
||||
aria-label={`More information about ${item.name}`}
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
color: 'var(--fa-color-neutral-400)',
|
||||
cursor: 'help',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Box component="span" sx={{ whiteSpace: 'nowrap' }}>
|
||||
{'\u00A0'}
|
||||
<Tooltip title={item.info} arrow placement="top">
|
||||
<InfoOutlinedIcon
|
||||
aria-label={`More information about ${item.name}`}
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
color: 'var(--fa-color-neutral-400)',
|
||||
cursor: 'help',
|
||||
verticalAlign: 'middle',
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user