ProvidersStep mobile: transparent control strip, icon-only sort, drawer header

- Drop the Paper container around the mobile-map floating controls —
  each control (Filters, Sort, view toggle) now carries its own white
  fill and reads over any map tile without a shared box
- Sort button becomes icon-only on mobile-map (the current sort is
  still communicated via the aria-label and the menu) — saves the
  row's horizontal budget
- Align all three controls to 32px height so Filters, Sort, and the
  List/Map toggle sit on a common baseline
- Move the drawer close X out of the image overlay area into a
  dedicated 40px drawer header bar; cluster header text ("N providers
  in this area") now lives in the same strip. No more overlap with the
  Verified badge on the card image.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-22 12:57:48 +10:00
parent 8c818fd5ac
commit 705e85b37c

View File

@@ -538,18 +538,15 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
/> />
</Box> </Box>
{/* Floating control strip */} {/* Floating control strip — no container chrome; each control has
<Paper its own fill/border so it reads cleanly over any map tile */}
elevation={0} <Box
sx={{ sx={{
position: 'absolute', position: 'absolute',
top: 12, top: 12,
left: 12, left: 12,
right: 12, right: 12,
zIndex: 2, zIndex: 2,
p: 1,
borderRadius: 2,
boxShadow: 'var(--fa-shadow-md)',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: 1, gap: 1,
@@ -634,31 +631,46 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
}} }}
/> />
{/* Control row: Filters, Sort, view toggle */} {/* Control row: Filters, Sort (icon-only on mobile), view toggle.
Each control carries its own white fill so it reads cleanly
over any map tile — no shared container. Heights aligned at
32px to match Button small + ToggleButton small. */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<FilterPanel <FilterPanel
activeCount={activeCount} activeCount={activeCount}
onClear={handleClear} onClear={handleClear}
sx={{ '& .MuiButton-root:focus-visible': { outline: 'none' } }} sx={{
'& .MuiButton-root': {
height: 32,
bgcolor: 'background.paper',
'&:hover': { bgcolor: 'background.paper' },
'&:focus-visible': { outline: 'none' },
},
}}
> >
{filterDialogChildren} {filterDialogChildren}
</FilterPanel> </FilterPanel>
<Button {/* Sort — icon-only on mobile (expansion of the current sort
variant="outlined" surfaced through the menu + aria-label) */}
color="secondary" <IconButton
size="small"
startIcon={<SwapVertIcon sx={{ fontSize: 16 }} />}
onClick={(e) => setSortAnchor(e.currentTarget)} onClick={(e) => setSortAnchor(e.currentTarget)}
aria-haspopup="listbox" aria-haspopup="listbox"
aria-label={`Sort by ${SORT_OPTIONS.find((o) => o.value === sortBy)?.label ?? 'Recommended'}`} aria-label={`Sort by ${SORT_OPTIONS.find((o) => o.value === sortBy)?.label ?? 'Recommended'}`}
sx={{ textTransform: 'none', '&:focus-visible': { outline: 'none' } }} sx={{
width: 32,
height: 32,
borderRadius: 1,
border: '1px solid',
borderColor: 'divider',
bgcolor: 'background.paper',
color: 'text.secondary',
'&:hover': { bgcolor: 'background.paper' },
'&:focus-visible': { outline: 'none' },
}}
> >
<Box component="span" sx={{ color: 'text.secondary', fontWeight: 400, mr: 0.5 }}> <SwapVertIcon sx={{ fontSize: 18 }} />
Sort: </IconButton>
</Box>
{SORT_OPTIONS.find((o) => o.value === sortBy)?.label ?? 'Recommended'}
</Button>
<Menu <Menu
anchorEl={sortAnchor} anchorEl={sortAnchor}
open={Boolean(sortAnchor)} open={Boolean(sortAnchor)}
@@ -681,7 +693,7 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
))} ))}
</Menu> </Menu>
{/* View toggle — icon-only on mobile to keep the row tight */} {/* View toggle — icon-only, aligned height with the buttons */}
<ToggleButtonGroup <ToggleButtonGroup
value={viewMode} value={viewMode}
exclusive exclusive
@@ -692,8 +704,11 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
ml: 'auto', ml: 'auto',
flexShrink: 0, flexShrink: 0,
'& .MuiToggleButton-root': { '& .MuiToggleButton-root': {
height: 32,
px: 1, px: 1,
py: 0.5, py: 0,
bgcolor: 'background.paper',
'&:hover': { bgcolor: 'background.paper' },
'&.Mui-selected': { '&.Mui-selected': {
bgcolor: 'var(--fa-color-brand-100)', bgcolor: 'var(--fa-color-brand-100)',
color: 'primary.main', color: 'primary.main',
@@ -710,7 +725,7 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
</ToggleButton> </ToggleButton>
</ToggleButtonGroup> </ToggleButtonGroup>
</Box> </Box>
</Paper> </Box>
{/* Bottom drawer — slides up when a pin/cluster is active */} {/* Bottom drawer — slides up when a pin/cluster is active */}
<Paper <Paper
@@ -741,28 +756,42 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
pointerEvents: drawerOpen ? 'auto' : 'none', pointerEvents: drawerOpen ? 'auto' : 'none',
}} }}
> >
{/* Close X */} {/* Drawer header — holds the close X (and the cluster count when
applicable) so it doesn't sit over the card image */}
<Box
sx={{
display: 'flex',
alignItems: 'center',
minHeight: 40,
px: 1.5,
py: 0.5,
gap: 1,
}}
>
{drawerCluster && !drawerProvider && (
<Typography variant="labelLg" sx={{ color: 'text.secondary', display: 'block' }}>
{drawerCluster.providers.length} providers in this area
</Typography>
)}
<IconButton <IconButton
aria-label="Close" aria-label="Close"
onClick={() => mapRef.current?.clearActive()} onClick={() => mapRef.current?.clearActive()}
size="small"
sx={{ sx={{
position: 'absolute', ml: 'auto',
top: 8,
right: 8,
zIndex: 1,
width: 32, width: 32,
height: 32, height: 32,
bgcolor: 'background.paper', color: 'text.secondary',
boxShadow: 'var(--fa-shadow-sm)', '&:hover': { bgcolor: 'var(--fa-color-surface-subtle)' },
'&:hover': { bgcolor: 'background.paper' },
}} }}
> >
<CloseRoundedIcon sx={{ fontSize: 18 }} /> <CloseRoundedIcon sx={{ fontSize: 20 }} />
</IconButton> </IconButton>
</Box>
{/* Single-provider drawer content — entire card clickable */} {/* Single-provider drawer content — entire card clickable */}
{drawerProvider && ( {drawerProvider && (
<Box sx={{ p: 1.5 }}> <Box sx={{ px: 1.5, pb: 1.5 }}>
<ProviderCard <ProviderCard
name={drawerProvider.name} name={drawerProvider.name}
location={drawerProvider.location} location={drawerProvider.location}
@@ -780,13 +809,7 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
{/* Cluster list drawer content — tap row to drill in */} {/* Cluster list drawer content — tap row to drill in */}
{drawerCluster && !drawerProvider && ( {drawerCluster && !drawerProvider && (
<Box sx={{ pt: 5, pb: 1 }}> <Box sx={{ pb: 1 }}>
<Typography
variant="labelLg"
sx={{ px: 2, pb: 1, color: 'text.secondary', display: 'block' }}
>
{drawerCluster.providers.length} providers in this area
</Typography>
<Box> <Box>
{[...drawerCluster.providers] {[...drawerCluster.providers]
.sort((a, b) => Number(!!b.verified) - Number(!!a.verified)) .sort((a, b) => Number(!!b.verified) - Number(!!a.verified))