New components for side-by-side funeral package comparison: - CompareBar molecule: floating bottom pill with fraction badge (1/3, 2/3, 3/3), contextual copy, Compare CTA. For ProvidersStep and PackagesStep. - ComparisonTable organism: CSS Grid comparison with info card, floating verified badges, separate section tables (Essentials/Optionals/Extras) with left accent borders, row hover, horizontal scroll on narrow desktops, font hierarchy. - ComparisonPage: WizardLayout wide-form with Share/Print actions. Desktop shows ComparisonTable, mobile shows mini-card tab rail + single package card view. Recommended package as separate prop (D038). Also fixes PackageDetail: adds priceLabel pass-through (D039), updates stories to Essentials/Optionals/Extras section naming (D035). Decisions: D035-D039 logged. Audits: CompareBar 18/20, ComparisonTable 17/20. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
115 lines
3.7 KiB
TypeScript
115 lines
3.7 KiB
TypeScript
import React from 'react';
|
|
import Paper from '@mui/material/Paper';
|
|
import Slide from '@mui/material/Slide';
|
|
import CompareArrowsIcon from '@mui/icons-material/CompareArrows';
|
|
import type { SxProps, Theme } from '@mui/material/styles';
|
|
import { Typography } from '../../atoms/Typography';
|
|
import { Button } from '../../atoms/Button';
|
|
import { Badge } from '../../atoms/Badge';
|
|
|
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
|
|
/** A package in the comparison basket */
|
|
export interface CompareBarPackage {
|
|
/** Unique package ID */
|
|
id: string;
|
|
/** Package display name */
|
|
name: string;
|
|
/** Provider name */
|
|
providerName: string;
|
|
}
|
|
|
|
/** Props for the CompareBar molecule */
|
|
export interface CompareBarProps {
|
|
/** Packages currently in the comparison basket (max 3 user-selected) */
|
|
packages: CompareBarPackage[];
|
|
/** Called when user clicks "Compare" CTA */
|
|
onCompare: () => void;
|
|
/** Error/status message shown inline (e.g. "Maximum 3 packages") */
|
|
error?: string;
|
|
/** MUI sx prop for the root wrapper */
|
|
sx?: SxProps<Theme>;
|
|
}
|
|
|
|
// ─── Component ───────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Floating comparison basket pill for the FA design system.
|
|
*
|
|
* Shows a fraction badge (1/3, 2/3, 3/3), contextual copy, and a Compare CTA.
|
|
* Present on both ProvidersStep and PackagesStep.
|
|
*
|
|
* Composes Badge + Button + Typography.
|
|
*/
|
|
export const CompareBar = React.forwardRef<HTMLDivElement, CompareBarProps>(
|
|
({ packages, onCompare, error, sx }, ref) => {
|
|
const count = packages.length;
|
|
const visible = count > 0;
|
|
const canCompare = count >= 2;
|
|
|
|
const statusText = count === 1 ? 'Add another to compare' : 'Ready to compare';
|
|
|
|
return (
|
|
<Slide direction="up" in={visible} mountOnEnter unmountOnExit>
|
|
<Paper
|
|
ref={ref}
|
|
elevation={8}
|
|
role="status"
|
|
aria-live="polite"
|
|
aria-label={`${count} of 3 packages selected for comparison`}
|
|
sx={[
|
|
(theme: Theme) => ({
|
|
position: 'fixed',
|
|
bottom: theme.spacing(3),
|
|
left: '50%',
|
|
transform: 'translateX(-50%)',
|
|
zIndex: theme.zIndex.snackbar,
|
|
borderRadius: '9999px',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 1.5,
|
|
px: 2.5,
|
|
py: 1.25,
|
|
maxWidth: { xs: 'calc(100vw - 32px)', md: 420 },
|
|
}),
|
|
...(Array.isArray(sx) ? sx : [sx]),
|
|
]}
|
|
>
|
|
{/* Fraction badge — 1/3, 2/3, 3/3 */}
|
|
<Badge color="brand" variant="soft" size="small" sx={{ flexShrink: 0 }}>
|
|
{count}/3
|
|
</Badge>
|
|
|
|
{/* Status text */}
|
|
<Typography
|
|
variant="body2"
|
|
role={error ? 'alert' : undefined}
|
|
sx={{
|
|
fontWeight: 500,
|
|
whiteSpace: 'nowrap',
|
|
color: error ? 'var(--fa-color-text-brand)' : 'text.primary',
|
|
}}
|
|
>
|
|
{error || statusText}
|
|
</Typography>
|
|
|
|
{/* Compare CTA */}
|
|
<Button
|
|
variant="contained"
|
|
size="small"
|
|
startIcon={<CompareArrowsIcon />}
|
|
onClick={onCompare}
|
|
disabled={!canCompare}
|
|
sx={{ flexShrink: 0, borderRadius: '9999px' }}
|
|
>
|
|
Compare
|
|
</Button>
|
|
</Paper>
|
|
</Slide>
|
|
);
|
|
},
|
|
);
|
|
|
|
CompareBar.displayName = 'CompareBar';
|
|
export default CompareBar;
|