Promote HelpBar to a shared molecule
Lifts HelpBar out of WizardLayout's internal scope into src/components/molecules/HelpBar/ so both WizardLayout and pages that bypass WizardLayout's chrome (currently: ProvidersStep's mobile map-first branch) render an identical sticky-footer. Before: WizardLayout had an internal HelpBar with the right styling (sticky, responsive px, phone format helper); ProvidersStep's mobile-map branch hand-rewrote the footer inline and had drifted — missing position: sticky, missing the md:4 responsive px, hard-coded phone number bypassing the prop default. This consolidates both to one source of truth. Props: `phone?` (defaults to FA's support number, spaces preserved in label, stripped in tel href) + `sx?` for caller chrome overrides. Two Storybook stories (Default, CustomNumber). Verified: footer text / height / sticky position identical between mobile-list (WizardLayout) and mobile-map (direct HelpBar). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
32
src/components/molecules/HelpBar/HelpBar.stories.tsx
Normal file
32
src/components/molecules/HelpBar/HelpBar.stories.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import { HelpBar } from './HelpBar';
|
||||||
|
|
||||||
|
const meta: Meta<typeof HelpBar> = {
|
||||||
|
title: 'Molecules/HelpBar',
|
||||||
|
component: HelpBar,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
parameters: { layout: 'fullscreen' },
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
// Fake page content so the sticky footer has something to sit under.
|
||||||
|
<Box sx={{ minHeight: 400, display: 'flex', flexDirection: 'column' }}>
|
||||||
|
<Box sx={{ flex: 1, p: 4, bgcolor: 'background.default' }}>
|
||||||
|
Page content scrolls above the help bar.
|
||||||
|
</Box>
|
||||||
|
<Story />
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof HelpBar>;
|
||||||
|
|
||||||
|
/** Default — uses FA's standard support number. */
|
||||||
|
export const Default: Story = {};
|
||||||
|
|
||||||
|
/** Custom number — spaces preserved in the label, stripped in the tel link. */
|
||||||
|
export const CustomNumber: Story = {
|
||||||
|
args: { phone: '1300 000 000' },
|
||||||
|
};
|
||||||
64
src/components/molecules/HelpBar/HelpBar.tsx
Normal file
64
src/components/molecules/HelpBar/HelpBar.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import PhoneIcon from '@mui/icons-material/Phone';
|
||||||
|
import type { SxProps, Theme } from '@mui/material/styles';
|
||||||
|
import { Typography } from '../../atoms/Typography';
|
||||||
|
import { Link } from '../../atoms/Link';
|
||||||
|
|
||||||
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Props for the FA HelpBar molecule */
|
||||||
|
export interface HelpBarProps {
|
||||||
|
/** Phone number shown in the bar. Spaces preserved in the label,
|
||||||
|
* stripped in the `tel:` href. Defaults to FA's support number. */
|
||||||
|
phone?: string;
|
||||||
|
/** MUI sx prop — merged onto the default footer chrome. */
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Component ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sticky help footer used at the bottom of every wizard page. Shows a
|
||||||
|
* phone-icon prefix + "Need help? Call us on" + the support number as a
|
||||||
|
* tel-link. White fill, top border, sticky to the viewport bottom.
|
||||||
|
*
|
||||||
|
* Used by `WizardLayout` (for all variants that don't set `hideHelpBar`)
|
||||||
|
* and by pages that bypass WizardLayout's chrome (e.g. the mobile-map-first
|
||||||
|
* layout on `ProvidersStep`). Promoted from a WizardLayout-internal
|
||||||
|
* component so both sources render an identical footer — preventing drift
|
||||||
|
* if the phone number or styling ever changes.
|
||||||
|
*/
|
||||||
|
export const HelpBar = React.forwardRef<HTMLDivElement, HelpBarProps>(
|
||||||
|
({ phone = '1800 987 888', sx }, ref) => (
|
||||||
|
<Box
|
||||||
|
ref={ref}
|
||||||
|
component="footer"
|
||||||
|
sx={[
|
||||||
|
{
|
||||||
|
position: 'sticky',
|
||||||
|
bottom: 0,
|
||||||
|
zIndex: 10,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
borderTop: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
py: 1.5,
|
||||||
|
px: { xs: 2, md: 4 },
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
...(Array.isArray(sx) ? sx : [sx]),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Typography variant="body2" color="text.secondary" component="span">
|
||||||
|
<PhoneIcon sx={{ fontSize: 16, verticalAlign: 'text-bottom', mr: 0.5 }} />
|
||||||
|
Need help? Call us on{' '}
|
||||||
|
<Link href={`tel:${phone.replace(/\s/g, '')}`} sx={{ fontWeight: 600 }}>
|
||||||
|
{phone}
|
||||||
|
</Link>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
HelpBar.displayName = 'HelpBar';
|
||||||
|
export default HelpBar;
|
||||||
1
src/components/molecules/HelpBar/index.ts
Normal file
1
src/components/molecules/HelpBar/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { HelpBar, type HelpBarProps } from './HelpBar';
|
||||||
@@ -13,7 +13,6 @@ import useMediaQuery from '@mui/material/useMediaQuery';
|
|||||||
import SwapVertIcon from '@mui/icons-material/SwapVert';
|
import SwapVertIcon from '@mui/icons-material/SwapVert';
|
||||||
import ViewListOutlinedIcon from '@mui/icons-material/ViewListOutlined';
|
import ViewListOutlinedIcon from '@mui/icons-material/ViewListOutlined';
|
||||||
import MapOutlinedIcon from '@mui/icons-material/MapOutlined';
|
import MapOutlinedIcon from '@mui/icons-material/MapOutlined';
|
||||||
import PhoneIcon from '@mui/icons-material/Phone';
|
|
||||||
import type { SxProps, Theme } from '@mui/material/styles';
|
import type { SxProps, Theme } from '@mui/material/styles';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import { WizardLayout } from '../../templates/WizardLayout';
|
import { WizardLayout } from '../../templates/WizardLayout';
|
||||||
@@ -21,6 +20,7 @@ import { ProviderCard } from '../../molecules/ProviderCard';
|
|||||||
import { FilterPanel } from '../../molecules/FilterPanel';
|
import { FilterPanel } from '../../molecules/FilterPanel';
|
||||||
import { MapProviderDrawer } from '../../molecules/MapProviderDrawer';
|
import { MapProviderDrawer } from '../../molecules/MapProviderDrawer';
|
||||||
import { LocationSearchInput } from '../../molecules/LocationSearchInput';
|
import { LocationSearchInput } from '../../molecules/LocationSearchInput';
|
||||||
|
import { HelpBar } from '../../molecules/HelpBar';
|
||||||
import {
|
import {
|
||||||
ProviderMap,
|
ProviderMap,
|
||||||
type ProviderMapActiveState,
|
type ProviderMapActiveState,
|
||||||
@@ -28,7 +28,6 @@ import {
|
|||||||
} from '../../organisms/ProviderMap';
|
} from '../../organisms/ProviderMap';
|
||||||
import { Button } from '../../atoms/Button';
|
import { Button } from '../../atoms/Button';
|
||||||
import { Chip } from '../../atoms/Chip';
|
import { Chip } from '../../atoms/Chip';
|
||||||
import { Link } from '../../atoms/Link';
|
|
||||||
import { Switch } from '../../atoms/Switch';
|
import { Switch } from '../../atoms/Switch';
|
||||||
import { Typography } from '../../atoms/Typography';
|
import { Typography } from '../../atoms/Typography';
|
||||||
import { Divider } from '../../atoms/Divider';
|
import { Divider } from '../../atoms/Divider';
|
||||||
@@ -673,26 +672,9 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Sticky help bar (matches WizardLayout) */}
|
{/* Sticky help bar — shared HelpBar molecule so this footer stays
|
||||||
<Box
|
identical to WizardLayout's (which we bypass in this branch). */}
|
||||||
component="footer"
|
<HelpBar />
|
||||||
sx={{
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
borderTop: '1px solid',
|
|
||||||
borderColor: 'divider',
|
|
||||||
py: 1.5,
|
|
||||||
px: 2,
|
|
||||||
textAlign: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="body2" color="text.secondary" component="span">
|
|
||||||
<PhoneIcon sx={{ fontSize: 16, verticalAlign: 'text-bottom', mr: 0.5 }} />
|
|
||||||
Need help? Call us on{' '}
|
|
||||||
<Link href="tel:1800987888" sx={{ fontWeight: 600 }}>
|
|
||||||
1800 987 888
|
|
||||||
</Link>
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ import React from 'react';
|
|||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Container from '@mui/material/Container';
|
import Container from '@mui/material/Container';
|
||||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
import PhoneIcon from '@mui/icons-material/Phone';
|
|
||||||
import type { SxProps, Theme } from '@mui/material/styles';
|
import type { SxProps, Theme } from '@mui/material/styles';
|
||||||
import { Link } from '../../atoms/Link';
|
import { Link } from '../../atoms/Link';
|
||||||
import { Typography } from '../../atoms/Typography';
|
import { HelpBar } from '../../molecules/HelpBar';
|
||||||
|
|
||||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -51,33 +50,6 @@ export interface WizardLayoutProps {
|
|||||||
sx?: SxProps<Theme>;
|
sx?: SxProps<Theme>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Help bar ────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
const HelpBar: React.FC<{ phone: string }> = ({ phone }) => (
|
|
||||||
<Box
|
|
||||||
component="footer"
|
|
||||||
sx={{
|
|
||||||
position: 'sticky',
|
|
||||||
bottom: 0,
|
|
||||||
zIndex: 10,
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
borderTop: '1px solid',
|
|
||||||
borderColor: 'divider',
|
|
||||||
py: 1.5,
|
|
||||||
px: { xs: 2, md: 4 },
|
|
||||||
textAlign: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="body2" color="text.secondary" component="span">
|
|
||||||
<PhoneIcon sx={{ fontSize: 16, verticalAlign: 'text-bottom', mr: 0.5 }} />
|
|
||||||
Need help? Call us on{' '}
|
|
||||||
<Link href={`tel:${phone.replace(/\s/g, '')}`} sx={{ fontWeight: 600 }}>
|
|
||||||
{phone}
|
|
||||||
</Link>
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
// ─── Back link ───────────────────────────────────────────────────────────────
|
// ─── Back link ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const BackLink: React.FC<{ label: string; onClick?: () => void }> = ({ label, onClick }) => (
|
const BackLink: React.FC<{ label: string; onClick?: () => void }> = ({ label, onClick }) => (
|
||||||
|
|||||||
Reference in New Issue
Block a user