PackageDetail: toggle pattern for Compare button inCart state

Replaces the earlier inert selected-state treatment. Now:
- Button keeps its default soft/secondary chrome in both states — no
  separate brand-tinted visual.
- When `inCart=true`, a leading CheckRoundedIcon is added and the
  label swaps from "Compare" to "Added".
- Button remains clickable; `onCompare` is invoked in both states.
  Caller treats it as a toggle — add when absent, remove when present.
- aria-pressed reflects the state for SR users; aria-label spells
  "Add to comparison" / "Remove from comparison" explicitly.

Demo route swaps `basket.add()` for `basket.toggle()` on the handler
so a second click removes the package from the comparison basket.

Simpler visual (less space, one chrome to maintain) and a clearer
interaction — the user can undo directly from the detail panel
rather than hunting for CompareBar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 11:02:30 +10:00
parent 13bd245872
commit 7ecf309459
2 changed files with 28 additions and 43 deletions

View File

@@ -55,9 +55,9 @@ export interface PackageDetailProps {
/** Whether the compare button is in loading state */ /** Whether the compare button is in loading state */
compareLoading?: boolean; compareLoading?: boolean;
/** Whether this package is already in the comparison basket. When true, /** Whether this package is already in the comparison basket. When true,
* the Compare button swaps to an "In comparison" selected-state (soft * the Compare button swaps its label to "Added" and adds a leading check
* brand tint + check icon) and is inert — removal happens via the * icon. The button remains clickable — the caller is expected to treat
* CompareBar, not this button. */ * `onCompare` as a toggle (add when not in cart, remove when in cart). */
inCart?: boolean; inCart?: boolean;
/** Custom label for the arrange CTA button (default: "Make Arrangement") */ /** Custom label for the arrange CTA button (default: "Make Arrangement") */
arrangeLabel?: string; arrangeLabel?: string;
@@ -219,42 +219,25 @@ export const PackageDetail = React.forwardRef<HTMLDivElement, PackageDetailProps
> >
{arrangeLabel} {arrangeLabel}
</Button> </Button>
{onCompare && {onCompare && (
(inCart ? ( // Same soft/secondary chrome in both states; when the package
// Selected-state: soft brand tint + check icon. Inert // is in the basket a leading check icon appears and the label
// (disabled + aria-disabled) — removal happens via the // changes to "Added". Click is a toggle — caller decides to
// CompareBar, not this button. sx override keeps the // add or remove based on the `inCart` it's passing in.
// brand tint instead of the default greyed-disabled look. <Button
<Button variant="soft"
variant="outlined" color="secondary"
color="secondary" size="large"
size="large" loading={compareLoading}
disabled startIcon={inCart ? <CheckRoundedIcon /> : undefined}
startIcon={<CheckRoundedIcon />} onClick={onCompare}
aria-label="Already in comparison" aria-pressed={inCart}
sx={{ aria-label={inCart ? 'Remove from comparison' : 'Add to comparison'}
flexShrink: 0, sx={{ flexShrink: 0 }}
'&.Mui-disabled': { >
bgcolor: 'var(--fa-color-brand-50)', {inCart ? 'Added' : 'Compare'}
borderColor: 'var(--fa-color-brand-300)', </Button>
color: 'var(--fa-color-text-brand)', )}
},
}}
>
In comparison
</Button>
) : (
<Button
variant="soft"
color="secondary"
size="large"
loading={compareLoading}
onClick={onCompare}
sx={{ flexShrink: 0 }}
>
Compare
</Button>
))}
</Box> </Box>
</Box> </Box>

View File

@@ -21,11 +21,13 @@ export function PackagesRoute() {
if (!provider || !bundle) return <Navigate to="/" replace />; if (!provider || !bundle) return <Navigate to="/" replace />;
// Compare CTA on the PackageDetail panel just adds the selection to the // Compare CTA on the PackageDetail panel toggles the selection in the
// basket. The floating CompareBar (mounted in App.tsx) handles navigation // basket — adds when absent, removes when present. The button's visible
// and removal once the user has 2+ packages selected. // state (Compare / Added + ✓) reflects `isSelectedInCart` below. The
// floating CompareBar (mounted in App.tsx) handles navigation once the
// user has 2+ packages selected.
const handleCompare = () => { const handleCompare = () => {
if (selectedId) basket.add(makeBasketKey(provider.id, selectedId)); if (selectedId) basket.toggle(makeBasketKey(provider.id, selectedId));
}; };
// When the selected package is already in the basket, PackageDetail swaps // When the selected package is already in the basket, PackageDetail swaps