Batch 2: List-map layout rework — 420px fixed column, sticky headers

- WizardLayout ListMapLayout: 420px fixed left column (D-B), flex:1
  right panel, back link rendered inside left panel instead of above
  the split (eliminates gap above map)
- LAYOUT_MAP type updated to accept backLink prop for list-map variant
- ProvidersStep: heading + search + filters wrapped in sticky Box
  that pins at top of scrollable left panel while card list scrolls
- VenueStep: same sticky header treatment, heading moved inside form
  for consistent wrapper structure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 22:18:52 +11:00
parent 6ebd52f36f
commit 1c3cdbc101
3 changed files with 134 additions and 94 deletions

View File

@@ -146,7 +146,18 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
) )
} }
> >
{/* Header */} {/* Sticky header — stays pinned while card list scrolls */}
<Box
sx={{
position: 'sticky',
top: 0,
zIndex: 1,
bgcolor: 'background.default',
pb: 1,
mx: -3,
px: 3,
}}
>
<Typography variant="display3" component="h1" sx={{ mb: 0.5 }} tabIndex={-1}> <Typography variant="display3" component="h1" sx={{ mb: 0.5 }} tabIndex={-1}>
Choose a funeral provider Choose a funeral provider
</Typography> </Typography>
@@ -185,11 +196,12 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
<Typography <Typography
variant="caption" variant="caption"
color="text.secondary" color="text.secondary"
sx={{ mb: 2, display: 'block' }} sx={{ mb: 0, display: 'block' }}
aria-live="polite" aria-live="polite"
> >
{providers.length} provider{providers.length !== 1 ? 's' : ''} found {providers.length} provider{providers.length !== 1 ? 's' : ''} found
</Typography> </Typography>
</Box>
{/* Error message */} {/* Error message */}
{error && ( {error && (

View File

@@ -193,7 +193,27 @@ export const VenueStep: React.FC<VenueStepProps> = ({
) )
} }
> >
{/* Page heading */} <Box
component="form"
noValidate
aria-busy={loading}
onSubmit={(e: React.FormEvent) => {
e.preventDefault();
if (!loading) onContinue();
}}
>
{/* Sticky header — stays pinned while card list scrolls */}
<Box
sx={{
position: 'sticky',
top: 0,
zIndex: 1,
bgcolor: 'background.default',
pb: 1,
mx: -3,
px: 3,
}}
>
<Typography variant="display3" component="h1" sx={{ mb: 0.5 }} tabIndex={-1}> <Typography variant="display3" component="h1" sx={{ mb: 0.5 }} tabIndex={-1}>
Where would you like the service? Where would you like the service?
</Typography> </Typography>
@@ -204,17 +224,8 @@ export const VenueStep: React.FC<VenueStepProps> = ({
: 'Choose a venue for the funeral service. You can filter by location, features, and religion.'} : 'Choose a venue for the funeral service. You can filter by location, features, and religion.'}
</Typography> </Typography>
<Box
component="form"
noValidate
aria-busy={loading}
onSubmit={(e: React.FormEvent) => {
e.preventDefault();
if (!loading) onContinue();
}}
>
{/* ─── Search + Filters ─── */} {/* ─── Search + Filters ─── */}
<Box sx={{ mb: 3 }}> <Box sx={{ mb: 2 }}>
<TextField <TextField
placeholder="Search a town or suburb..." placeholder="Search a town or suburb..."
value={values.search} value={values.search}
@@ -243,10 +254,11 @@ export const VenueStep: React.FC<VenueStepProps> = ({
</Box> </Box>
{/* ─── Results count ─── */} {/* ─── Results count ─── */}
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }} aria-live="polite"> <Typography variant="body2" color="text.secondary" sx={{ mb: 0 }} aria-live="polite">
Found {venues.length} venue{venues.length !== 1 ? 's' : ''} Found {venues.length} venue{venues.length !== 1 ? 's' : ''}
{locationName ? ` near ${locationName}` : ''} {locationName ? ` near ${locationName}` : ''}
</Typography> </Typography>
</Box>
{/* ─── Venue card grid ─── */} {/* ─── Venue card grid ─── */}
<Box <Box

View File

@@ -142,11 +142,12 @@ const CenteredFormLayout: React.FC<{ children: React.ReactNode }> = ({ children
</Container> </Container>
); );
/** List + Map: ~40% scrollable list (left) / ~60% map (right) */ /** List + Map: 420px fixed scrollable list (left) / flex map (right) — D-B */
const ListMapLayout: React.FC<{ const ListMapLayout: React.FC<{
children: React.ReactNode; children: React.ReactNode;
secondaryPanel?: React.ReactNode; secondaryPanel?: React.ReactNode;
}> = ({ children, secondaryPanel }) => ( backLink?: React.ReactNode;
}> = ({ children, secondaryPanel, backLink }) => (
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@@ -156,18 +157,20 @@ const ListMapLayout: React.FC<{
> >
<Box <Box
sx={{ sx={{
width: { xs: '100%', md: '40%' }, width: { xs: '100%', md: 420 },
flexShrink: 0,
overflowY: 'auto', overflowY: 'auto',
px: { xs: 2, md: 3 }, px: { xs: 2, md: 3 },
py: 3, py: 3,
}} }}
> >
{backLink}
{children} {children}
</Box> </Box>
<Box <Box
sx={{ sx={{
display: { xs: 'none', md: 'block' }, display: { xs: 'none', md: 'flex' },
width: '60%', flex: 1,
position: 'relative', position: 'relative',
}} }}
> >
@@ -246,7 +249,11 @@ const DetailTogglesLayout: React.FC<{
const LAYOUT_MAP: Record< const LAYOUT_MAP: Record<
WizardLayoutVariant, WizardLayoutVariant,
React.FC<{ children: React.ReactNode; secondaryPanel?: React.ReactNode }> React.FC<{
children: React.ReactNode;
secondaryPanel?: React.ReactNode;
backLink?: React.ReactNode;
}>
> = { > = {
'centered-form': CenteredFormLayout, 'centered-form': CenteredFormLayout,
'list-map': ListMapLayout, 'list-map': ListMapLayout,
@@ -313,8 +320,8 @@ export const WizardLayout = React.forwardRef<HTMLDivElement, WizardLayoutProps>(
{/* Stepper + running total bar (grid-sidebar, detail-toggles only) */} {/* Stepper + running total bar (grid-sidebar, detail-toggles only) */}
{showStepper && <StepperBar stepper={progressStepper} total={runningTotal} />} {showStepper && <StepperBar stepper={progressStepper} total={runningTotal} />}
{/* Back link — inside a container for consistent alignment */} {/* Back link — inside left panel for list-map, above content for others */}
{showBackLink && ( {showBackLink && variant !== 'list-map' && (
<Container <Container
maxWidth={variant === 'centered-form' ? 'sm' : 'lg'} maxWidth={variant === 'centered-form' ? 'sm' : 'lg'}
sx={{ pt: 2, px: { xs: 4, md: 3 } }} sx={{ pt: 2, px: { xs: 4, md: 3 } }}
@@ -325,7 +332,16 @@ export const WizardLayout = React.forwardRef<HTMLDivElement, WizardLayoutProps>(
{/* Main content area */} {/* Main content area */}
<Box component="main" sx={{ display: 'flex', flexDirection: 'column', flex: 1 }}> <Box component="main" sx={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
<LayoutComponent secondaryPanel={secondaryPanel}>{children}</LayoutComponent> <LayoutComponent
secondaryPanel={secondaryPanel}
backLink={
showBackLink && variant === 'list-map' ? (
<BackLink label={backLabel} onClick={onBack} />
) : undefined
}
>
{children}
</LayoutComponent>
</Box> </Box>
{/* Sticky help bar */} {/* Sticky help bar */}