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:
@@ -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,9 +589,55 @@ 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>
|
</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>
|
||||||
|
|
||||||
{/* Provider list — click-to-navigate (D-D) */}
|
{/* Provider list — click-to-navigate (D-D) */}
|
||||||
<Box
|
<Box
|
||||||
role="list"
|
role="list"
|
||||||
|
|||||||
@@ -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,9 +472,56 @@ 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>
|
</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>
|
||||||
|
|
||||||
{/* Venue list — click-to-navigate */}
|
{/* Venue list — click-to-navigate */}
|
||||||
<Box
|
<Box
|
||||||
role="list"
|
role="list"
|
||||||
|
|||||||
Reference in New Issue
Block a user