Feedback iteration: DialogShell, page consistency, popup standardisation
- Add DialogShell atom — shared dialog container (header, scrollable body, footer)
- Refactor FilterPanel to use DialogShell (Popover → centered Dialog)
- Refactor ArrangementDialog to use DialogShell
- Remove PreviewStep + AuthGateStep pages (consolidated into ArrangementDialog, D-E)
- IntroStep: static subheading, top-left aligned toggle button content
- ProvidersStep: h4 heading "Find a funeral director", location search with pin icon,
filter moved below search right-aligned, map fill fix, hover scrollbar
- VenueStep: same consistency fixes (h4 heading, filter layout, location icon, map fix)
- PackagesStep: grouped packages ("Matching your preferences" / "Other packages from
[Provider]"), removed budget filter + Most Popular badge, clickable provider card,
onArrange replaces onContinue, h4 heading
- WizardLayout: list-map left panel gets thin scrollbar visible on hover
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -43,14 +43,13 @@ const mockProvider: PackagesStepProvider = {
|
||||
reviewCount: 7,
|
||||
};
|
||||
|
||||
const mockPackages: PackageData[] = [
|
||||
const matchedPackages: PackageData[] = [
|
||||
{
|
||||
id: 'everyday',
|
||||
name: 'Everyday Funeral Package',
|
||||
price: 2700,
|
||||
description:
|
||||
'This package includes a funeral service at a chapel or a church with a funeral procession. It includes many of the most commonly selected funeral options.',
|
||||
popular: true,
|
||||
sections: [
|
||||
{
|
||||
heading: 'Essentials',
|
||||
@@ -86,6 +85,27 @@ const mockPackages: PackageData[] = [
|
||||
terms:
|
||||
'This package includes a funeral service at a chapel or a church with a funeral procession. Pricing may vary based on additional selections.',
|
||||
},
|
||||
{
|
||||
id: 'essential',
|
||||
name: 'Essential Funeral Package',
|
||||
price: 1800,
|
||||
description:
|
||||
'A simple, dignified option covering the essential requirements for a cremation service.',
|
||||
sections: [
|
||||
{
|
||||
heading: 'Essentials',
|
||||
items: [
|
||||
{ name: 'Death registration certificate', price: 150 },
|
||||
{ name: 'Professional Mortuary Care', price: 800 },
|
||||
{ name: 'Professional Service Fee', price: 850 },
|
||||
],
|
||||
},
|
||||
],
|
||||
total: 1800,
|
||||
},
|
||||
];
|
||||
|
||||
const otherPackages: PackageData[] = [
|
||||
{
|
||||
id: 'deluxe',
|
||||
name: 'Deluxe Funeral Package',
|
||||
@@ -106,24 +126,6 @@ const mockPackages: PackageData[] = [
|
||||
],
|
||||
total: 4900,
|
||||
},
|
||||
{
|
||||
id: 'essential',
|
||||
name: 'Essential Funeral Package',
|
||||
price: 1800,
|
||||
description:
|
||||
'A simple, dignified option covering the essential requirements for a cremation service.',
|
||||
sections: [
|
||||
{
|
||||
heading: 'Essentials',
|
||||
items: [
|
||||
{ name: 'Death registration certificate', price: 150 },
|
||||
{ name: 'Professional Mortuary Care', price: 800 },
|
||||
{ name: 'Professional Service Fee', price: 850 },
|
||||
],
|
||||
},
|
||||
],
|
||||
total: 1800,
|
||||
},
|
||||
{
|
||||
id: 'catholic',
|
||||
name: 'Catholic Service',
|
||||
@@ -161,44 +163,21 @@ type Story = StoryObj<typeof PackagesStep>;
|
||||
|
||||
// ─── Interactive (default) ──────────────────────────────────────────────────
|
||||
|
||||
/** Fully interactive — browse, filter, select a package, see detail */
|
||||
/** Matched + other packages — select a package, see detail, click Make Arrangement */
|
||||
export const Default: Story = {
|
||||
render: () => {
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const [budget, setBudget] = useState('all');
|
||||
const [error, setError] = useState<string | undefined>();
|
||||
|
||||
const filtered =
|
||||
budget === 'all'
|
||||
? mockPackages
|
||||
: mockPackages.filter((p) => {
|
||||
const [min, max] = budget.split('-').map(Number);
|
||||
return p.price >= min && p.price <= (max || Infinity);
|
||||
});
|
||||
|
||||
const handleContinue = () => {
|
||||
if (!selectedId) {
|
||||
setError('Please choose a package to continue.');
|
||||
return;
|
||||
}
|
||||
setError(undefined);
|
||||
alert(`Continue with package: ${selectedId}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<PackagesStep
|
||||
provider={mockProvider}
|
||||
packages={filtered}
|
||||
packages={matchedPackages}
|
||||
otherPackages={otherPackages}
|
||||
selectedPackageId={selectedId}
|
||||
onSelectPackage={(id) => {
|
||||
setSelectedId(id);
|
||||
setError(undefined);
|
||||
}}
|
||||
budgetFilter={budget}
|
||||
onBudgetFilterChange={setBudget}
|
||||
onContinue={handleContinue}
|
||||
onSelectPackage={setSelectedId}
|
||||
onArrange={() => alert('Open ArrangementDialog')}
|
||||
onProviderClick={() => alert('Open provider profile')}
|
||||
onBack={() => alert('Back')}
|
||||
error={error}
|
||||
navigation={nav}
|
||||
/>
|
||||
);
|
||||
@@ -211,17 +190,38 @@ export const Default: Story = {
|
||||
export const WithSelection: Story = {
|
||||
render: () => {
|
||||
const [selectedId, setSelectedId] = useState<string | null>('everyday');
|
||||
const [budget, setBudget] = useState('all');
|
||||
|
||||
return (
|
||||
<PackagesStep
|
||||
provider={mockProvider}
|
||||
packages={mockPackages}
|
||||
packages={matchedPackages}
|
||||
otherPackages={otherPackages}
|
||||
selectedPackageId={selectedId}
|
||||
onSelectPackage={setSelectedId}
|
||||
budgetFilter={budget}
|
||||
onBudgetFilterChange={setBudget}
|
||||
onContinue={() => alert('Continue')}
|
||||
onArrange={() => alert('Open ArrangementDialog')}
|
||||
onProviderClick={() => alert('Open provider profile')}
|
||||
onBack={() => alert('Back')}
|
||||
navigation={nav}
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
// ─── No other packages (all match) ─────────────────────────────────────────
|
||||
|
||||
/** All packages match filters — no "Other packages" section */
|
||||
export const AllMatching: Story = {
|
||||
render: () => {
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<PackagesStep
|
||||
provider={mockProvider}
|
||||
packages={[...matchedPackages, ...otherPackages]}
|
||||
selectedPackageId={selectedId}
|
||||
onSelectPackage={setSelectedId}
|
||||
onArrange={() => alert('Open ArrangementDialog')}
|
||||
onProviderClick={() => alert('Open provider profile')}
|
||||
onBack={() => alert('Back')}
|
||||
navigation={nav}
|
||||
/>
|
||||
@@ -231,21 +231,20 @@ export const WithSelection: Story = {
|
||||
|
||||
// ─── Pre-planning ───────────────────────────────────────────────────────────
|
||||
|
||||
/** Pre-planning flow — softer helper text */
|
||||
/** Pre-planning flow — softer copy */
|
||||
export const PrePlanning: Story = {
|
||||
render: () => {
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const [budget, setBudget] = useState('all');
|
||||
|
||||
return (
|
||||
<PackagesStep
|
||||
provider={mockProvider}
|
||||
packages={mockPackages}
|
||||
packages={matchedPackages}
|
||||
otherPackages={otherPackages}
|
||||
selectedPackageId={selectedId}
|
||||
onSelectPackage={setSelectedId}
|
||||
budgetFilter={budget}
|
||||
onBudgetFilterChange={setBudget}
|
||||
onContinue={() => alert('Continue')}
|
||||
onArrange={() => alert('Open ArrangementDialog')}
|
||||
onProviderClick={() => alert('Open provider profile')}
|
||||
onBack={() => alert('Back')}
|
||||
navigation={nav}
|
||||
isPrePlanning
|
||||
@@ -254,46 +253,20 @@ export const PrePlanning: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
// ─── Filtered empty ─────────────────────────────────────────────────────────
|
||||
|
||||
/** Budget filter yielding no results */
|
||||
export const FilteredEmpty: Story = {
|
||||
render: () => {
|
||||
const [budget, setBudget] = useState('7000-10000');
|
||||
|
||||
return (
|
||||
<PackagesStep
|
||||
provider={mockProvider}
|
||||
packages={[]}
|
||||
selectedPackageId={null}
|
||||
onSelectPackage={() => {}}
|
||||
budgetFilter={budget}
|
||||
onBudgetFilterChange={setBudget}
|
||||
onContinue={() => {}}
|
||||
onBack={() => alert('Back')}
|
||||
navigation={nav}
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
// ─── Validation error ───────────────────────────────────────────────────────
|
||||
|
||||
/** Error shown when no package selected */
|
||||
export const WithError: Story = {
|
||||
render: () => {
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const [budget, setBudget] = useState('all');
|
||||
|
||||
return (
|
||||
<PackagesStep
|
||||
provider={mockProvider}
|
||||
packages={mockPackages}
|
||||
packages={matchedPackages}
|
||||
selectedPackageId={selectedId}
|
||||
onSelectPackage={setSelectedId}
|
||||
budgetFilter={budget}
|
||||
onBudgetFilterChange={setBudget}
|
||||
onContinue={() => {}}
|
||||
onArrange={() => {}}
|
||||
onBack={() => alert('Back')}
|
||||
error="Please choose a package to continue."
|
||||
navigation={nav}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import type { SxProps, Theme } from '@mui/material/styles';
|
||||
import { WizardLayout } from '../../templates/WizardLayout';
|
||||
import { ProviderCardCompact } from '../../molecules/ProviderCardCompact';
|
||||
@@ -9,8 +7,7 @@ import { ServiceOption } from '../../molecules/ServiceOption';
|
||||
import { PackageDetail } from '../../organisms/PackageDetail';
|
||||
import type { PackageSection } from '../../organisms/PackageDetail';
|
||||
import { Typography } from '../../atoms/Typography';
|
||||
import { Badge } from '../../atoms/Badge';
|
||||
import { Button } from '../../atoms/Button';
|
||||
import { Divider } from '../../atoms/Divider';
|
||||
|
||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -38,8 +35,6 @@ export interface PackageData {
|
||||
price: number;
|
||||
/** Short description */
|
||||
description?: string;
|
||||
/** Whether this is a "Most Popular" package */
|
||||
popular?: boolean;
|
||||
/** Line item sections for the detail panel */
|
||||
sections: PackageSection[];
|
||||
/** Total price (may differ from base price with extras) */
|
||||
@@ -50,37 +45,27 @@ export interface PackageData {
|
||||
terms?: string;
|
||||
}
|
||||
|
||||
/** Budget filter option */
|
||||
export interface BudgetOption {
|
||||
/** Option value */
|
||||
value: string;
|
||||
/** Display label */
|
||||
label: string;
|
||||
}
|
||||
|
||||
/** Props for the PackagesStep page component */
|
||||
export interface PackagesStepProps {
|
||||
/** Provider summary shown at top of the list panel */
|
||||
provider: PackagesStepProvider;
|
||||
/** Available packages */
|
||||
/** Packages matching the user's filters from the previous step */
|
||||
packages: PackageData[];
|
||||
/** Other packages from this provider that didn't match filters (shown in secondary group) */
|
||||
otherPackages?: PackageData[];
|
||||
/** Currently selected package ID */
|
||||
selectedPackageId: string | null;
|
||||
/** Callback when a package is selected */
|
||||
onSelectPackage: (id: string) => void;
|
||||
/** Current budget filter value */
|
||||
budgetFilter: string;
|
||||
/** Callback when budget filter changes */
|
||||
onBudgetFilterChange: (value: string) => void;
|
||||
/** Budget filter options */
|
||||
budgetOptions?: BudgetOption[];
|
||||
/** Callback for the Continue button */
|
||||
onContinue: () => void;
|
||||
/** Callback when "Make Arrangement" is clicked (opens ArrangementDialog) */
|
||||
onArrange: () => void;
|
||||
/** Callback when the provider card is clicked (opens provider profile popup) */
|
||||
onProviderClick?: () => void;
|
||||
/** Callback for the Back button */
|
||||
onBack: () => void;
|
||||
/** Validation error */
|
||||
error?: string;
|
||||
/** Whether Continue is loading */
|
||||
/** Whether the arrange action is loading */
|
||||
loading?: boolean;
|
||||
/** Navigation bar */
|
||||
navigation?: React.ReactNode;
|
||||
@@ -90,26 +75,23 @@ export interface PackagesStepProps {
|
||||
sx?: SxProps<Theme>;
|
||||
}
|
||||
|
||||
// ─── Constants ───────────────────────────────────────────────────────────────
|
||||
|
||||
const DEFAULT_BUDGET_OPTIONS: BudgetOption[] = [
|
||||
{ value: 'all', label: 'All packages' },
|
||||
{ value: '2000-4000', label: '$2,000 \u2013 $4,000' },
|
||||
{ value: '4000-7000', label: '$4,000 \u2013 $7,000' },
|
||||
{ value: '7000-10000', label: '$7,000 \u2013 $10,000+' },
|
||||
];
|
||||
|
||||
// ─── Component ───────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Step 3 — Package selection page for the FA arrangement wizard.
|
||||
*
|
||||
* List + Detail split layout. Left panel shows the selected provider
|
||||
* (compact), a budget filter, and selectable package cards. Right panel
|
||||
* shows the full detail breakdown of the selected package.
|
||||
* (compact) and selectable package cards. Right panel shows the full
|
||||
* detail breakdown of the selected package with "Make Arrangement" CTA.
|
||||
*
|
||||
* Packages are displayed as ServiceOption cards in a radiogroup pattern.
|
||||
* "Most Popular" badge on qualifying packages reduces decision paralysis.
|
||||
* Packages are split into two groups:
|
||||
* - **Matching your preferences**: packages that matched the user's filters
|
||||
* from the providers step
|
||||
* - **Other packages from [Provider]**: remaining packages outside those
|
||||
* filters, shown below a divider for passive discovery
|
||||
*
|
||||
* Selecting a package reveals its detail. Clicking "Make Arrangement"
|
||||
* on the detail panel triggers the ArrangementDialog (D-E).
|
||||
*
|
||||
* Pure presentation component — props in, callbacks out.
|
||||
*
|
||||
@@ -118,12 +100,11 @@ const DEFAULT_BUDGET_OPTIONS: BudgetOption[] = [
|
||||
export const PackagesStep: React.FC<PackagesStepProps> = ({
|
||||
provider,
|
||||
packages,
|
||||
otherPackages = [],
|
||||
selectedPackageId,
|
||||
onSelectPackage,
|
||||
budgetFilter,
|
||||
onBudgetFilterChange,
|
||||
budgetOptions = DEFAULT_BUDGET_OPTIONS,
|
||||
onContinue,
|
||||
onArrange,
|
||||
onProviderClick,
|
||||
onBack,
|
||||
error,
|
||||
loading = false,
|
||||
@@ -131,13 +112,13 @@ export const PackagesStep: React.FC<PackagesStepProps> = ({
|
||||
isPrePlanning = false,
|
||||
sx,
|
||||
}) => {
|
||||
const selectedPackage = packages.find((p) => p.id === selectedPackageId);
|
||||
const allPackages = [...packages, ...otherPackages];
|
||||
const selectedPackage = allPackages.find((p) => p.id === selectedPackageId);
|
||||
const hasOtherPackages = otherPackages.length > 0;
|
||||
|
||||
const subheading =
|
||||
'Each package includes a set of services. You can customise your selections in the next steps.';
|
||||
const helperText = isPrePlanning
|
||||
const subheading = isPrePlanning
|
||||
? 'Compare packages to find what suits your wishes. Nothing is committed until you confirm.'
|
||||
: 'Prices shown include the base services listed. Additional options may change the total.';
|
||||
: 'Each package includes a set of services. You can customise your selections in the next steps.';
|
||||
|
||||
return (
|
||||
<WizardLayout
|
||||
@@ -156,7 +137,7 @@ export const PackagesStep: React.FC<PackagesStepProps> = ({
|
||||
total={selectedPackage.total}
|
||||
extras={selectedPackage.extras}
|
||||
terms={selectedPackage.terms}
|
||||
onArrange={onContinue}
|
||||
onArrange={onArrange}
|
||||
arrangeDisabled={loading}
|
||||
/>
|
||||
) : (
|
||||
@@ -179,7 +160,7 @@ export const PackagesStep: React.FC<PackagesStepProps> = ({
|
||||
)
|
||||
}
|
||||
>
|
||||
{/* Provider compact card */}
|
||||
{/* Provider compact card — clickable to open provider profile */}
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<ProviderCardCompact
|
||||
name={provider.name}
|
||||
@@ -187,37 +168,17 @@ export const PackagesStep: React.FC<PackagesStepProps> = ({
|
||||
imageUrl={provider.imageUrl}
|
||||
rating={provider.rating}
|
||||
reviewCount={provider.reviewCount}
|
||||
onClick={onProviderClick}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Heading */}
|
||||
<Typography variant="display3" component="h1" sx={{ mb: 0.5 }} tabIndex={-1}>
|
||||
<Typography variant="h4" component="h1" sx={{ mb: 0.5 }} tabIndex={-1}>
|
||||
Choose a funeral package
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||
{subheading}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ mb: 3, display: 'block' }}>
|
||||
{helperText}
|
||||
</Typography>
|
||||
|
||||
{/* Budget filter */}
|
||||
<Box sx={{ mb: 3, pt: 0.5 }}>
|
||||
<TextField
|
||||
select
|
||||
size="small"
|
||||
value={budgetFilter}
|
||||
onChange={(e) => onBudgetFilterChange(e.target.value)}
|
||||
label="Budget range"
|
||||
sx={{ width: { xs: '100%', sm: 240 } }}
|
||||
>
|
||||
{budgetOptions.map((opt) => (
|
||||
<MenuItem key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Box>
|
||||
|
||||
{/* Error message */}
|
||||
{error && (
|
||||
@@ -230,64 +191,99 @@ export const PackagesStep: React.FC<PackagesStepProps> = ({
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{/* Package list — radiogroup pattern */}
|
||||
{/* ─── Matching packages ─── */}
|
||||
{hasOtherPackages && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1.5,
|
||||
mb: 2,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: 3,
|
||||
height: 20,
|
||||
borderRadius: 1,
|
||||
bgcolor: 'primary.main',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600, color: 'text.primary' }}>
|
||||
Matching your preferences
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
role="radiogroup"
|
||||
aria-label="Funeral packages"
|
||||
sx={{ display: 'flex', flexDirection: 'column', gap: 2, mb: 3 }}
|
||||
>
|
||||
{packages.map((pkg) => (
|
||||
<Box key={pkg.id} sx={{ position: 'relative' }}>
|
||||
{pkg.popular && (
|
||||
<Badge
|
||||
variant="filled"
|
||||
color="brand"
|
||||
size="small"
|
||||
aria-label="Most popular choice"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: -8,
|
||||
right: 12,
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
Most Popular
|
||||
</Badge>
|
||||
)}
|
||||
<ServiceOption
|
||||
name={pkg.name}
|
||||
description={pkg.description}
|
||||
price={pkg.price}
|
||||
selected={selectedPackageId === pkg.id}
|
||||
onClick={() => onSelectPackage(pkg.id)}
|
||||
/>
|
||||
</Box>
|
||||
<ServiceOption
|
||||
key={pkg.id}
|
||||
name={pkg.name}
|
||||
description={pkg.description}
|
||||
price={pkg.price}
|
||||
selected={selectedPackageId === pkg.id}
|
||||
onClick={() => onSelectPackage(pkg.id)}
|
||||
/>
|
||||
))}
|
||||
|
||||
{packages.length === 0 && (
|
||||
<Box sx={{ py: 6, textAlign: 'center' }}>
|
||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 1 }}>
|
||||
No packages match the selected budget range.
|
||||
</Typography>
|
||||
<Box sx={{ py: 4, textAlign: 'center' }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Try selecting "All packages" to see the full range.
|
||||
No packages match your current preferences.
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Mobile: Continue button (desktop uses PackageDetail's CTA) */}
|
||||
<Box sx={{ display: { xs: 'flex', md: 'none' }, justifyContent: 'flex-end', pb: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large"
|
||||
onClick={onContinue}
|
||||
disabled={!selectedPackageId}
|
||||
loading={loading}
|
||||
>
|
||||
Continue
|
||||
</Button>
|
||||
</Box>
|
||||
{/* ─── Other packages (passive discovery) ─── */}
|
||||
{hasOtherPackages && (
|
||||
<>
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1.5,
|
||||
mb: 2,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: 3,
|
||||
height: 20,
|
||||
borderRadius: 1,
|
||||
bgcolor: 'text.secondary',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600, color: 'text.secondary' }}>
|
||||
Other packages from {provider.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
role="radiogroup"
|
||||
aria-label={`Other packages from ${provider.name}`}
|
||||
sx={{ display: 'flex', flexDirection: 'column', gap: 2, mb: 3, opacity: 0.85 }}
|
||||
>
|
||||
{otherPackages.map((pkg) => (
|
||||
<ServiceOption
|
||||
key={pkg.id}
|
||||
name={pkg.name}
|
||||
description={pkg.description}
|
||||
price={pkg.price}
|
||||
selected={selectedPackageId === pkg.id}
|
||||
onClick={() => onSelectPackage(pkg.id)}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</WizardLayout>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user