CompareBar: raise above HelpBar, fix centering, responsive sizing on xs
- Raise bottom offset from theme.spacing(3) (24px) → theme.spacing(9) (72px) so the pill clears the sticky HelpBar with ~16px breathing. - Centering: swap `left:50%; transform: translateX(-50%)` for `left:0; right:0; mx:auto; width:fit-content`. Slide (the wrapper) animates via transform, which was clobbering our centering transform and leaving the bar's left edge at the viewport centre instead of its centre (measured 171px off-centre pre-fix). Auto-margin centering doesn't fight Slide's animation. - Mobile sizing: responsive step-down on xs — - Badge: large (32px) → medium (26px) - Typography: body1 (16px) → body2 (14px) - Button: medium (40px) → small (32px) - Container: gap 2→1.5, px 3→2, py 1.5→1 md+ keeps the larger sizes from the earlier bump. Rejected alternatives: slide/peek collapsed-state (adds interaction cost and hides state behind a tap — bad for FA's grief-sensitive audience); full-width bottom bar (loses the "floating reminder" pill character). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Paper from '@mui/material/Paper';
|
import Paper from '@mui/material/Paper';
|
||||||
import Slide from '@mui/material/Slide';
|
import Slide from '@mui/material/Slide';
|
||||||
import type { SxProps, Theme } from '@mui/material/styles';
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
|
import { useTheme, type SxProps, type Theme } from '@mui/material/styles';
|
||||||
import { Typography } from '../../atoms/Typography';
|
import { Typography } from '../../atoms/Typography';
|
||||||
import { Button } from '../../atoms/Button';
|
import { Button } from '../../atoms/Button';
|
||||||
import { Badge } from '../../atoms/Badge';
|
import { Badge } from '../../atoms/Badge';
|
||||||
@@ -42,6 +43,8 @@ export interface CompareBarProps {
|
|||||||
*/
|
*/
|
||||||
export const CompareBar = React.forwardRef<HTMLDivElement, CompareBarProps>(
|
export const CompareBar = React.forwardRef<HTMLDivElement, CompareBarProps>(
|
||||||
({ packages, onCompare, error, sx }, ref) => {
|
({ packages, onCompare, error, sx }, ref) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
const count = packages.length;
|
const count = packages.length;
|
||||||
const visible = count > 0;
|
const visible = count > 0;
|
||||||
const canCompare = count >= 2;
|
const canCompare = count >= 2;
|
||||||
@@ -59,29 +62,42 @@ export const CompareBar = React.forwardRef<HTMLDivElement, CompareBarProps>(
|
|||||||
sx={[
|
sx={[
|
||||||
(theme: Theme) => ({
|
(theme: Theme) => ({
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
bottom: theme.spacing(3),
|
// Clear the sticky HelpBar (~56px + 16px breathing) so the
|
||||||
left: '50%',
|
// pill doesn't overlap it.
|
||||||
transform: 'translateX(-50%)',
|
bottom: theme.spacing(9),
|
||||||
|
// Centre via auto-margin rather than `left:50%; translateX(-50%)`
|
||||||
|
// — Slide (the wrapper) animates via transform, which would
|
||||||
|
// otherwise clobber the centering transform.
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
mx: 'auto',
|
||||||
|
width: 'fit-content',
|
||||||
zIndex: theme.zIndex.snackbar,
|
zIndex: theme.zIndex.snackbar,
|
||||||
borderRadius: '9999px',
|
borderRadius: '9999px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 2,
|
gap: { xs: 1.5, md: 2 },
|
||||||
px: 3,
|
px: { xs: 2, md: 3 },
|
||||||
py: 1.5,
|
py: { xs: 1, md: 1.5 },
|
||||||
maxWidth: { xs: 'calc(100vw - 32px)', md: 460 },
|
maxWidth: { xs: 'calc(100vw - 32px)', md: 460 },
|
||||||
}),
|
}),
|
||||||
...(Array.isArray(sx) ? sx : [sx]),
|
...(Array.isArray(sx) ? sx : [sx]),
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{/* Fraction badge — 1/3, 2/3, 3/3 */}
|
{/* Fraction badge — 1/3, 2/3, 3/3. Responsive: medium on xs,
|
||||||
<Badge color="brand" variant="soft" size="large" sx={{ flexShrink: 0 }}>
|
large on md+ to match the rest of the bar's size step. */}
|
||||||
|
<Badge
|
||||||
|
color="brand"
|
||||||
|
variant="soft"
|
||||||
|
size={isMobile ? 'medium' : 'large'}
|
||||||
|
sx={{ flexShrink: 0 }}
|
||||||
|
>
|
||||||
{count}/3
|
{count}/3
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
||||||
{/* Status text */}
|
{/* Status text — body2 on mobile (smaller footprint), body1 on md+ */}
|
||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant={isMobile ? 'body2' : 'body1'}
|
||||||
role={error ? 'alert' : undefined}
|
role={error ? 'alert' : undefined}
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
@@ -92,10 +108,10 @@ export const CompareBar = React.forwardRef<HTMLDivElement, CompareBarProps>(
|
|||||||
{error || statusText}
|
{error || statusText}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{/* Compare CTA — icon dropped; label alone at medium size is enough */}
|
{/* Compare CTA — small (32px) on mobile, medium (40px) on md+ */}
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
size="medium"
|
size={isMobile ? 'small' : 'medium'}
|
||||||
onClick={onCompare}
|
onClick={onCompare}
|
||||||
disabled={!canCompare}
|
disabled={!canCompare}
|
||||||
sx={{ flexShrink: 0, borderRadius: '9999px' }}
|
sx={{ flexShrink: 0, borderRadius: '9999px' }}
|
||||||
|
|||||||
Reference in New Issue
Block a user