From 43f03602528ce387e73e6621e9563dc9e17aa02a Mon Sep 17 00:00:00 2001 From: Richie Date: Sun, 29 Mar 2026 14:15:41 +1100 Subject: [PATCH] Add WizardLayout template with 5 layout variants - centered-form: single column ~600px for form steps (intro, auth, etc.) - list-map: 40/60 split for provider search (card list + map) - list-detail: 40/60 master-detail for package selection - grid-sidebar: 25/75 filter sidebar + card grid (coffins) - detail-toggles: 50/50 hero image + product info (venue/coffin details) Common elements: nav slot, sticky help bar, optional back link, optional progress stepper + running total (grid-sidebar, detail-toggles). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../WizardLayout/WizardLayout.stories.tsx | 424 ++++++++++++++++++ .../templates/WizardLayout/WizardLayout.tsx | 337 ++++++++++++++ .../templates/WizardLayout/index.ts | 2 + 3 files changed, 763 insertions(+) create mode 100644 src/components/templates/WizardLayout/WizardLayout.stories.tsx create mode 100644 src/components/templates/WizardLayout/WizardLayout.tsx create mode 100644 src/components/templates/WizardLayout/index.ts diff --git a/src/components/templates/WizardLayout/WizardLayout.stories.tsx b/src/components/templates/WizardLayout/WizardLayout.stories.tsx new file mode 100644 index 0000000..32a3e9b --- /dev/null +++ b/src/components/templates/WizardLayout/WizardLayout.stories.tsx @@ -0,0 +1,424 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { WizardLayout } from './WizardLayout'; +import Box from '@mui/material/Box'; +import { Typography } from '../../atoms/Typography'; +import { Button } from '../../atoms/Button'; +import { Divider } from '../../atoms/Divider'; +import { Navigation } from '../../organisms/Navigation'; +import { StepIndicator } from '../../molecules/StepIndicator'; + +// ─── Helpers ───────────────────────────────────────────────────────────────── + +const FALogo = () => ( + + + + +); + +const nav = ( + } + items={[ + { label: 'FAQ', href: '/faq' }, + { label: 'Contact Us', href: '/contact' }, + { label: 'Log in', href: '/login' }, + ]} + /> +); + +const stepper = ( + +); + +const runningTotal = ( + + + Your plan + + + $3,600 + + +); + +const PlaceholderCard: React.FC<{ title: string; height?: number }> = ({ title, height = 100 }) => ( + + {title} + + Placeholder content for layout demonstration. + + +); + +// ─── Meta ──────────────────────────────────────────────────────────────────── + +const meta: Meta = { + title: 'Templates/WizardLayout', + component: WizardLayout, + tags: ['autodocs'], + parameters: { + layout: 'fullscreen', + }, + argTypes: { + variant: { + control: 'select', + options: ['centered-form', 'list-map', 'list-detail', 'grid-sidebar', 'detail-toggles'], + description: 'Layout variant', + table: { defaultValue: { summary: 'centered-form' } }, + }, + showBackLink: { control: 'boolean' }, + hideHelpBar: { control: 'boolean' }, + backLabel: { control: 'text' }, + helpPhone: { control: 'text' }, + }, +}; + +export default meta; +type Story = StoryObj; + +// ─── Centered Form ────────────────────────────────────────────────────────── + +/** Step 1 (Intro) style — single centered column, no back link, no progress stepper */ +export const CenteredForm: Story = { + args: { + variant: 'centered-form', + navigation: nav, + }, + render: (args) => ( + + + Let's get started + + + We'll guide you through arranging a funeral, step by step. + + + + + Who is this funeral being arranged for? + + + + + + + + + + + + + + ), +}; + +// ─── Centered Form with Back ──────────────────────────────────────────────── + +/** Form step with back link (e.g. date/time, payment) */ +export const CenteredFormWithBack: Story = { + args: { + variant: 'centered-form', + navigation: nav, + showBackLink: true, + backLabel: 'Back', + }, + render: (args) => ( + + + Date and time + + + When would you like the service to take place? + + + + + + + + + ), +}; + +// ─── List + Map ────────────────────────────────────────────────────────────── + +/** Provider search — scrollable card list with map panel */ +export const ListMap: Story = { + args: { + variant: 'list-map', + navigation: nav, + showBackLink: true, + }, + render: (args) => ( + + + Map placeholder + + + } + > + + Choose a funeral provider + + + Explore providers near you + + + + Showing results from 5 providers + + {Array.from({ length: 4 }).map((_, i) => ( + + ))} + + ), +}; + +// ─── List + Detail ─────────────────────────────────────────────────────────── + +/** Package selection — list left, detail panel right */ +export const ListDetail: Story = { + args: { + variant: 'list-detail', + navigation: nav, + showBackLink: true, + }, + render: (args) => ( + + + Everyday Funeral Package + + + $2,700 + + + + {['Essentials', 'Complimentary Items', 'Extras'].map((section) => ( + + + {section} + + {Array.from({ length: 3 }).map((_, i) => ( + + ))} + + ))} + + } + > + + + Packages + + {Array.from({ length: 4 }).map((_, i) => ( + + ))} + + ), +}; + +// ─── Grid + Sidebar ────────────────────────────────────────────────────────── + +/** Coffin selection — filter sidebar + responsive card grid */ +export const GridSidebar: Story = { + args: { + variant: 'grid-sidebar', + navigation: nav, + showBackLink: true, + progressStepper: stepper, + runningTotal: runningTotal, + }, + render: (args) => ( + + + Coffins + + + Browse our selection of bespoke designer coffins. + + + {Array.from({ length: 6 }).map((_, i) => ( + + ))} + + + } + > + + Categories + + {['Materials', 'Colour', 'Environmental', 'Religious'].map((cat) => ( + + ))} + + Price + + + + ), +}; + +// ─── Detail + Toggles ─────────────────────────────────────────────────────── + +/** Venue/coffin detail — hero image + product info */ +export const DetailToggles: Story = { + args: { + variant: 'detail-toggles', + navigation: nav, + showBackLink: true, + progressStepper: stepper, + runningTotal: runningTotal, + }, + render: (args) => ( + + + West Chapel + + + Wentworth, NSW + + + Capacity: 120 guests + + + A beautiful heritage chapel set in peaceful gardens, offering a serene setting for + farewell services. + + + $900 + + + + } + > + + + Venue image placeholder + + + + {Array.from({ length: 5 }).map((_, i) => ( + + ))} + + + ), +}; + +// ─── No Navigation ────────────────────────────────────────────────────────── + +/** Minimal — no nav, no help bar (for embedded use) */ +export const Minimal: Story = { + args: { + variant: 'centered-form', + hideHelpBar: true, + }, + render: (args) => ( + + + Embedded form + + + No navigation or help bar — for iframe or modal contexts. + + + ), +}; diff --git a/src/components/templates/WizardLayout/WizardLayout.tsx b/src/components/templates/WizardLayout/WizardLayout.tsx new file mode 100644 index 0000000..9cf485f --- /dev/null +++ b/src/components/templates/WizardLayout/WizardLayout.tsx @@ -0,0 +1,337 @@ +import React from 'react'; +import Box from '@mui/material/Box'; +import Container from '@mui/material/Container'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import PhoneIcon from '@mui/icons-material/Phone'; +import type { SxProps, Theme } from '@mui/material/styles'; +import { Link } from '../../atoms/Link'; +import { Typography } from '../../atoms/Typography'; + +// ─── Types ─────────────────────────────────────────────────────────────────── + +/** Layout variant matching the 5 wizard page templates */ +export type WizardLayoutVariant = + | 'centered-form' + | 'list-map' + | 'list-detail' + | 'grid-sidebar' + | 'detail-toggles'; + +/** Props for the WizardLayout template */ +export interface WizardLayoutProps { + /** Which layout variant to render */ + variant: WizardLayoutVariant; + /** Navigation bar — rendered at the top of the page */ + navigation?: React.ReactNode; + /** Optional progress stepper — shown below nav on grid-sidebar and detail-toggles variants */ + progressStepper?: React.ReactNode; + /** Optional running total widget — shown in a bar below nav (grid-sidebar, detail-toggles) */ + runningTotal?: React.ReactNode; + /** Show a back link above the content area */ + showBackLink?: boolean; + /** Label for the back link */ + backLabel?: string; + /** Click handler for the back link */ + onBack?: () => void; + /** Help bar phone number */ + helpPhone?: string; + /** Hide the sticky help bar */ + hideHelpBar?: boolean; + + // ─── Slot content ─── + + /** Main content — for centered-form this is the form; for split layouts this is the primary (left) panel */ + children: React.ReactNode; + /** Secondary panel content — right panel for split layouts (map, detail, grid) */ + secondaryPanel?: React.ReactNode; + + /** MUI sx prop for the root container */ + sx?: SxProps; +} + +// ─── Help bar ──────────────────────────────────────────────────────────────── + +const HelpBar: React.FC<{ phone: string }> = ({ phone }) => ( + + + + Need help? Call us on{' '} + + {phone} + + + +); + +// ─── Back link ─────────────────────────────────────────────────────────────── + +const BackLink: React.FC<{ label: string; onClick?: () => void }> = ({ label, onClick }) => ( + + + {label} + +); + +// ─── Stepper + total bar ───────────────────────────────────────────────────── + +const StepperBar: React.FC<{ + stepper?: React.ReactNode; + total?: React.ReactNode; +}> = ({ stepper, total }) => { + if (!stepper && !total) return null; + return ( + + {stepper} + {total && {total}} + + ); +}; + +// ─── Layout variants ───────────────────────────────────────────────────────── + +/** Centered Form: single column ~600px, heading + fields + CTA */ +const CenteredFormLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => ( + + {children} + +); + +/** List + Map: ~40% scrollable list (left) / ~60% map (right) */ +const ListMapLayout: React.FC<{ + children: React.ReactNode; + secondaryPanel?: React.ReactNode; +}> = ({ children, secondaryPanel }) => ( + + + {children} + + + {secondaryPanel} + + +); + +/** List + Detail: ~40% selection list (left) / ~60% detail panel (right) */ +const ListDetailLayout: React.FC<{ + children: React.ReactNode; + secondaryPanel?: React.ReactNode; +}> = ({ children, secondaryPanel }) => ( + + + {children} + {secondaryPanel} + + +); + +/** Grid + Sidebar: ~25% filter sidebar (left) / ~75% card grid (right) */ +const GridSidebarLayout: React.FC<{ + children: React.ReactNode; + secondaryPanel?: React.ReactNode; +}> = ({ children, secondaryPanel }) => ( + + + + {children} + + {secondaryPanel} + + +); + +/** Detail + Toggles: two-column hero (image left / info right), full-width section below */ +const DetailTogglesLayout: React.FC<{ + children: React.ReactNode; + secondaryPanel?: React.ReactNode; +}> = ({ children, secondaryPanel }) => ( + + + {children} + {secondaryPanel} + + +); + +// ─── Variant map ───────────────────────────────────────────────────────────── + +const LAYOUT_MAP: Record< + WizardLayoutVariant, + React.FC<{ children: React.ReactNode; secondaryPanel?: React.ReactNode }> +> = { + 'centered-form': CenteredFormLayout, + 'list-map': ListMapLayout, + 'list-detail': ListDetailLayout, + 'grid-sidebar': GridSidebarLayout, + 'detail-toggles': DetailTogglesLayout, +}; + +/** Variants that show the stepper/total bar */ +const STEPPER_VARIANTS: WizardLayoutVariant[] = ['grid-sidebar', 'detail-toggles']; + +// ─── Component ─────────────────────────────────────────────────────────────── + +/** + * Page-level layout template for the FA arrangement wizard. + * + * Provides 5 layout variants matching the wizard page templates: + * - **centered-form**: Single centered column for form steps (intro, auth, date/time, etc.) + * - **list-map**: Split view with scrollable card list and map panel (providers) + * - **list-detail**: Master-detail split for selection + detail (packages, preview) + * - **grid-sidebar**: Filter sidebar + card grid (coffins) + * - **detail-toggles**: Hero image + info column (venue, coffin details) + * + * All variants share: navigation slot, optional back link, sticky help bar. + * Grid-sidebar and detail-toggles add: progress stepper, running total widget. + */ +export const WizardLayout = React.forwardRef( + ( + { + variant, + navigation, + progressStepper, + runningTotal, + showBackLink = false, + backLabel = 'Back', + onBack, + helpPhone = '1800 987 888', + hideHelpBar = false, + children, + secondaryPanel, + sx, + }, + ref, + ) => { + const LayoutComponent = LAYOUT_MAP[variant]; + const showStepper = STEPPER_VARIANTS.includes(variant); + + return ( + + {/* Navigation */} + {navigation} + + {/* Stepper + running total bar (grid-sidebar, detail-toggles only) */} + {showStepper && } + + {/* Back link — inside a container for consistent alignment */} + {showBackLink && ( + + + + )} + + {/* Main content area */} + {children} + + {/* Sticky help bar */} + {!hideHelpBar && } + + ); + }, +); + +WizardLayout.displayName = 'WizardLayout'; +export default WizardLayout; diff --git a/src/components/templates/WizardLayout/index.ts b/src/components/templates/WizardLayout/index.ts new file mode 100644 index 0000000..21509bc --- /dev/null +++ b/src/components/templates/WizardLayout/index.ts @@ -0,0 +1,2 @@ +export { default } from './WizardLayout'; +export * from './WizardLayout';