CemeteryStep: freetext search input replaces static dropdown

- MUI Autocomplete with static list replaced by Input atom with
  search icon — parent wires to Google Places or geocoding API
- Optional searchSlot prop for custom autocomplete integration
- Removed CemeteryOption type and cemeteries prop
- cemeterySearch field stores freetext, not a selected ID

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-30 22:19:35 +11:00
parent 7b126ac9df
commit 42a2802998
3 changed files with 34 additions and 91 deletions

View File

@@ -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 { CemeteryStep } from './CemeteryStep'; import { CemeteryStep } from './CemeteryStep';
import type { CemeteryStepValues, CemeteryOption } from './CemeteryStep'; import type { CemeteryStepValues } from './CemeteryStep';
import { Navigation } from '../../organisms/Navigation'; import { Navigation } from '../../organisms/Navigation';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
@@ -34,19 +34,10 @@ const nav = (
/> />
); );
const sampleCemeteries: CemeteryOption[] = [
{ value: 'rookwood', label: 'Rookwood Cemetery' },
{ value: 'northern-suburbs', label: 'Northern Suburbs Memorial Gardens' },
{ value: 'macquarie-park', label: 'Macquarie Park Cemetery' },
{ value: 'pinegrove', label: 'Pinegrove Memorial Park' },
{ value: 'waverley', label: 'Waverley Cemetery' },
{ value: 'botany', label: 'Botany Cemetery' },
];
const defaultValues: CemeteryStepValues = { const defaultValues: CemeteryStepValues = {
ownPlot: null, ownPlot: null,
hasPreference: null, hasPreference: null,
selectedCemetery: '', cemeterySearch: '',
}; };
// ─── Meta ──────────────────────────────────────────────────────────────────── // ─── Meta ────────────────────────────────────────────────────────────────────
@@ -76,7 +67,6 @@ export const Default: Story = {
onContinue={() => alert('Continue')} onContinue={() => alert('Continue')}
onBack={() => alert('Back')} onBack={() => alert('Back')}
onSaveAndExit={() => alert('Save')} onSaveAndExit={() => alert('Save')}
cemeteries={sampleCemeteries}
navigation={nav} navigation={nav}
/> />
); );
@@ -99,7 +89,6 @@ export const OwnsPlot: Story = {
onContinue={() => alert('Continue')} onContinue={() => alert('Continue')}
onBack={() => alert('Back')} onBack={() => alert('Back')}
onSaveAndExit={() => alert('Save')} onSaveAndExit={() => alert('Save')}
cemeteries={sampleCemeteries}
navigation={nav} navigation={nav}
/> />
); );
@@ -123,7 +112,6 @@ export const HasPreference: Story = {
onContinue={() => alert('Continue')} onContinue={() => alert('Continue')}
onBack={() => alert('Back')} onBack={() => alert('Back')}
onSaveAndExit={() => alert('Save')} onSaveAndExit={() => alert('Save')}
cemeteries={sampleCemeteries}
navigation={nav} navigation={nav}
/> />
); );
@@ -147,7 +135,6 @@ export const NoPreference: Story = {
onContinue={() => alert('Continue')} onContinue={() => alert('Continue')}
onBack={() => alert('Back')} onBack={() => alert('Back')}
onSaveAndExit={() => alert('Save')} onSaveAndExit={() => alert('Save')}
cemeteries={sampleCemeteries}
navigation={nav} navigation={nav}
/> />
); );
@@ -166,7 +153,6 @@ export const PrePlanning: Story = {
onChange={setValues} onChange={setValues}
onContinue={() => alert('Continue')} onContinue={() => alert('Continue')}
onBack={() => alert('Back')} onBack={() => alert('Back')}
cemeteries={sampleCemeteries}
isPrePlanning isPrePlanning
navigation={nav} navigation={nav}
/> />

View File

@@ -1,11 +1,9 @@
import React from 'react'; import React from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Autocomplete from '@mui/material/Autocomplete';
import TextField from '@mui/material/TextField';
import SearchOutlinedIcon from '@mui/icons-material/SearchOutlined'; import SearchOutlinedIcon from '@mui/icons-material/SearchOutlined';
import InputAdornment from '@mui/material/InputAdornment';
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';
import { Input } from '../../atoms/Input';
import { ToggleButtonGroup } from '../../atoms/ToggleButtonGroup'; import { ToggleButtonGroup } from '../../atoms/ToggleButtonGroup';
import { Collapse } from '../../atoms/Collapse'; import { Collapse } from '../../atoms/Collapse';
import { Typography } from '../../atoms/Typography'; import { Typography } from '../../atoms/Typography';
@@ -14,27 +12,21 @@ import { Divider } from '../../atoms/Divider';
// ─── Types ─────────────────────────────────────────────────────────────────── // ─── Types ───────────────────────────────────────────────────────────────────
/** A cemetery option for the dropdown */
export interface CemeteryOption {
value: string;
label: string;
}
/** Form values for the cemetery step */ /** Form values for the cemetery step */
export interface CemeteryStepValues { export interface CemeteryStepValues {
/** Does the family already own a burial plot? */ /** Does the family already own a burial plot? */
ownPlot: 'yes' | 'no' | 'unsure' | null; ownPlot: 'yes' | 'no' | 'unsure' | null;
/** Do they have a preference for a cemetery? (when ownPlot ≠ yes) */ /** Do they have a preference for a cemetery? (when ownPlot ≠ yes) */
hasPreference: 'yes' | 'no' | 'unsure' | null; hasPreference: 'yes' | 'no' | 'unsure' | null;
/** Selected cemetery ID */ /** Cemetery search text (freetext — parent wires to Places API) */
selectedCemetery: string; cemeterySearch: string;
} }
/** Field-level error messages */ /** Field-level error messages */
export interface CemeteryStepErrors { export interface CemeteryStepErrors {
ownPlot?: string; ownPlot?: string;
hasPreference?: string; hasPreference?: string;
selectedCemetery?: string; cemeterySearch?: string;
} }
/** Props for the CemeteryStep page component */ /** Props for the CemeteryStep page component */
@@ -53,8 +45,8 @@ export interface CemeteryStepProps {
errors?: CemeteryStepErrors; errors?: CemeteryStepErrors;
/** Whether Continue is loading */ /** Whether Continue is loading */
loading?: boolean; loading?: boolean;
/** Available cemeteries for the dropdown */ /** Slot for a location autocomplete (e.g. Google Places) — rendered below the search input */
cemeteries: CemeteryOption[]; searchSlot?: React.ReactNode;
/** Whether this is a pre-planning flow */ /** Whether this is a pre-planning flow */
isPrePlanning?: boolean; isPrePlanning?: boolean;
/** Navigation bar */ /** Navigation bar */
@@ -95,7 +87,7 @@ export const CemeteryStep: React.FC<CemeteryStepProps> = ({
onSaveAndExit, onSaveAndExit,
errors, errors,
loading = false, loading = false,
cemeteries, searchSlot,
isPrePlanning = false, isPrePlanning = false,
navigation, navigation,
progressStepper, progressStepper,
@@ -113,7 +105,7 @@ export const CemeteryStep: React.FC<CemeteryStepProps> = ({
ownPlot: (value ?? null) as CemeteryStepValues['ownPlot'], ownPlot: (value ?? null) as CemeteryStepValues['ownPlot'],
// Reset dependent fields // Reset dependent fields
hasPreference: null, hasPreference: null,
selectedCemetery: '', cemeterySearch: '',
}); });
}; };
@@ -121,7 +113,7 @@ export const CemeteryStep: React.FC<CemeteryStepProps> = ({
onChange({ onChange({
...values, ...values,
hasPreference: (value ?? null) as CemeteryStepValues['hasPreference'], hasPreference: (value ?? null) as CemeteryStepValues['hasPreference'],
selectedCemetery: '', cemeterySearch: '',
}); });
}; };
@@ -181,32 +173,17 @@ export const CemeteryStep: React.FC<CemeteryStepProps> = ({
Tell us where the burial plot is located. Tell us where the burial plot is located.
</Typography> </Typography>
<Autocomplete {searchSlot ?? (
options={cemeteries} <Input
getOptionLabel={(option) => option.label} value={values.cemeterySearch}
value={cemeteries.find((c) => c.value === values.selectedCemetery) ?? null} onChange={(e) => onChange({ ...values, cemeterySearch: e.target.value })}
onChange={(_, option) => placeholder="Search for a cemetery or location..."
onChange({ ...values, selectedCemetery: option?.value ?? '' }) startIcon={<SearchOutlinedIcon />}
} error={!!errors?.cemeterySearch}
renderInput={(params) => ( helperText={errors?.cemeterySearch}
<TextField
{...params}
placeholder="Search for a cemetery..."
error={!!errors?.selectedCemetery}
helperText={errors?.selectedCemetery}
InputProps={{
...params.InputProps,
startAdornment: (
<InputAdornment position="start">
<SearchOutlinedIcon sx={{ fontSize: 20, color: 'text.secondary' }} />
</InputAdornment>
),
}}
/>
)}
noOptionsText="No cemeteries found"
fullWidth fullWidth
/> />
)}
</Box> </Box>
</Collapse> </Collapse>
@@ -232,32 +209,17 @@ export const CemeteryStep: React.FC<CemeteryStepProps> = ({
{/* ─── Preference: search cemetery ─── */} {/* ─── Preference: search cemetery ─── */}
<Collapse in={showCemeteryForPreference}> <Collapse in={showCemeteryForPreference}>
<Box sx={{ mb: 4 }}> <Box sx={{ mb: 4 }}>
<Autocomplete {searchSlot ?? (
options={cemeteries} <Input
getOptionLabel={(option) => option.label} value={values.cemeterySearch}
value={cemeteries.find((c) => c.value === values.selectedCemetery) ?? null} onChange={(e) => onChange({ ...values, cemeterySearch: e.target.value })}
onChange={(_, option) => placeholder="Search for a cemetery or location..."
onChange({ ...values, selectedCemetery: option?.value ?? '' }) startIcon={<SearchOutlinedIcon />}
} error={!!errors?.cemeterySearch}
renderInput={(params) => ( helperText={errors?.cemeterySearch}
<TextField
{...params}
placeholder="Search for a cemetery..."
error={!!errors?.selectedCemetery}
helperText={errors?.selectedCemetery}
InputProps={{
...params.InputProps,
startAdornment: (
<InputAdornment position="start">
<SearchOutlinedIcon sx={{ fontSize: 20, color: 'text.secondary' }} />
</InputAdornment>
),
}}
/>
)}
noOptionsText="No cemeteries found"
fullWidth fullWidth
/> />
)}
</Box> </Box>
</Collapse> </Collapse>

View File

@@ -1,7 +1,2 @@
export { CemeteryStep, default } from './CemeteryStep'; export { CemeteryStep, default } from './CemeteryStep';
export type { export type { CemeteryStepProps, CemeteryStepValues, CemeteryStepErrors } from './CemeteryStep';
CemeteryStepProps,
CemeteryStepValues,
CemeteryStepErrors,
CemeteryOption,
} from './CemeteryStep';