format: Apply Prettier to existing codebase

Formatting-only changes across all component and story files.
No logic or behaviour changes — only whitespace, line breaks, and trailing commas.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-27 16:42:16 +11:00
parent aa7cdeecf0
commit 047d913960
46 changed files with 1510 additions and 886 deletions

View File

@@ -9,7 +9,12 @@ import { Typography } from '../../atoms/Typography';
const funeralTypes = [
{ id: 'cremation', label: 'Cremation', hasServiceOption: true },
{ id: 'burial', label: 'Burial', hasServiceOption: true },
{ id: 'water-burial', label: 'Water Burial', note: 'Available in QLD only', hasServiceOption: false },
{
id: 'water-burial',
label: 'Water Burial',
note: 'Available in QLD only',
hasServiceOption: false,
},
];
const themeOptions = [
@@ -152,8 +157,8 @@ export const InHeroDesktop: Story = {
color="text.secondary"
sx={{ textAlign: 'center', mb: 4, maxWidth: 440, mx: 'auto' }}
>
Whether you're thinking ahead or arranging for a loved one, find
trusted local providers with transparent pricing.
Whether you're thinking ahead or arranging for a loved one, find trusted local
providers with transparent pricing.
</Typography>
<FuneralFinder
funeralTypes={funeralTypes}
@@ -199,7 +204,11 @@ export const InHeroMobile: Story = {
]}
/>
<Box sx={{ bgcolor: 'var(--fa-color-brand-100)', px: 3, py: 4, textAlign: 'center' }}>
<Typography variant="h3" component="h1" sx={{ mb: 1.5, color: 'var(--fa-color-brand-950)' }}>
<Typography
variant="h3"
component="h1"
sx={{ mb: 1.5, color: 'var(--fa-color-brand-950)' }}
>
Discover, Explore, and Plan Funerals in Minutes
</Typography>
<Typography variant="body2" color="text.secondary">
@@ -210,7 +219,8 @@ export const InHeroMobile: Story = {
sx={{
height: 180,
bgcolor: 'var(--fa-color-brand-200)',
backgroundImage: 'url(https://images.unsplash.com/photo-1516733968668-dbdce39c0571?w=800&h=400&fit=crop)',
backgroundImage:
'url(https://images.unsplash.com/photo-1516733968668-dbdce39c0571?w=800&h=400&fit=crop)',
backgroundSize: 'cover',
backgroundPosition: 'center',
}}

View File

@@ -146,7 +146,11 @@ function ChoiceCard({
</Typography>
</Box>
{description && (
<Typography variant="caption" component="span" sx={{ display: 'block', mt: 0.5, color: 'text.secondary' }}>
<Typography
variant="caption"
component="span"
sx={{ display: 'block', mt: 0.5, color: 'text.secondary' }}
>
{description}
</Typography>
)}
@@ -214,12 +218,20 @@ function TypeCard({
</Typography>
</Box>
{description && (
<Typography variant="caption" component="span" sx={{ display: 'block', mt: 0.25, color: 'text.secondary' }}>
<Typography
variant="caption"
component="span"
sx={{ display: 'block', mt: 0.25, color: 'text.secondary' }}
>
{description}
</Typography>
)}
{note && (
<Typography variant="captionSm" component="span" sx={{ display: 'block', mt: 0.5, color: 'text.secondary', fontWeight: 500 }}>
<Typography
variant="captionSm"
component="span"
sx={{ display: 'block', mt: 0.5, color: 'text.secondary', fontWeight: 500 }}
>
{note}
</Typography>
)}
@@ -261,7 +273,13 @@ function CompletedRow({
onClick={onChangeClick}
underline="hover"
aria-label={`Change ${question.toLowerCase()}`}
sx={{ color: 'text.secondary', ml: 'auto', minHeight: 44, display: 'inline-flex', alignItems: 'center' }}
sx={{
color: 'text.secondary',
ml: 'auto',
minHeight: 44,
display: 'inline-flex',
alignItems: 'center',
}}
>
Change
</Link>
@@ -348,9 +366,11 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
const typeSummary = [typeLabel, themeSuffix].filter(Boolean).join(', ');
const serviceLabel =
servicePref === 'with-service' ? 'With a service'
: servicePref === 'without-service' ? 'No service'
: 'Flexible';
servicePref === 'with-service'
? 'With a service'
: servicePref === 'without-service'
? 'No service'
: 'Flexible';
// ─── Handlers ───────────────────────────────────────────────────
const selectIntent = (value: Intent) => {
@@ -409,7 +429,7 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
intent,
planningFor: needsPlanningFor ? (planningFor ?? undefined) : undefined,
funeralTypeId: isExploreAll ? null : (typeSelection ?? null),
servicePreference: (showServiceStep && serviceAnswered) ? servicePref : 'either',
servicePreference: showServiceStep && serviceAnswered ? servicePref : 'either',
themes: selectedThemes,
location: location.trim(),
});
@@ -448,16 +468,32 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
{/* ── Completed rows ─────────────────────────────────────── */}
<Collapse in={intent !== null && activeStep !== 1} timeout={250}>
<CompletedRow question="I'm here to" answer={intentLabel} onChangeClick={() => revertTo(1)} />
<CompletedRow
question="I'm here to"
answer={intentLabel}
onChangeClick={() => revertTo(1)}
/>
</Collapse>
<Collapse in={needsPlanningFor && planningFor !== null && activeStep !== 2} timeout={250}>
<CompletedRow question="Planning for" answer={planningForLabel} onChangeClick={() => revertTo(2)} />
<CompletedRow
question="Planning for"
answer={planningForLabel}
onChangeClick={() => revertTo(2)}
/>
</Collapse>
<Collapse in={typeSelected && activeStep !== 3} timeout={250}>
<CompletedRow question="Looking for" answer={typeSummary} onChangeClick={() => revertTo(3)} />
<CompletedRow
question="Looking for"
answer={typeSummary}
onChangeClick={() => revertTo(3)}
/>
</Collapse>
<Collapse in={showServiceStep && serviceAnswered && activeStep !== 4} timeout={250}>
<CompletedRow question="Service" answer={serviceLabel} onChangeClick={() => revertTo(4)} />
<CompletedRow
question="Service"
answer={serviceLabel}
onChangeClick={() => revertTo(4)}
/>
</Collapse>
{/* ── Step 1: Intent ─────────────────────────────────────── */}
@@ -467,13 +503,22 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
<Typography
variant="caption"
role="alert"
sx={{ color: 'var(--fa-color-brand-600)', textAlign: 'center', display: 'block', mb: 1.5 }}
sx={{
color: 'var(--fa-color-brand-600)',
textAlign: 'center',
display: 'block',
mb: 1.5,
}}
>
Please let us know how we can help
</Typography>
)}
<StepHeading>How can we help you today?</StepHeading>
<Box role="radiogroup" aria-label="How can we help" sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
<Box
role="radiogroup"
aria-label="How can we help"
sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}
>
<ChoiceCard
label="Arrange a funeral now"
description="Someone has passed and I need to make arrangements"
@@ -494,7 +539,11 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
<Collapse in={activeStep === 2 && needsPlanningFor} timeout={250}>
<Box sx={{ mt: 3 }}>
<StepHeading>Who are you planning for?</StepHeading>
<Box role="radiogroup" aria-label="Who are you planning for" sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
<Box
role="radiogroup"
aria-label="Who are you planning for"
sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}
>
<ChoiceCard
label="Myself"
description="I want to plan my own funeral in advance"
@@ -515,7 +564,11 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
<Collapse in={activeStep === 3} timeout={250}>
<Box sx={{ mt: 3 }}>
<StepHeading>What type of funeral are you considering?</StepHeading>
<Box role="radiogroup" aria-label="Type of funeral" sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
<Box
role="radiogroup"
aria-label="Type of funeral"
sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}
>
{funeralTypes.map((ft) => (
<TypeCard
key={ft.id}
@@ -543,11 +596,20 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
<Typography variant="body2" component="span" sx={{ fontWeight: 600 }}>
Any preferences?
</Typography>
<Typography variant="caption" component="span" color="text.secondary" sx={{ ml: 0.75 }}>
<Typography
variant="caption"
component="span"
color="text.secondary"
sx={{ ml: 0.75 }}
>
(optional)
</Typography>
</Box>
<Box role="group" aria-label="Preferences" sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<Box
role="group"
aria-label="Preferences"
sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}
>
{themeOptions.map((theme) => {
const isSelected = selectedThemes.includes(theme.id);
return (
@@ -573,7 +635,11 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
<Collapse in={activeStep === 4 && showServiceStep} timeout={250}>
<Box sx={{ mt: 3 }}>
<StepHeading>Would you like a service?</StepHeading>
<Box role="group" aria-label="Service preference" sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box
role="group"
aria-label="Service preference"
sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}
>
{SERVICE_OPTIONS.map((opt) => (
<Chip
key={opt.value}
@@ -583,7 +649,11 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
onClick={() => selectService(opt.value)}
clickable
aria-pressed={serviceAnswered && servicePref === opt.value}
sx={{ justifyContent: 'flex-start', height: 44, borderRadius: 'var(--fa-border-radius-md)' }}
sx={{
justifyContent: 'flex-start',
height: 44,
borderRadius: 'var(--fa-border-radius-md)',
}}
/>
))}
</Box>

View File

@@ -41,12 +41,8 @@ export const BelowMasthead: Story = {
textAlign: 'center',
}}
>
<Box sx={{ fontSize: '2rem', fontWeight: 700, mb: 1 }}>
Funeral Arranger
</Box>
<Box sx={{ opacity: 0.8 }}>
Find trusted funeral directors near you
</Box>
<Box sx={{ fontSize: '2rem', fontWeight: 700, mb: 1 }}>Funeral Arranger</Box>
<Box sx={{ opacity: 0.8 }}>Find trusted funeral directors near you</Box>
</Box>
{/* Widget below masthead */}
<Box sx={{ maxWidth: 560, mx: 'auto', mt: -4, px: 2, position: 'relative', zIndex: 1 }}>

View File

@@ -103,7 +103,10 @@ function StepCircle({
transition: 'background-color 200ms ease, color 200ms ease',
...(usePrimary
? { bgcolor: 'var(--fa-color-brand-500)', color: 'common.white' }
: { bgcolor: 'var(--fa-color-brand-200, #EBDAC8)', color: 'var(--fa-color-brand-700, #8B4E0D)' }),
: {
bgcolor: 'var(--fa-color-brand-200, #EBDAC8)',
color: 'var(--fa-color-brand-700, #8B4E0D)',
}),
// Connector line from bottom of this circle toward the next
...(showConnector && {
'&::after': {
@@ -245,7 +248,8 @@ export const FuneralFinderV2 = React.forwardRef<HTMLDivElement, FuneralFinderV2P
}
: { lookingTo: false, planningFor: false, funeralType: false, location: false };
const hasErrors = submitted && (errs.lookingTo || errs.planningFor || errs.funeralType || errs.location);
const hasErrors =
submitted && (errs.lookingTo || errs.planningFor || errs.funeralType || errs.location);
// ─── Handlers ────────────────────────────────────────────────
const handleLookingTo = (e: SelectChangeEvent<string>) => {
@@ -311,11 +315,7 @@ export const FuneralFinderV2 = React.forwardRef<HTMLDivElement, FuneralFinderV2P
>
{heading}
</Typography>
<Typography
variant="body2"
color="text.secondary"
sx={{ textAlign: 'center', mb: 0 }}
>
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', mb: 0 }}>
{subheading}
</Typography>
<Divider sx={{ my: 3.5 }} />
@@ -326,7 +326,10 @@ export const FuneralFinderV2 = React.forwardRef<HTMLDivElement, FuneralFinderV2P
<Box sx={{ display: 'flex', gap: 2.5, alignItems: 'flex-end' }}>
<StepCircle step={1} completed={!!lookingTo} active showConnector />
<Box sx={{ flex: 1 }}>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1, color: 'var(--fa-color-brand-700)' }}>
<Typography
variant="body1"
sx={{ fontWeight: 600, mb: 1, color: 'var(--fa-color-brand-700)' }}
>
I&rsquo;m looking to&hellip;
</Typography>
<Select
@@ -352,7 +355,14 @@ export const FuneralFinderV2 = React.forwardRef<HTMLDivElement, FuneralFinderV2P
<Box sx={{ display: 'flex', gap: 2.5, alignItems: 'flex-end' }}>
<StepCircle step={2} completed={!!planningFor} showConnector />
<Box sx={{ flex: 1 }}>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1, color: lookingTo ? 'var(--fa-color-brand-700)' : 'text.disabled' }}>
<Typography
variant="body1"
sx={{
fontWeight: 600,
mb: 1,
color: lookingTo ? 'var(--fa-color-brand-700)' : 'text.disabled',
}}
>
I&rsquo;m planning for
</Typography>
<Select
@@ -383,7 +393,14 @@ export const FuneralFinderV2 = React.forwardRef<HTMLDivElement, FuneralFinderV2P
<Box sx={{ display: 'flex', gap: 2.5, alignItems: 'flex-end' }}>
<StepCircle step={3} completed={!!funeralType} showConnector />
<Box sx={{ flex: 1 }}>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1, color: step3Disabled ? 'text.disabled' : 'var(--fa-color-brand-700)' }}>
<Typography
variant="body1"
sx={{
fontWeight: 600,
mb: 1,
color: step3Disabled ? 'text.disabled' : 'var(--fa-color-brand-700)',
}}
>
Type of funeral
</Typography>
<Select
@@ -410,7 +427,14 @@ export const FuneralFinderV2 = React.forwardRef<HTMLDivElement, FuneralFinderV2P
<Box sx={{ display: 'flex', gap: 2.5, alignItems: 'flex-end' }}>
<StepCircle step={4} completed={location.trim().length >= 3} />
<Box sx={{ flex: 1 }}>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1, color: step4Disabled ? 'text.disabled' : 'var(--fa-color-brand-700)' }}>
<Typography
variant="body1"
sx={{
fontWeight: 600,
mb: 1,
color: step4Disabled ? 'text.disabled' : 'var(--fa-color-brand-700)',
}}
>
Looking for providers in
</Typography>
<Input

View File

@@ -33,8 +33,7 @@ export const BelowMasthead: Story = {
<Box>
<Box
sx={{
background:
'linear-gradient(160deg, #2C2E35 0%, #4C5B6B 60%, #6B3C13 100%)',
background: 'linear-gradient(160deg, #2C2E35 0%, #4C5B6B 60%, #6B3C13 100%)',
color: '#fff',
py: 8,
px: 4,

View File

@@ -75,28 +75,15 @@ const FUNERAL_TYPE_OPTIONS: { value: FuneralType; label: string }[] = [
/** Hoisted outside component to avoid re-creation on render */
const selectPlaceholder = (
<span style={{ color: 'var(--fa-color-text-disabled)' }}>
Select funeral type
</span>
<span style={{ color: 'var(--fa-color-text-disabled)' }}>Select funeral type</span>
);
// ─── Sub-components ──────────────────────────────────────────────────────────
/** Uppercase section label — overline style */
function SectionLabel({
children,
id,
}: {
children: React.ReactNode;
id?: string;
}) {
function SectionLabel({ children, id }: { children: React.ReactNode; id?: string }) {
return (
<Typography
variant="overline"
component="div"
id={id}
sx={{ color: 'text.secondary' }}
>
<Typography variant="overline" component="div" id={id} sx={{ color: 'text.secondary' }}>
{children}
</Typography>
);
@@ -138,8 +125,7 @@ const StatusCard = React.forwardRef<
cursor: 'pointer',
fontFamily: 'inherit',
textAlign: 'center',
transition:
'border-color 200ms ease, background-color 200ms ease, transform 100ms ease',
transition: 'border-color 200ms ease, background-color 200ms ease, transform 100ms ease',
'&:hover': {
borderColor: selected
? 'var(--fa-color-border-brand, #BA834E)'
@@ -164,9 +150,7 @@ const StatusCard = React.forwardRef<
fontWeight: 600,
display: 'block',
mb: 0.75,
color: selected
? 'var(--fa-color-text-brand, #B0610F)'
: 'text.primary',
color: selected ? 'var(--fa-color-text-brand, #B0610F)' : 'text.primary',
}}
>
{title}
@@ -253,317 +237,309 @@ const selectMenuProps = {
* Required fields: status + location (min 3 chars).
* Funeral type defaults to "show all" if not selected.
*/
export const FuneralFinderV3 = React.forwardRef<
HTMLDivElement,
FuneralFinderV3Props
>((props, ref) => {
const {
onSearch,
loading = false,
heading = 'Find funeral directors near you',
subheading =
"Tell us what you need and we\u2019ll show options in your area.",
sx,
} = props;
export const FuneralFinderV3 = React.forwardRef<HTMLDivElement, FuneralFinderV3Props>(
(props, ref) => {
const {
onSearch,
loading = false,
heading = 'Find funeral directors near you',
subheading = 'Tell us what you need and we\u2019ll show options in your area.',
sx,
} = props;
// ─── IDs for aria-labelledby ──────────────────────────────
const id = React.useId();
const statusLabelId = `${id}-status`;
const funeralTypeLabelId = `${id}-funeral-type`;
const locationLabelId = `${id}-location`;
// ─── IDs for aria-labelledby ──────────────────────────────
const id = React.useId();
const statusLabelId = `${id}-status`;
const funeralTypeLabelId = `${id}-funeral-type`;
const locationLabelId = `${id}-location`;
// ─── State ───────────────────────────────────────────────
const [status, setStatus] = React.useState<Status | ''>('immediate');
const [funeralType, setFuneralType] = React.useState<FuneralType | ''>('');
const [location, setLocation] = React.useState('');
const [errors, setErrors] = React.useState<{
status?: boolean;
location?: boolean;
}>({});
// ─── State ───────────────────────────────────────────────
const [status, setStatus] = React.useState<Status | ''>('immediate');
const [funeralType, setFuneralType] = React.useState<FuneralType | ''>('');
const [location, setLocation] = React.useState('');
const [errors, setErrors] = React.useState<{
status?: boolean;
location?: boolean;
}>({});
// ─── Refs ────────────────────────────────────────────────
const statusSectionRef = React.useRef<HTMLDivElement>(null);
const locationSectionRef = React.useRef<HTMLDivElement>(null);
const locationInputRef = React.useRef<HTMLInputElement>(null);
const cardRefs = React.useRef<(HTMLButtonElement | null)[]>([null, null]);
// ─── Refs ────────────────────────────────────────────────
const statusSectionRef = React.useRef<HTMLDivElement>(null);
const locationSectionRef = React.useRef<HTMLDivElement>(null);
const locationInputRef = React.useRef<HTMLInputElement>(null);
const cardRefs = React.useRef<(HTMLButtonElement | null)[]>([null, null]);
// ─── Clear errors as fields are filled ───────────────────
const prevStatus = React.useRef(status);
React.useEffect(() => {
if (status !== prevStatus.current) {
prevStatus.current = status;
if (status && errors.status) {
setErrors((prev) => ({ ...prev, status: false }));
// ─── Clear errors as fields are filled ───────────────────
const prevStatus = React.useRef(status);
React.useEffect(() => {
if (status !== prevStatus.current) {
prevStatus.current = status;
if (status && errors.status) {
setErrors((prev) => ({ ...prev, status: false }));
}
}
}
}, [status, errors.status]);
}, [status, errors.status]);
const prevLocation = React.useRef(location);
React.useEffect(() => {
if (location !== prevLocation.current) {
prevLocation.current = location;
if (location.trim().length >= 3 && errors.location) {
setErrors((prev) => ({ ...prev, location: false }));
const prevLocation = React.useRef(location);
React.useEffect(() => {
if (location !== prevLocation.current) {
prevLocation.current = location;
if (location.trim().length >= 3 && errors.location) {
setErrors((prev) => ({ ...prev, location: false }));
}
}
}
}, [location, errors.location]);
}, [location, errors.location]);
// ─── Radiogroup keyboard nav (WAI-ARIA pattern) ──────────
const activeStatusIndex = status
? STATUS_OPTIONS.findIndex((o) => o.key === status)
: 0;
// ─── Radiogroup keyboard nav (WAI-ARIA pattern) ──────────
const activeStatusIndex = status ? STATUS_OPTIONS.findIndex((o) => o.key === status) : 0;
const handleStatusKeyDown = (e: React.KeyboardEvent) => {
const isNext = e.key === 'ArrowRight' || e.key === 'ArrowDown';
const isPrev = e.key === 'ArrowLeft' || e.key === 'ArrowUp';
if (!isNext && !isPrev) return;
e.preventDefault();
const current = cardRefs.current.indexOf(
e.target as HTMLButtonElement,
);
if (current === -1) return;
const next = isNext
? Math.min(current + 1, STATUS_OPTIONS.length - 1)
: Math.max(current - 1, 0);
if (next !== current) {
cardRefs.current[next]?.focus();
setStatus(STATUS_OPTIONS[next].key);
}
};
const handleStatusKeyDown = (e: React.KeyboardEvent) => {
const isNext = e.key === 'ArrowRight' || e.key === 'ArrowDown';
const isPrev = e.key === 'ArrowLeft' || e.key === 'ArrowUp';
if (!isNext && !isPrev) return;
e.preventDefault();
const current = cardRefs.current.indexOf(e.target as HTMLButtonElement);
if (current === -1) return;
const next = isNext
? Math.min(current + 1, STATUS_OPTIONS.length - 1)
: Math.max(current - 1, 0);
if (next !== current) {
cardRefs.current[next]?.focus();
setStatus(STATUS_OPTIONS[next].key);
}
};
// ─── Handlers ────────────────────────────────────────────
const handleFuneralType = (e: SelectChangeEvent<string>) => {
setFuneralType(e.target.value as FuneralType);
};
// ─── Handlers ────────────────────────────────────────────
const handleFuneralType = (e: SelectChangeEvent<string>) => {
setFuneralType(e.target.value as FuneralType);
};
const handleSubmit = () => {
if (!status) {
setErrors({ status: true });
statusSectionRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'center',
const handleSubmit = () => {
if (!status) {
setErrors({ status: true });
statusSectionRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
return;
}
if (location.trim().length < 3) {
setErrors({ location: true });
locationSectionRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
locationInputRef.current?.focus();
return;
}
setErrors({});
onSearch?.({
status,
funeralType: funeralType || 'show-all',
location: location.trim(),
});
return;
}
if (location.trim().length < 3) {
setErrors({ location: true });
locationSectionRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
locationInputRef.current?.focus();
return;
}
setErrors({});
onSearch?.({
status,
funeralType: funeralType || 'show-all',
location: location.trim(),
});
};
};
// ─── Render ──────────────────────────────────────────────
return (
<Box
ref={ref}
role="search"
aria-label="Find funeral directors"
sx={[
{
bgcolor: 'var(--fa-color-surface-raised, #fff)',
borderRadius: 'var(--fa-card-border-radius-default, 8px)',
boxShadow: 'var(--fa-card-shadow-default)',
px: { xs: 3.5, sm: 5 },
py: { xs: 4, sm: 5 },
display: 'flex',
flexDirection: 'column',
gap: 4,
},
...(Array.isArray(sx) ? sx : [sx]),
]}
>
{/* ── Header ──────────────────────────────────────────── */}
<Box sx={{ textAlign: 'center' }}>
<Typography
variant="h3"
component="h2"
sx={{
fontFamily: 'var(--fa-font-family-display)',
fontWeight: 400,
mb: 1,
}}
>
{heading}
</Typography>
<Typography variant="body2" color="text.secondary">
{subheading}
</Typography>
</Box>
<Divider />
{/* ── How can we help ─────────────────────────────────── */}
<Box ref={statusSectionRef}>
<SectionLabel id={statusLabelId}>How Can We Help</SectionLabel>
<Box
role="radiogroup"
aria-labelledby={statusLabelId}
sx={{
display: 'grid',
gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' },
gap: 2,
mt: 2,
}}
>
{STATUS_OPTIONS.map((opt, i) => (
<StatusCard
key={opt.key}
ref={(el) => {
cardRefs.current[i] = el;
}}
title={opt.title}
description={opt.description}
selected={status === opt.key}
onClick={() => setStatus(opt.key)}
tabIndex={i === activeStatusIndex ? 0 : -1}
onKeyDown={handleStatusKeyDown}
/>
))}
</Box>
<Box aria-live="polite" sx={{ textAlign: 'center' }}>
{errors.status && (
<Typography
variant="caption"
role="alert"
sx={{
color: 'var(--fa-color-text-brand, #B0610F)',
display: 'block',
mt: 1,
}}
>
Please select how we can help
</Typography>
)}
</Box>
</Box>
{/* ── Funeral Type ────────────────────────────────────── */}
<Box>
<SectionLabel id={funeralTypeLabelId}>Funeral Type</SectionLabel>
<Box sx={{ mt: 2 }}>
<Select
value={funeralType}
onChange={handleFuneralType}
displayEmpty
renderValue={(v) =>
v
? FUNERAL_TYPE_OPTIONS.find((o) => o.value === v)?.label
: selectPlaceholder
}
MenuProps={selectMenuProps}
// ─── Render ──────────────────────────────────────────────
return (
<Box
ref={ref}
role="search"
aria-label="Find funeral directors"
sx={[
{
bgcolor: 'var(--fa-color-surface-raised, #fff)',
borderRadius: 'var(--fa-card-border-radius-default, 8px)',
boxShadow: 'var(--fa-card-shadow-default)',
px: { xs: 3.5, sm: 5 },
py: { xs: 4, sm: 5 },
display: 'flex',
flexDirection: 'column',
gap: 4,
},
...(Array.isArray(sx) ? sx : [sx]),
]}
>
{/* ── Header ──────────────────────────────────────────── */}
<Box sx={{ textAlign: 'center' }}>
<Typography
variant="h3"
component="h2"
sx={{
...fieldBaseSx,
'& .MuiSelect-select': {
...fieldInputStyles,
minHeight: 'unset !important',
},
'& .MuiSelect-icon': {
color: 'var(--fa-color-text-disabled)',
right: 12,
},
fontFamily: 'var(--fa-font-family-display)',
fontWeight: 400,
mb: 1,
}}
inputProps={{ 'aria-labelledby': funeralTypeLabelId }}
>
{FUNERAL_TYPE_OPTIONS.map((o) => (
<MenuItem key={o.value} value={o.value}>
{o.label}
</MenuItem>
))}
</Select>
{heading}
</Typography>
<Typography variant="body2" color="text.secondary">
{subheading}
</Typography>
</Box>
</Box>
{/* ── Location ────────────────────────────────────────── */}
<Box ref={locationSectionRef}>
<SectionLabel id={locationLabelId}>Location</SectionLabel>
<Box sx={{ mt: 2 }}>
<OutlinedInput
value={location}
onChange={(e) => setLocation(e.target.value)}
placeholder="Enter suburb or postcode"
inputRef={locationInputRef}
startAdornment={
<InputAdornment position="start" sx={{ ml: 0.5 }}>
<LocationOnOutlinedIcon
sx={{
fontSize: 20,
color: 'var(--fa-color-text-disabled)',
}}
/>
</InputAdornment>
}
onKeyDown={(e) => {
if (e.key === 'Enter') handleSubmit();
}}
<Divider />
{/* ── How can we help ─────────────────────────────────── */}
<Box ref={statusSectionRef}>
<SectionLabel id={statusLabelId}>How Can We Help</SectionLabel>
<Box
role="radiogroup"
aria-labelledby={statusLabelId}
sx={{
...fieldBaseSx,
'& .MuiOutlinedInput-input': {
...fieldInputStyles,
'&::placeholder': {
color: 'var(--fa-color-text-disabled)',
opacity: 1,
},
},
display: 'grid',
gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' },
gap: 2,
mt: 2,
}}
inputProps={{
'aria-labelledby': locationLabelId,
'aria-required': true,
}}
/>
>
{STATUS_OPTIONS.map((opt, i) => (
<StatusCard
key={opt.key}
ref={(el) => {
cardRefs.current[i] = el;
}}
title={opt.title}
description={opt.description}
selected={status === opt.key}
onClick={() => setStatus(opt.key)}
tabIndex={i === activeStatusIndex ? 0 : -1}
onKeyDown={handleStatusKeyDown}
/>
))}
</Box>
<Box aria-live="polite" sx={{ textAlign: 'center' }}>
{errors.status && (
<Typography
variant="caption"
role="alert"
sx={{
color: 'var(--fa-color-text-brand, #B0610F)',
display: 'block',
mt: 1,
}}
>
Please select how we can help
</Typography>
)}
</Box>
</Box>
<Box aria-live="polite">
{errors.location && (
<Typography
variant="caption"
role="alert"
{/* ── Funeral Type ────────────────────────────────────── */}
<Box>
<SectionLabel id={funeralTypeLabelId}>Funeral Type</SectionLabel>
<Box sx={{ mt: 2 }}>
<Select
value={funeralType}
onChange={handleFuneralType}
displayEmpty
renderValue={(v) =>
v ? FUNERAL_TYPE_OPTIONS.find((o) => o.value === v)?.label : selectPlaceholder
}
MenuProps={selectMenuProps}
sx={{
color: 'var(--fa-color-text-brand, #B0610F)',
display: 'block',
mt: 1,
...fieldBaseSx,
'& .MuiSelect-select': {
...fieldInputStyles,
minHeight: 'unset !important',
},
'& .MuiSelect-icon': {
color: 'var(--fa-color-text-disabled)',
right: 12,
},
}}
inputProps={{ 'aria-labelledby': funeralTypeLabelId }}
>
Please enter a suburb or postcode
</Typography>
)}
{FUNERAL_TYPE_OPTIONS.map((o) => (
<MenuItem key={o.value} value={o.value}>
{o.label}
</MenuItem>
))}
</Select>
</Box>
</Box>
{/* ── Location ────────────────────────────────────────── */}
<Box ref={locationSectionRef}>
<SectionLabel id={locationLabelId}>Location</SectionLabel>
<Box sx={{ mt: 2 }}>
<OutlinedInput
value={location}
onChange={(e) => setLocation(e.target.value)}
placeholder="Enter suburb or postcode"
inputRef={locationInputRef}
startAdornment={
<InputAdornment position="start" sx={{ ml: 0.5 }}>
<LocationOnOutlinedIcon
sx={{
fontSize: 20,
color: 'var(--fa-color-text-disabled)',
}}
/>
</InputAdornment>
}
onKeyDown={(e) => {
if (e.key === 'Enter') handleSubmit();
}}
sx={{
...fieldBaseSx,
'& .MuiOutlinedInput-input': {
...fieldInputStyles,
'&::placeholder': {
color: 'var(--fa-color-text-disabled)',
opacity: 1,
},
},
}}
inputProps={{
'aria-labelledby': locationLabelId,
'aria-required': true,
}}
/>
</Box>
<Box aria-live="polite">
{errors.location && (
<Typography
variant="caption"
role="alert"
sx={{
color: 'var(--fa-color-text-brand, #B0610F)',
display: 'block',
mt: 1,
}}
>
Please enter a suburb or postcode
</Typography>
)}
</Box>
</Box>
<Divider />
{/* ── CTA ─────────────────────────────────────────────── */}
<Box>
<Button
variant="contained"
size="large"
fullWidth
loading={loading}
endIcon={!loading ? <ArrowForwardIcon /> : undefined}
onClick={handleSubmit}
sx={{ minHeight: 52 }}
>
Find Funeral Directors
</Button>
<Typography
variant="captionSm"
color="text.secondary"
sx={{ textAlign: 'center', display: 'block', mt: 1.5 }}
>
Free to use &middot; No obligation
</Typography>
</Box>
</Box>
<Divider />
{/* ── CTA ─────────────────────────────────────────────── */}
<Box>
<Button
variant="contained"
size="large"
fullWidth
loading={loading}
endIcon={!loading ? <ArrowForwardIcon /> : undefined}
onClick={handleSubmit}
sx={{ minHeight: 52 }}
>
Find Funeral Directors
</Button>
<Typography
variant="captionSm"
color="text.secondary"
sx={{ textAlign: 'center', display: 'block', mt: 1.5 }}
>
Free to use &middot; No obligation
</Typography>
</Box>
</Box>
);
});
);
},
);
FuneralFinderV3.displayName = 'FuneralFinderV3';
export default FuneralFinderV3;