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 type { Meta, StoryObj } from '@storybook/react';
import { CemeteryStep } from './CemeteryStep';
import type { CemeteryStepValues, CemeteryOption } from './CemeteryStep';
import type { CemeteryStepValues } from './CemeteryStep';
import { Navigation } from '../../organisms/Navigation';
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 = {
ownPlot: null,
hasPreference: null,
selectedCemetery: '',
cemeterySearch: '',
};
// ─── Meta ────────────────────────────────────────────────────────────────────
@@ -76,7 +67,6 @@ export const Default: Story = {
onContinue={() => alert('Continue')}
onBack={() => alert('Back')}
onSaveAndExit={() => alert('Save')}
cemeteries={sampleCemeteries}
navigation={nav}
/>
);
@@ -99,7 +89,6 @@ export const OwnsPlot: Story = {
onContinue={() => alert('Continue')}
onBack={() => alert('Back')}
onSaveAndExit={() => alert('Save')}
cemeteries={sampleCemeteries}
navigation={nav}
/>
);
@@ -123,7 +112,6 @@ export const HasPreference: Story = {
onContinue={() => alert('Continue')}
onBack={() => alert('Back')}
onSaveAndExit={() => alert('Save')}
cemeteries={sampleCemeteries}
navigation={nav}
/>
);
@@ -147,7 +135,6 @@ export const NoPreference: Story = {
onContinue={() => alert('Continue')}
onBack={() => alert('Back')}
onSaveAndExit={() => alert('Save')}
cemeteries={sampleCemeteries}
navigation={nav}
/>
);
@@ -166,7 +153,6 @@ export const PrePlanning: Story = {
onChange={setValues}
onContinue={() => alert('Continue')}
onBack={() => alert('Back')}
cemeteries={sampleCemeteries}
isPrePlanning
navigation={nav}
/>

View File

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

View File

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