Refine LineItem + PackageDetail from review feedback
LineItem: - Bump font weight 400 → 500 (D019: Montserrat Regular too light) - Price text now text.secondary for readability hierarchy (name primary, price quieter) - Total row stays fully prominent (primary colour) - Item gap increased 12px → 16px in stories PackageDetail: - Restructure: sections (before total) + extras (after total) Essentials + Complimentary → Total → Extras (additional cost) - Add compareLoading prop — loading spinner on Compare button - Add CompareLoading story with simulated 1.5s load - T&C line height reduced 1.5 → 1.3 - Section gap increased for breathing room - Complimentary items now correctly shown before total - Default story includes onArrange + onCompare handlers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,7 @@ export interface PackageLineItem {
|
||||
isAllowance?: boolean;
|
||||
}
|
||||
|
||||
/** A section of items within a package (e.g. "Essentials", "Extras") */
|
||||
/** A section of items within a package (e.g. "Essentials", "Complimentary Items") */
|
||||
export interface PackageSection {
|
||||
/** Section heading */
|
||||
heading: string;
|
||||
@@ -34,10 +34,12 @@ export interface PackageDetailProps {
|
||||
name: string;
|
||||
/** Package price in dollars */
|
||||
price: number;
|
||||
/** Grouped sections of package contents */
|
||||
/** Main package sections shown BEFORE the total (Essentials, Complimentary Items) */
|
||||
sections: PackageSection[];
|
||||
/** Package total — usually the sum of priced essentials */
|
||||
/** Package total — shown between main sections and extras */
|
||||
total?: number;
|
||||
/** Additional-cost extras shown AFTER the total — these can be added at extra cost */
|
||||
extras?: PackageSection;
|
||||
/** Terms and conditions text — required by providers */
|
||||
terms?: string;
|
||||
/** Called when user clicks "Make Arrangement" */
|
||||
@@ -46,17 +48,49 @@ export interface PackageDetailProps {
|
||||
onCompare?: () => void;
|
||||
/** Whether the arrange button is disabled */
|
||||
arrangeDisabled?: boolean;
|
||||
/** Whether the compare button is in loading state */
|
||||
compareLoading?: boolean;
|
||||
/** MUI sx prop for the root element */
|
||||
sx?: SxProps<Theme>;
|
||||
}
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Renders a section heading + list of LineItems */
|
||||
function SectionBlock({ section }: { section: PackageSection }) {
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="label" component="h3" sx={{ display: 'block', mb: 2 }}>
|
||||
{section.heading}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
{section.items.map((item) => (
|
||||
<LineItem
|
||||
key={item.name}
|
||||
name={item.name}
|
||||
info={item.info}
|
||||
price={item.price}
|
||||
isAllowance={item.isAllowance}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Component ───────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Package detail panel for the FA design system.
|
||||
*
|
||||
* Displays the full contents of a funeral package — name, price, CTA buttons,
|
||||
* grouped line items (Essentials, Complimentary, Extras), total, and T&Cs.
|
||||
* grouped line items, total, optional extras, and T&Cs.
|
||||
*
|
||||
* Structure:
|
||||
* - **sections** (before total): What's included in the package price
|
||||
* (Essentials, Complimentary Items)
|
||||
* - **total**: The package price
|
||||
* - **extras** (after total): Additional items that can be added at extra cost
|
||||
*
|
||||
* Used as the right-side panel on the Package Select page. The contents and
|
||||
* T&Cs are provider-authored and must be displayed in full.
|
||||
@@ -64,22 +98,6 @@ export interface PackageDetailProps {
|
||||
* "Make Arrangement" is the FA term for selecting/committing to a package.
|
||||
*
|
||||
* Composes Typography + Button + Divider + LineItem.
|
||||
*
|
||||
* Usage:
|
||||
* ```tsx
|
||||
* <PackageDetail
|
||||
* name="Everyday Funeral Package"
|
||||
* price={2700}
|
||||
* sections={[
|
||||
* { heading: 'Essentials', items: [...] },
|
||||
* { heading: 'Complimentary Items', items: [...] },
|
||||
* ]}
|
||||
* total={2700}
|
||||
* terms="* This package includes a funeral service at a chapel..."
|
||||
* onArrange={() => startArrangement(pkg.id)}
|
||||
* onCompare={() => addToCompare(pkg.id)}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
export const PackageDetail = React.forwardRef<HTMLDivElement, PackageDetailProps>(
|
||||
(
|
||||
@@ -88,10 +106,12 @@ export const PackageDetail = React.forwardRef<HTMLDivElement, PackageDetailProps
|
||||
price,
|
||||
sections,
|
||||
total,
|
||||
extras,
|
||||
terms,
|
||||
onArrange,
|
||||
onCompare,
|
||||
arrangeDisabled = false,
|
||||
compareLoading = false,
|
||||
sx,
|
||||
},
|
||||
ref,
|
||||
@@ -137,6 +157,7 @@ export const PackageDetail = React.forwardRef<HTMLDivElement, PackageDetailProps
|
||||
variant="soft"
|
||||
color="secondary"
|
||||
size="large"
|
||||
loading={compareLoading}
|
||||
onClick={onCompare}
|
||||
sx={{ flexShrink: 0 }}
|
||||
>
|
||||
@@ -145,32 +166,26 @@ export const PackageDetail = React.forwardRef<HTMLDivElement, PackageDetailProps
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
<Divider sx={{ mb: 3 }} />
|
||||
|
||||
{/* Sections */}
|
||||
{sections.map((section, sectionIdx) => (
|
||||
<Box key={section.heading} sx={{ mb: sectionIdx < sections.length - 1 ? 2 : 0 }}>
|
||||
<Typography variant="label" component="h3" sx={{ display: 'block', mb: 1.5 }}>
|
||||
{section.heading}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
||||
{section.items.map((item) => (
|
||||
<LineItem
|
||||
key={item.name}
|
||||
name={item.name}
|
||||
info={item.info}
|
||||
price={item.price}
|
||||
isAllowance={item.isAllowance}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
{/* Main sections — included in the package price */}
|
||||
{sections.map((section, idx) => (
|
||||
<Box key={section.heading} sx={{ mb: idx < sections.length - 1 ? 3 : 0 }}>
|
||||
<SectionBlock section={section} />
|
||||
</Box>
|
||||
))}
|
||||
|
||||
{/* Total */}
|
||||
{/* Total — separates included content from extras */}
|
||||
{total != null && (
|
||||
<LineItem name="Total" price={total} variant="total" />
|
||||
)}
|
||||
|
||||
{/* Extras — additional cost items after the total */}
|
||||
{extras && extras.items.length > 0 && (
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<SectionBlock section={extras} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Terms & Conditions footer */}
|
||||
@@ -182,7 +197,7 @@ export const PackageDetail = React.forwardRef<HTMLDivElement, PackageDetailProps
|
||||
py: 2,
|
||||
}}
|
||||
>
|
||||
<Typography variant="captionSm" color="text.secondary" sx={{ lineHeight: 1.5 }}>
|
||||
<Typography variant="captionSm" color="text.secondary" sx={{ lineHeight: 1.3 }}>
|
||||
{terms}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user