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:
@@ -1,7 +1,12 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
import { ProvidersStep } from './ProvidersStep';
|
import { ProvidersStep } from './ProvidersStep';
|
||||||
import type { ProviderData, ProviderFilterValues } from './ProvidersStep';
|
import type {
|
||||||
|
ProviderData,
|
||||||
|
ProviderFilterValues,
|
||||||
|
ProviderSortBy,
|
||||||
|
ListViewMode,
|
||||||
|
} from './ProvidersStep';
|
||||||
import { EMPTY_FILTER_VALUES } from './ProvidersStep';
|
import { EMPTY_FILTER_VALUES } from './ProvidersStep';
|
||||||
import { Navigation } from '../../organisms/Navigation';
|
import { Navigation } from '../../organisms/Navigation';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
@@ -118,6 +123,8 @@ export const Default: Story = {
|
|||||||
render: () => {
|
render: () => {
|
||||||
const [query, setQuery] = useState('');
|
const [query, setQuery] = useState('');
|
||||||
const [filters, setFilters] = useState<ProviderFilterValues>(EMPTY_FILTER_VALUES);
|
const [filters, setFilters] = useState<ProviderFilterValues>(EMPTY_FILTER_VALUES);
|
||||||
|
const [sort, setSort] = useState<ProviderSortBy>('recommended');
|
||||||
|
const [view, setView] = useState<ListViewMode>('list');
|
||||||
|
|
||||||
const filtered = mockProviders.filter((p) =>
|
const filtered = mockProviders.filter((p) =>
|
||||||
p.location.toLowerCase().includes(query.toLowerCase()),
|
p.location.toLowerCase().includes(query.toLowerCase()),
|
||||||
@@ -131,6 +138,10 @@ export const Default: Story = {
|
|||||||
onSearchChange={setQuery}
|
onSearchChange={setQuery}
|
||||||
filterValues={filters}
|
filterValues={filters}
|
||||||
onFilterChange={setFilters}
|
onFilterChange={setFilters}
|
||||||
|
sortBy={sort}
|
||||||
|
onSortChange={setSort}
|
||||||
|
viewMode={view}
|
||||||
|
onViewModeChange={setView}
|
||||||
onBack={() => alert('Back')}
|
onBack={() => alert('Back')}
|
||||||
navigation={nav}
|
navigation={nav}
|
||||||
/>
|
/>
|
||||||
@@ -151,6 +162,8 @@ export const WithActiveFilters: Story = {
|
|||||||
onlineArrangements: false,
|
onlineArrangements: false,
|
||||||
priceRange: [0, 2000],
|
priceRange: [0, 2000],
|
||||||
});
|
});
|
||||||
|
const [sort, setSort] = useState<ProviderSortBy>('nearest');
|
||||||
|
const [view, setView] = useState<ListViewMode>('list');
|
||||||
|
|
||||||
const filtered = mockProviders.filter((p) =>
|
const filtered = mockProviders.filter((p) =>
|
||||||
p.location.toLowerCase().includes(query.toLowerCase()),
|
p.location.toLowerCase().includes(query.toLowerCase()),
|
||||||
@@ -164,6 +177,10 @@ export const WithActiveFilters: Story = {
|
|||||||
onSearchChange={setQuery}
|
onSearchChange={setQuery}
|
||||||
filterValues={filters}
|
filterValues={filters}
|
||||||
onFilterChange={setFilters}
|
onFilterChange={setFilters}
|
||||||
|
sortBy={sort}
|
||||||
|
onSortChange={setSort}
|
||||||
|
viewMode={view}
|
||||||
|
onViewModeChange={setView}
|
||||||
onBack={() => alert('Back')}
|
onBack={() => alert('Back')}
|
||||||
navigation={nav}
|
navigation={nav}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ 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 Slider from '@mui/material/Slider';
|
import Slider from '@mui/material/Slider';
|
||||||
|
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 LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined';
|
||||||
import type { SxProps, Theme } from '@mui/material/styles';
|
import type { SxProps, Theme } from '@mui/material/styles';
|
||||||
import { WizardLayout } from '../../templates/WizardLayout';
|
import { WizardLayout } from '../../templates/WizardLayout';
|
||||||
@@ -65,6 +70,12 @@ export interface ProviderFilterValues {
|
|||||||
priceRange: [number, number];
|
priceRange: [number, number];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sort options for the provider list */
|
||||||
|
export type ProviderSortBy = 'recommended' | 'nearest' | 'price_low' | 'price_high';
|
||||||
|
|
||||||
|
/** View mode for the listing */
|
||||||
|
export type ListViewMode = 'list' | 'map';
|
||||||
|
|
||||||
/** Props for the ProvidersStep page component */
|
/** Props for the ProvidersStep page component */
|
||||||
export interface ProvidersStepProps {
|
export interface ProvidersStepProps {
|
||||||
/** List of providers to display */
|
/** List of providers to display */
|
||||||
@@ -89,6 +100,14 @@ export interface ProvidersStepProps {
|
|||||||
minPrice?: number;
|
minPrice?: number;
|
||||||
/** Maximum price for the slider (default 15000) */
|
/** Maximum price for the slider (default 15000) */
|
||||||
maxPrice?: number;
|
maxPrice?: number;
|
||||||
|
/** Current sort order */
|
||||||
|
sortBy?: ProviderSortBy;
|
||||||
|
/** Callback when sort order changes */
|
||||||
|
onSortChange?: (sort: ProviderSortBy) => void;
|
||||||
|
/** Current view mode */
|
||||||
|
viewMode?: ListViewMode;
|
||||||
|
/** Callback when view mode changes */
|
||||||
|
onViewModeChange?: (mode: ListViewMode) => void;
|
||||||
/** Callback for the Back button */
|
/** Callback for the Back button */
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
/** Map panel content — slot for future map integration */
|
/** Map panel content — slot for future map integration */
|
||||||
@@ -140,6 +159,13 @@ const DEFAULT_FUNERAL_TYPES: FuneralTypeOption[] = [
|
|||||||
{ value: 'burial_only', label: 'Burial Only' },
|
{ value: 'burial_only', label: 'Burial Only' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const SORT_OPTIONS: { value: ProviderSortBy; 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_FILTER_VALUES: ProviderFilterValues = {
|
export const EMPTY_FILTER_VALUES: ProviderFilterValues = {
|
||||||
tradition: null,
|
tradition: null,
|
||||||
funeralTypes: [],
|
funeralTypes: [],
|
||||||
@@ -197,6 +223,10 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
|
|||||||
funeralTypeOptions = DEFAULT_FUNERAL_TYPES,
|
funeralTypeOptions = DEFAULT_FUNERAL_TYPES,
|
||||||
minPrice = 0,
|
minPrice = 0,
|
||||||
maxPrice = 15000,
|
maxPrice = 15000,
|
||||||
|
sortBy = 'recommended',
|
||||||
|
onSortChange,
|
||||||
|
viewMode = 'list',
|
||||||
|
onViewModeChange,
|
||||||
onBack,
|
onBack,
|
||||||
mapPanel,
|
mapPanel,
|
||||||
navigation,
|
navigation,
|
||||||
@@ -335,8 +365,76 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
|
|||||||
sx={{ mb: 1.5 }}
|
sx={{ mb: 1.5 }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Filters — right-aligned below search */}
|
{/* Control bar — results count, sort, view toggle, filters */}
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}>
|
<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"
|
||||||
|
>
|
||||||
|
{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 */}
|
||||||
|
<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}>
|
<FilterPanel activeCount={activeCount} onClear={handleClear}>
|
||||||
{/* ── Location ── */}
|
{/* ── Location ── */}
|
||||||
<Box>
|
<Box>
|
||||||
@@ -511,16 +609,6 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
|
|||||||
</Box>
|
</Box>
|
||||||
</FilterPanel>
|
</FilterPanel>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Results count */}
|
|
||||||
<Typography
|
|
||||||
variant="caption"
|
|
||||||
color="text.secondary"
|
|
||||||
sx={{ mb: 0, 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) */}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
import { VenueStep } from './VenueStep';
|
import { VenueStep } from './VenueStep';
|
||||||
import type { Venue, VenueFilterValues } from './VenueStep';
|
import type { Venue, VenueFilterValues, VenueSortBy, ListViewMode } from './VenueStep';
|
||||||
import { EMPTY_VENUE_FILTERS } from './VenueStep';
|
import { EMPTY_VENUE_FILTERS } from './VenueStep';
|
||||||
import { Navigation } from '../../organisms/Navigation';
|
import { Navigation } from '../../organisms/Navigation';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
@@ -91,6 +91,8 @@ export const Default: Story = {
|
|||||||
render: () => {
|
render: () => {
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [filters, setFilters] = useState<VenueFilterValues>(EMPTY_VENUE_FILTERS);
|
const [filters, setFilters] = useState<VenueFilterValues>(EMPTY_VENUE_FILTERS);
|
||||||
|
const [sort, setSort] = useState<VenueSortBy>('recommended');
|
||||||
|
const [view, setView] = useState<ListViewMode>('list');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VenueStep
|
<VenueStep
|
||||||
@@ -100,6 +102,10 @@ export const Default: Story = {
|
|||||||
onSearchChange={setSearch}
|
onSearchChange={setSearch}
|
||||||
filterValues={filters}
|
filterValues={filters}
|
||||||
onFilterChange={setFilters}
|
onFilterChange={setFilters}
|
||||||
|
sortBy={sort}
|
||||||
|
onSortChange={setSort}
|
||||||
|
viewMode={view}
|
||||||
|
onViewModeChange={setView}
|
||||||
onBack={() => alert('Back')}
|
onBack={() => alert('Back')}
|
||||||
locationName="Strathfield"
|
locationName="Strathfield"
|
||||||
navigation={nav}
|
navigation={nav}
|
||||||
@@ -110,7 +116,7 @@ export const Default: Story = {
|
|||||||
|
|
||||||
// ─── With active filters ────────────────────────────────────────────────────
|
// ─── With active filters ────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** Filters pre-applied — chapel + video streaming + Catholic */
|
/** Filters pre-applied — chapel + video streaming + Catholic, sorted nearest */
|
||||||
export const WithActiveFilters: Story = {
|
export const WithActiveFilters: Story = {
|
||||||
render: () => {
|
render: () => {
|
||||||
const [search, setSearch] = useState('Strathfield');
|
const [search, setSearch] = useState('Strathfield');
|
||||||
@@ -120,6 +126,8 @@ export const WithActiveFilters: Story = {
|
|||||||
photoDisplay: false,
|
photoDisplay: false,
|
||||||
tradition: 'Catholic',
|
tradition: 'Catholic',
|
||||||
});
|
});
|
||||||
|
const [sort, setSort] = useState<VenueSortBy>('nearest');
|
||||||
|
const [view, setView] = useState<ListViewMode>('list');
|
||||||
|
|
||||||
const filtered = sampleVenues.filter((v) =>
|
const filtered = sampleVenues.filter((v) =>
|
||||||
v.location.toLowerCase().includes(search.toLowerCase()),
|
v.location.toLowerCase().includes(search.toLowerCase()),
|
||||||
@@ -133,6 +141,10 @@ export const WithActiveFilters: Story = {
|
|||||||
onSearchChange={setSearch}
|
onSearchChange={setSearch}
|
||||||
filterValues={filters}
|
filterValues={filters}
|
||||||
onFilterChange={setFilters}
|
onFilterChange={setFilters}
|
||||||
|
sortBy={sort}
|
||||||
|
onSortChange={setSort}
|
||||||
|
viewMode={view}
|
||||||
|
onViewModeChange={setView}
|
||||||
onBack={() => alert('Back')}
|
onBack={() => alert('Back')}
|
||||||
locationName="Strathfield"
|
locationName="Strathfield"
|
||||||
navigation={nav}
|
navigation={nav}
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import TextField from '@mui/material/TextField';
|
|||||||
import InputAdornment from '@mui/material/InputAdornment';
|
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 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 LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined';
|
||||||
import type { SxProps, Theme } from '@mui/material/styles';
|
import type { SxProps, Theme } from '@mui/material/styles';
|
||||||
import { WizardLayout } from '../../templates/WizardLayout';
|
import { WizardLayout } from '../../templates/WizardLayout';
|
||||||
@@ -46,6 +51,12 @@ export interface VenueFilterValues {
|
|||||||
tradition: string | null;
|
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 */
|
/** Props for the VenueStep page component */
|
||||||
export interface VenueStepProps {
|
export interface VenueStepProps {
|
||||||
/** List of venues to display */
|
/** List of venues to display */
|
||||||
@@ -66,6 +77,14 @@ export interface VenueStepProps {
|
|||||||
venueTypeOptions?: VenueTypeOption[];
|
venueTypeOptions?: VenueTypeOption[];
|
||||||
/** Available service tradition options for the autocomplete */
|
/** Available service tradition options for the autocomplete */
|
||||||
traditionOptions?: string[];
|
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 */
|
/** Callback for back navigation */
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
/** Location name for the results count */
|
/** Location name for the results count */
|
||||||
@@ -120,6 +139,13 @@ const DEFAULT_TRADITIONS = [
|
|||||||
'Uniting Church',
|
'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 = {
|
export const EMPTY_VENUE_FILTERS: VenueFilterValues = {
|
||||||
venueTypes: [],
|
venueTypes: [],
|
||||||
videoStreaming: false,
|
videoStreaming: false,
|
||||||
@@ -171,6 +197,10 @@ export const VenueStep: React.FC<VenueStepProps> = ({
|
|||||||
onFilterChange,
|
onFilterChange,
|
||||||
venueTypeOptions = DEFAULT_VENUE_TYPES,
|
venueTypeOptions = DEFAULT_VENUE_TYPES,
|
||||||
traditionOptions = DEFAULT_TRADITIONS,
|
traditionOptions = DEFAULT_TRADITIONS,
|
||||||
|
sortBy = 'recommended',
|
||||||
|
onSortChange,
|
||||||
|
viewMode = 'list',
|
||||||
|
onViewModeChange,
|
||||||
onBack,
|
onBack,
|
||||||
locationName,
|
locationName,
|
||||||
isPrePlanning = false,
|
isPrePlanning = false,
|
||||||
@@ -280,8 +310,77 @@ export const VenueStep: React.FC<VenueStepProps> = ({
|
|||||||
sx={{ mb: 1.5 }}
|
sx={{ mb: 1.5 }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Filters — right-aligned below search */}
|
{/* Control bar — results count, sort, view toggle, filters */}
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 2 }}>
|
<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}>
|
<FilterPanel activeCount={activeCount} onClear={handleClear}>
|
||||||
{/* ── Location ── */}
|
{/* ── Location ── */}
|
||||||
<Box>
|
<Box>
|
||||||
@@ -395,17 +494,6 @@ export const VenueStep: React.FC<VenueStepProps> = ({
|
|||||||
</Box>
|
</Box>
|
||||||
</FilterPanel>
|
</FilterPanel>
|
||||||
</Box>
|
</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>
|
</Box>
|
||||||
|
|
||||||
{/* Venue list — click-to-navigate */}
|
{/* Venue list — click-to-navigate */}
|
||||||
|
|||||||
Reference in New Issue
Block a user