Refine control bar: single-line layout, compact sort, labelled toggle

- All controls on one line: [List|Map] [Filters] [↕ Recommended]
- Sort: compact menu button replaces bulky TextField select
- View toggle: "List" / "Map" text labels alongside icons
- Results count: own line below controls
- Sort pushed right with ml:auto for visual separation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 17:09:52 +11:00
parent e3090e6aed
commit e67c6342e9
2 changed files with 126 additions and 73 deletions

View File

@@ -6,8 +6,10 @@ import Autocomplete from '@mui/material/Autocomplete';
import FormControlLabel from '@mui/material/FormControlLabel'; import FormControlLabel from '@mui/material/FormControlLabel';
import Slider from '@mui/material/Slider'; import Slider from '@mui/material/Slider';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import Menu from '@mui/material/Menu';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import ToggleButton from '@mui/material/ToggleButton'; import ToggleButton from '@mui/material/ToggleButton';
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 LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined'; import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined';
@@ -15,6 +17,7 @@ import type { SxProps, Theme } from '@mui/material/styles';
import { WizardLayout } from '../../templates/WizardLayout'; import { WizardLayout } from '../../templates/WizardLayout';
import { ProviderCard } from '../../molecules/ProviderCard'; import { ProviderCard } from '../../molecules/ProviderCard';
import { FilterPanel } from '../../molecules/FilterPanel'; import { FilterPanel } from '../../molecules/FilterPanel';
import { Button } from '../../atoms/Button';
import { Chip } from '../../atoms/Chip'; import { Chip } from '../../atoms/Chip';
import { Switch } from '../../atoms/Switch'; import { Switch } from '../../atoms/Switch';
import { Typography } from '../../atoms/Typography'; import { Typography } from '../../atoms/Typography';
@@ -239,6 +242,9 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
? 'Take your time exploring providers. You can always come back and choose a different one.' ? 'Take your time exploring providers. You can always come back and choose a different one.'
: 'These providers are near your location. Each has their own packages and pricing.'; : 'These providers are near your location. Each has their own packages and pricing.';
// ─── Local state ───
const [sortAnchor, setSortAnchor] = React.useState<null | HTMLElement>(null);
// ─── Price input local state (commits on blur / Enter) ─── // ─── Price input local state (commits on blur / Enter) ───
const [priceMinInput, setPriceMinInput] = React.useState(String(filterValues.priceRange[0])); const [priceMinInput, setPriceMinInput] = React.useState(String(filterValues.priceRange[0]));
const [priceMaxInput, setPriceMaxInput] = React.useState(String(filterValues.priceRange[1])); const [priceMaxInput, setPriceMaxInput] = React.useState(String(filterValues.priceRange[1]));
@@ -365,45 +371,14 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
sx={{ mb: 1.5 }} sx={{ mb: 1.5 }}
/> />
{/* Control bar — results count, sort, view toggle, filters */} {/* Control bar — view toggle, filters, sort on one line */}
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: 1.5, gap: 1,
flexWrap: 'wrap',
}} }}
> >
{/* Results count — left */}
<Typography
variant="caption"
color="text.secondary"
sx={{ mr: 'auto' }}
aria-live="polite"
>
{providers.length} provider{providers.length !== 1 ? 's' : ''} found
</Typography>
{/* Sort */}
<TextField
select
value={sortBy}
onChange={(e) => onSortChange?.(e.target.value as ProviderSortBy)}
size="small"
aria-label="Sort providers"
sx={{
minWidth: 160,
'& .MuiOutlinedInput-root': { fontSize: '0.813rem' },
'& .MuiSelect-select': { py: '5px' },
}}
>
{SORT_OPTIONS.map((opt) => (
<MenuItem key={opt.value} value={opt.value} sx={{ fontSize: '0.813rem' }}>
{opt.label}
</MenuItem>
))}
</TextField>
{/* View toggle */} {/* View toggle */}
<ToggleButtonGroup <ToggleButtonGroup
value={viewMode} value={viewMode}
@@ -413,10 +388,14 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
aria-label="View mode" aria-label="View mode"
sx={{ sx={{
'& .MuiToggleButton-root': { '& .MuiToggleButton-root': {
px: 1, px: 1.5,
py: 0.5, py: 0.5,
fontSize: '0.75rem',
fontWeight: 500,
gap: 0.5,
border: '1px solid', border: '1px solid',
borderColor: 'divider', borderColor: 'divider',
textTransform: 'none',
'&.Mui-selected': { '&.Mui-selected': {
bgcolor: 'var(--fa-color-brand-100)', bgcolor: 'var(--fa-color-brand-100)',
color: 'primary.main', color: 'primary.main',
@@ -427,10 +406,12 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
}} }}
> >
<ToggleButton value="list" aria-label="List view"> <ToggleButton value="list" aria-label="List view">
<ViewListOutlinedIcon sx={{ fontSize: 18 }} /> <ViewListOutlinedIcon sx={{ fontSize: 16 }} />
List
</ToggleButton> </ToggleButton>
<ToggleButton value="map" aria-label="Map view"> <ToggleButton value="map" aria-label="Map view">
<MapOutlinedIcon sx={{ fontSize: 18 }} /> <MapOutlinedIcon sx={{ fontSize: 16 }} />
Map
</ToggleButton> </ToggleButton>
</ToggleButtonGroup> </ToggleButtonGroup>
@@ -608,7 +589,53 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
</Box> </Box>
</Box> </Box>
</FilterPanel> </FilterPanel>
{/* Sort — compact menu button, pushed right */}
<Box sx={{ ml: 'auto' }}>
<Button
variant="outlined"
color="secondary"
size="small"
startIcon={<SwapVertIcon sx={{ fontSize: 16 }} />}
onClick={(e) => setSortAnchor(e.currentTarget)}
aria-haspopup="listbox"
sx={{ fontSize: '0.75rem', textTransform: 'none' }}
>
{SORT_OPTIONS.find((o) => o.value === sortBy)?.label ?? 'Sort'}
</Button>
<Menu
anchorEl={sortAnchor}
open={Boolean(sortAnchor)}
onClose={() => setSortAnchor(null)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
>
{SORT_OPTIONS.map((opt) => (
<MenuItem
key={opt.value}
selected={opt.value === sortBy}
onClick={() => {
onSortChange?.(opt.value);
setSortAnchor(null);
}}
sx={{ fontSize: '0.813rem' }}
>
{opt.label}
</MenuItem>
))}
</Menu>
</Box>
</Box> </Box>
{/* Results count — below controls */}
<Typography
variant="caption"
color="text.secondary"
sx={{ mt: 1, display: 'block' }}
aria-live="polite"
>
{providers.length} provider{providers.length !== 1 ? 's' : ''} found
</Typography>
</Box> </Box>
{/* Provider list — click-to-navigate (D-D) */} {/* Provider list — click-to-navigate (D-D) */}

View File

@@ -5,8 +5,10 @@ import InputAdornment from '@mui/material/InputAdornment';
import Autocomplete from '@mui/material/Autocomplete'; import Autocomplete from '@mui/material/Autocomplete';
import FormControlLabel from '@mui/material/FormControlLabel'; import FormControlLabel from '@mui/material/FormControlLabel';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import Menu from '@mui/material/Menu';
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
import ToggleButton from '@mui/material/ToggleButton'; import ToggleButton from '@mui/material/ToggleButton';
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 LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined'; import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined';
@@ -14,6 +16,7 @@ import type { SxProps, Theme } from '@mui/material/styles';
import { WizardLayout } from '../../templates/WizardLayout'; import { WizardLayout } from '../../templates/WizardLayout';
import { VenueCard } from '../../molecules/VenueCard'; import { VenueCard } from '../../molecules/VenueCard';
import { FilterPanel } from '../../molecules/FilterPanel'; import { FilterPanel } from '../../molecules/FilterPanel';
import { Button } from '../../atoms/Button';
import { Chip } from '../../atoms/Chip'; import { Chip } from '../../atoms/Chip';
import { Switch } from '../../atoms/Switch'; import { Switch } from '../../atoms/Switch';
import { Typography } from '../../atoms/Typography'; import { Typography } from '../../atoms/Typography';
@@ -210,6 +213,8 @@ export const VenueStep: React.FC<VenueStepProps> = ({
runningTotal, runningTotal,
sx, sx,
}) => { }) => {
const [sortAnchor, setSortAnchor] = React.useState<null | HTMLElement>(null);
const subheading = isPrePlanning const subheading = isPrePlanning
? 'Browse available venues. Your choice can be changed later.' ? 'Browse available venues. Your choice can be changed later.'
: 'Choose a venue for the funeral service. You can filter by type, services, and tradition.'; : 'Choose a venue for the funeral service. You can filter by type, services, and tradition.';
@@ -310,46 +315,14 @@ export const VenueStep: React.FC<VenueStepProps> = ({
sx={{ mb: 1.5 }} sx={{ mb: 1.5 }}
/> />
{/* Control bar — results count, sort, view toggle, filters */} {/* Control bar — view toggle, filters, sort on one line */}
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: 1.5, gap: 1,
flexWrap: 'wrap',
}} }}
> >
{/* Results count — left */}
<Typography
variant="caption"
color="text.secondary"
sx={{ mr: 'auto' }}
aria-live="polite"
>
{venues.length} venue{venues.length !== 1 ? 's' : ''}
{locationName ? ` near ${locationName}` : ''} found
</Typography>
{/* Sort */}
<TextField
select
value={sortBy}
onChange={(e) => onSortChange?.(e.target.value as VenueSortBy)}
size="small"
aria-label="Sort venues"
sx={{
minWidth: 160,
'& .MuiOutlinedInput-root': { fontSize: '0.813rem' },
'& .MuiSelect-select': { py: '5px' },
}}
>
{SORT_OPTIONS.map((opt) => (
<MenuItem key={opt.value} value={opt.value} sx={{ fontSize: '0.813rem' }}>
{opt.label}
</MenuItem>
))}
</TextField>
{/* View toggle */} {/* View toggle */}
<ToggleButtonGroup <ToggleButtonGroup
value={viewMode} value={viewMode}
@@ -359,10 +332,14 @@ export const VenueStep: React.FC<VenueStepProps> = ({
aria-label="View mode" aria-label="View mode"
sx={{ sx={{
'& .MuiToggleButton-root': { '& .MuiToggleButton-root': {
px: 1, px: 1.5,
py: 0.5, py: 0.5,
fontSize: '0.75rem',
fontWeight: 500,
gap: 0.5,
border: '1px solid', border: '1px solid',
borderColor: 'divider', borderColor: 'divider',
textTransform: 'none',
'&.Mui-selected': { '&.Mui-selected': {
bgcolor: 'var(--fa-color-brand-100)', bgcolor: 'var(--fa-color-brand-100)',
color: 'primary.main', color: 'primary.main',
@@ -373,10 +350,12 @@ export const VenueStep: React.FC<VenueStepProps> = ({
}} }}
> >
<ToggleButton value="list" aria-label="List view"> <ToggleButton value="list" aria-label="List view">
<ViewListOutlinedIcon sx={{ fontSize: 18 }} /> <ViewListOutlinedIcon sx={{ fontSize: 16 }} />
List
</ToggleButton> </ToggleButton>
<ToggleButton value="map" aria-label="Map view"> <ToggleButton value="map" aria-label="Map view">
<MapOutlinedIcon sx={{ fontSize: 18 }} /> <MapOutlinedIcon sx={{ fontSize: 16 }} />
Map
</ToggleButton> </ToggleButton>
</ToggleButtonGroup> </ToggleButtonGroup>
@@ -493,7 +472,54 @@ export const VenueStep: React.FC<VenueStepProps> = ({
/> />
</Box> </Box>
</FilterPanel> </FilterPanel>
{/* Sort — compact menu button, pushed right */}
<Box sx={{ ml: 'auto' }}>
<Button
variant="outlined"
color="secondary"
size="small"
startIcon={<SwapVertIcon sx={{ fontSize: 16 }} />}
onClick={(e) => setSortAnchor(e.currentTarget)}
aria-haspopup="listbox"
sx={{ fontSize: '0.75rem', textTransform: 'none' }}
>
{SORT_OPTIONS.find((o) => o.value === sortBy)?.label ?? 'Sort'}
</Button>
<Menu
anchorEl={sortAnchor}
open={Boolean(sortAnchor)}
onClose={() => setSortAnchor(null)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
>
{SORT_OPTIONS.map((opt) => (
<MenuItem
key={opt.value}
selected={opt.value === sortBy}
onClick={() => {
onSortChange?.(opt.value);
setSortAnchor(null);
}}
sx={{ fontSize: '0.813rem' }}
>
{opt.label}
</MenuItem>
))}
</Menu>
</Box>
</Box> </Box>
{/* Results count — below controls */}
<Typography
variant="caption"
color="text.secondary"
sx={{ mt: 1, display: 'block' }}
aria-live="polite"
>
{venues.length} venue{venues.length !== 1 ? 's' : ''}
{locationName ? ` near ${locationName}` : ''} found
</Typography>
</Box> </Box>
{/* Venue list — click-to-navigate */} {/* Venue list — click-to-navigate */}