From 22d14ef9bccbde0c68f3de8b3940fb035f742fa0 Mon Sep 17 00:00:00 2001 From: Richie Date: Wed, 22 Apr 2026 22:31:12 +1000 Subject: [PATCH] ProvidersStep + MapPin: verified-icon alignment, filter tidy, drawer fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MapPin (both tiers) - Verified providers now get an inline verified tick on the left of the name, matching the tick colour to the name so it reads as part of the label. Inline SVG (not @mui/icons-material) because MapPin is mounted via createRoot outside the ThemeProvider. - Max label width bumped 180 → 210px to accommodate the icon without aggressively truncating long names. Mobile cluster drawer rows - Verified icon aligns with the name's top line (flex-start + 1.25em icon slot) — matches the desktop ClusterPopup layout. - New right-aligned "From $X" price column, copper for verified. Controls - Mobile List/Map toggle: text labels (List, Map) instead of icons. - Desktop List/Map toggle: resized to 32px height matching Filters + Sort buttons; bigger type, more padding. - Search input corner radius now matches the button radius (8px) instead of the input radius (4px) so it reads as part of the chip set rather than a separate control. Filter dialog (desktop + mobile) - Remove the Location field — the sticky search bar is primary. - Funeral-type chips bump small → medium. - Reset filters button always renders; disabled when no filters are active (was previously hidden), so the affordance is discoverable. - Provider-feature switches (Verified only, Online arrangements) align to the first text line so wrapped labels don't sink the switch visually below the second line. Mobile drawer close - drawerOpen now excludes the `exiting` phase, so tapping the X slides the drawer down immediately instead of lingering with a stale opacity fade. Visibility flips to hidden only after the slide ends. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/atoms/MapPin/MapPin.tsx | 48 ++++++-- .../molecules/FilterPanel/FilterPanel.tsx | 10 +- .../pages/ProvidersStep/ProvidersStep.tsx | 115 +++++++++--------- 3 files changed, 104 insertions(+), 69 deletions(-) diff --git a/src/components/atoms/MapPin/MapPin.tsx b/src/components/atoms/MapPin/MapPin.tsx index 06c511d..5eb54eb 100644 --- a/src/components/atoms/MapPin/MapPin.tsx +++ b/src/components/atoms/MapPin/MapPin.tsx @@ -25,7 +25,7 @@ export interface MapPinProps { const PIN_PX = 'var(--fa-map-pin-padding-x)'; const PIN_RADIUS = 'var(--fa-map-pin-border-radius)'; const NUB_SIZE = 'var(--fa-map-pin-nub-size)'; -const MAX_WIDTH = 180; +const MAX_WIDTH = 210; // ─── Colour sets ──────────────────────────────────────────────────────────── @@ -141,22 +141,46 @@ export const MapPin = React.forwardRef( boxShadow: 'var(--fa-shadow-sm)', }} > - {/* Name */} + {/* Name row — verified icon (left) + name */} - {name} + {verified && ( + // Inline SVG of Material's Verified (outlined) icon. Kept as + // inline SVG because MapPin is mounted via createRoot outside + // the MUI ThemeProvider, so @mui/icons-material wouldn't pick + // up theme defaults. + + + + )} + + {name} + {/* Price line */} diff --git a/src/components/molecules/FilterPanel/FilterPanel.tsx b/src/components/molecules/FilterPanel/FilterPanel.tsx index 84a63d7..807638a 100644 --- a/src/components/molecules/FilterPanel/FilterPanel.tsx +++ b/src/components/molecules/FilterPanel/FilterPanel.tsx @@ -77,8 +77,14 @@ export const FilterPanel = React.forwardRef( title={label} footer={ - {onClear && activeCount > 0 ? ( - ) : ( diff --git a/src/components/pages/ProvidersStep/ProvidersStep.tsx b/src/components/pages/ProvidersStep/ProvidersStep.tsx index 11fa31a..160805e 100644 --- a/src/components/pages/ProvidersStep/ProvidersStep.tsx +++ b/src/components/pages/ProvidersStep/ProvidersStep.tsx @@ -337,44 +337,6 @@ export const ProvidersStep: React.FC = ({ * and the mobile-map floating FilterPanel. */ const filterDialogChildren = ( <> - {/* ── Location ── */} - - - Location - - { - const last = newValue[newValue.length - 1] ?? ''; - onSearchChange(typeof last === 'string' ? last : ''); - }} - options={[]} - renderInput={(params) => ( - - - - - {params.InputProps.startAdornment} - - ), - }} - /> - )} - size="small" - /> - - - - {/* ── Service tradition ── */} @@ -407,7 +369,7 @@ export const ProvidersStep: React.FC = ({ selected={filterValues.funeralTypes.includes(option.value)} onClick={() => handleFuneralTypeToggle(option.value)} variant="outlined" - size="small" + size="medium" /> ))} @@ -415,7 +377,8 @@ export const ProvidersStep: React.FC = ({ - {/* ── Provider features ── */} + {/* ── Provider features ── Switch aligned to the first text line so + wrapped labels read cleanly on narrow screens */} = ({ /> } label="Verified providers only" - sx={{ mx: 0 }} + sx={{ + mx: 0, + alignItems: 'flex-start', + '& .MuiFormControlLabel-label': { pt: 0.75 }, + }} /> = ({ /> } label="Online arrangements available" - sx={{ mx: 0 }} + sx={{ + mx: 0, + alignItems: 'flex-start', + '& .MuiFormControlLabel-label': { pt: 0.75 }, + }} /> @@ -510,7 +481,11 @@ export const ProvidersStep: React.FC = ({ if (showMobileMapLayout) { const active = mapActive ?? null; - const drawerOpen = !!(active && (active.provider || active.cluster)); + // Drawer is "open" only when there's an active selection AND the map + // isn't in the middle of its exit animation. Flipping to false on + // `exiting` kicks off the slide-down transform immediately, so the user + // sees the drawer leave as soon as they tap the close X. + const drawerOpen = !!(active && !active.exiting && (active.provider || active.cluster)); const drawerProvider = active?.provider ?? null; const drawerCluster = active?.cluster ?? null; @@ -624,6 +599,7 @@ export const ProvidersStep: React.FC = ({ '& .MuiOutlinedInput-root': { bgcolor: 'background.paper', boxShadow: 'var(--fa-shadow-sm)', + borderRadius: 'var(--fa-button-border-radius-default)', }, '& .MuiOutlinedInput-notchedOutline': { borderColor: 'var(--fa-color-neutral-300)', @@ -710,7 +686,8 @@ export const ProvidersStep: React.FC = ({ ))} - {/* View toggle — icon-only, aligned height with the buttons */} + {/* View toggle — text labels on mobile, aligned height with + the buttons */} = ({ boxShadow: 'var(--fa-shadow-sm)', '& .MuiToggleButton-root': { height: 32, - px: 1, + px: 1.25, py: 0, + textTransform: 'none', + fontSize: '0.8125rem', + fontWeight: 500, borderColor: 'var(--fa-color-neutral-300)', bgcolor: 'background.paper', '&:hover': { bgcolor: 'background.paper' }, @@ -737,10 +717,10 @@ export const ProvidersStep: React.FC = ({ }} > - + List - + Map @@ -771,9 +751,9 @@ export const ProvidersStep: React.FC = ({ borderTopRightRadius: 16, boxShadow: 'var(--fa-shadow-lg)', transform: drawerOpen ? 'translateY(0)' : 'translateY(100%)', - opacity: mapActive?.exiting ? 0 : 1, - transition: 'transform 220ms ease-out, opacity 180ms ease-out', + transition: 'transform 220ms ease-out', pointerEvents: drawerOpen ? 'auto' : 'none', + visibility: drawerOpen || mapActive?.exiting ? 'visible' : 'hidden', }} > {/* Drawer header — holds the close X (and the cluster count when @@ -845,16 +825,21 @@ export const ProvidersStep: React.FC = ({ px: 2, py: 1.25, gap: 1, + // Start-align so the verified icon sits on the + // name's baseline (matches desktop ClusterPopup) + alignItems: 'flex-start', borderBottom: '1px solid', borderColor: 'divider', '&:last-of-type': { borderBottom: 'none' }, '&:hover': { bgcolor: 'var(--fa-color-surface-subtle)' }, }} > - {/* Verified-icon slot (aligns all names) */} + {/* Verified-icon slot — height tuned to the name's + line-box so the tick aligns with the title top */} = ({ )} + {/* Price column — right-aligned "From $X" */} + {p.startingPrice != null && ( + + + From + + + ${p.startingPrice.toLocaleString('en-AU')} + + + )} ))} @@ -938,7 +943,7 @@ export const ProvidersStep: React.FC = ({ sx={sx} secondaryPanel={ - {/* Floating view toggle */} + {/* Floating view toggle — sized to match Filters/Sort buttons */} = ({ zIndex: 1, bgcolor: 'background.paper', boxShadow: 'var(--fa-shadow-md)', - borderRadius: 1, '& .MuiToggleButton-root': { + height: 'var(--fa-button-height-sm)', px: 1.5, - py: 0.5, - fontSize: '0.75rem', + py: 0, + fontSize: '0.8125rem', fontWeight: 500, - gap: 0.5, + gap: 0.75, border: '1px solid', borderColor: 'divider', textTransform: 'none',