Add sort dropdown + list/map view toggle to ProvidersStep & VenueStep
New control bar below search on both listing pages: - Left: results count (passive) - Right: sort select (Recommended/Nearest/Price), list/map toggle, filters Sort: compact TextField select with 4 options, 0.813rem font. View toggle: MUI ToggleButtonGroup with list/map icons, brand highlight. Control bar wraps gracefully on narrow panels (flex-wrap). New types: ProviderSortBy, VenueSortBy, ListViewMode. Stories updated with interactive sort + view state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,11 @@ import TextField from '@mui/material/TextField';
|
||||
import InputAdornment from '@mui/material/InputAdornment';
|
||||
import Autocomplete from '@mui/material/Autocomplete';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
|
||||
import ToggleButton from '@mui/material/ToggleButton';
|
||||
import ViewListOutlinedIcon from '@mui/icons-material/ViewListOutlined';
|
||||
import MapOutlinedIcon from '@mui/icons-material/MapOutlined';
|
||||
import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined';
|
||||
import type { SxProps, Theme } from '@mui/material/styles';
|
||||
import { WizardLayout } from '../../templates/WizardLayout';
|
||||
@@ -46,6 +51,12 @@ export interface VenueFilterValues {
|
||||
tradition: string | null;
|
||||
}
|
||||
|
||||
/** Sort options for the venue list */
|
||||
export type VenueSortBy = 'recommended' | 'nearest' | 'price_low' | 'price_high';
|
||||
|
||||
/** View mode for the listing */
|
||||
export type ListViewMode = 'list' | 'map';
|
||||
|
||||
/** Props for the VenueStep page component */
|
||||
export interface VenueStepProps {
|
||||
/** List of venues to display */
|
||||
@@ -66,6 +77,14 @@ export interface VenueStepProps {
|
||||
venueTypeOptions?: VenueTypeOption[];
|
||||
/** Available service tradition options for the autocomplete */
|
||||
traditionOptions?: string[];
|
||||
/** Current sort order */
|
||||
sortBy?: VenueSortBy;
|
||||
/** Callback when sort order changes */
|
||||
onSortChange?: (sort: VenueSortBy) => void;
|
||||
/** Current view mode */
|
||||
viewMode?: ListViewMode;
|
||||
/** Callback when view mode changes */
|
||||
onViewModeChange?: (mode: ListViewMode) => void;
|
||||
/** Callback for back navigation */
|
||||
onBack: () => void;
|
||||
/** Location name for the results count */
|
||||
@@ -120,6 +139,13 @@ const DEFAULT_TRADITIONS = [
|
||||
'Uniting Church',
|
||||
];
|
||||
|
||||
const SORT_OPTIONS: { value: VenueSortBy; label: string }[] = [
|
||||
{ value: 'recommended', label: 'Recommended' },
|
||||
{ value: 'nearest', label: 'Nearest' },
|
||||
{ value: 'price_low', label: 'Price: Low to High' },
|
||||
{ value: 'price_high', label: 'Price: High to Low' },
|
||||
];
|
||||
|
||||
export const EMPTY_VENUE_FILTERS: VenueFilterValues = {
|
||||
venueTypes: [],
|
||||
videoStreaming: false,
|
||||
@@ -171,6 +197,10 @@ export const VenueStep: React.FC<VenueStepProps> = ({
|
||||
onFilterChange,
|
||||
venueTypeOptions = DEFAULT_VENUE_TYPES,
|
||||
traditionOptions = DEFAULT_TRADITIONS,
|
||||
sortBy = 'recommended',
|
||||
onSortChange,
|
||||
viewMode = 'list',
|
||||
onViewModeChange,
|
||||
onBack,
|
||||
locationName,
|
||||
isPrePlanning = false,
|
||||
@@ -280,8 +310,77 @@ export const VenueStep: React.FC<VenueStepProps> = ({
|
||||
sx={{ mb: 1.5 }}
|
||||
/>
|
||||
|
||||
{/* Filters — right-aligned below search */}
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}>
|
||||
{/* Control bar — results count, sort, view toggle, filters */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1.5,
|
||||
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 */}
|
||||
<ToggleButtonGroup
|
||||
value={viewMode}
|
||||
exclusive
|
||||
onChange={(_, val) => val && onViewModeChange?.(val as ListViewMode)}
|
||||
size="small"
|
||||
aria-label="View mode"
|
||||
sx={{
|
||||
'& .MuiToggleButton-root': {
|
||||
px: 1,
|
||||
py: 0.5,
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
'&.Mui-selected': {
|
||||
bgcolor: 'var(--fa-color-brand-100)',
|
||||
color: 'primary.main',
|
||||
borderColor: 'primary.main',
|
||||
'&:hover': { bgcolor: 'var(--fa-color-brand-200)' },
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ToggleButton value="list" aria-label="List view">
|
||||
<ViewListOutlinedIcon sx={{ fontSize: 18 }} />
|
||||
</ToggleButton>
|
||||
<ToggleButton value="map" aria-label="Map view">
|
||||
<MapOutlinedIcon sx={{ fontSize: 18 }} />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
|
||||
{/* Filters */}
|
||||
<FilterPanel activeCount={activeCount} onClear={handleClear}>
|
||||
{/* ── Location ── */}
|
||||
<Box>
|
||||
@@ -395,17 +494,6 @@ export const VenueStep: React.FC<VenueStepProps> = ({
|
||||
</Box>
|
||||
</FilterPanel>
|
||||
</Box>
|
||||
|
||||
{/* Results count */}
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
sx={{ mb: 0, display: 'block' }}
|
||||
aria-live="polite"
|
||||
>
|
||||
{venues.length} venue{venues.length !== 1 ? 's' : ''}
|
||||
{locationName ? ` near ${locationName}` : ''} found
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Venue list — click-to-navigate */}
|
||||
|
||||
Reference in New Issue
Block a user