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:
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
fullWidth
|
||||
/>
|
||||
{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"
|
||||
fullWidth
|
||||
/>
|
||||
{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>
|
||||
|
||||
|
||||
@@ -1,7 +1,2 @@
|
||||
export { CemeteryStep, default } from './CemeteryStep';
|
||||
export type {
|
||||
CemeteryStepProps,
|
||||
CemeteryStepValues,
|
||||
CemeteryStepErrors,
|
||||
CemeteryOption,
|
||||
} from './CemeteryStep';
|
||||
export type { CemeteryStepProps, CemeteryStepValues, CemeteryStepErrors } from './CemeteryStep';
|
||||
|
||||
Reference in New Issue
Block a user