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) <noreply@anthropic.com>
This commit is contained in:
424
src/components/templates/WizardLayout/WizardLayout.stories.tsx
Normal file
424
src/components/templates/WizardLayout/WizardLayout.stories.tsx
Normal file
@@ -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 = () => (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box
|
||||
component="img"
|
||||
src="/brandlogo/logo-full.svg"
|
||||
alt="Funeral Arranger"
|
||||
sx={{ height: 28, display: { xs: 'none', md: 'block' } }}
|
||||
/>
|
||||
<Box
|
||||
component="img"
|
||||
src="/brandlogo/logo-short.svg"
|
||||
alt="Funeral Arranger"
|
||||
sx={{ height: 28, display: { xs: 'block', md: 'none' } }}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const nav = (
|
||||
<Navigation
|
||||
logo={<FALogo />}
|
||||
items={[
|
||||
{ label: 'FAQ', href: '/faq' },
|
||||
{ label: 'Contact Us', href: '/contact' },
|
||||
{ label: 'Log in', href: '/login' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
const stepper = (
|
||||
<StepIndicator
|
||||
steps={[
|
||||
{ label: 'Details' },
|
||||
{ label: 'Service' },
|
||||
{ label: 'Venue' },
|
||||
{ label: 'Coffin' },
|
||||
{ label: 'Extras' },
|
||||
]}
|
||||
currentStep={2}
|
||||
/>
|
||||
);
|
||||
|
||||
const runningTotal = (
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: 'background.paper',
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
borderRadius: 1,
|
||||
px: 2,
|
||||
py: 0.75,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Your plan
|
||||
</Typography>
|
||||
<Typography variant="h6" color="primary.main">
|
||||
$3,600
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const PlaceholderCard: React.FC<{ title: string; height?: number }> = ({ title, height = 100 }) => (
|
||||
<Box
|
||||
sx={{
|
||||
p: 3,
|
||||
mb: 2,
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: 1,
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
minHeight: height,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6">{title}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Placeholder content for layout demonstration.
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
|
||||
// ─── Meta ────────────────────────────────────────────────────────────────────
|
||||
|
||||
const meta: Meta<typeof WizardLayout> = {
|
||||
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<typeof WizardLayout>;
|
||||
|
||||
// ─── 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) => (
|
||||
<WizardLayout {...args}>
|
||||
<Typography variant="h4" sx={{ mb: 1 }}>
|
||||
Let's get started
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 4 }}>
|
||||
We'll guide you through arranging a funeral, step by step.
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="label" sx={{ mb: 1, display: 'block' }}>
|
||||
Who is this funeral being arranged for?
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
<Button variant="soft" color="secondary" size="large" fullWidth>
|
||||
Myself
|
||||
</Button>
|
||||
<Button variant="soft" color="secondary" size="large" fullWidth>
|
||||
Someone else
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Divider sx={{ my: 4 }} />
|
||||
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Button variant="contained" size="large">
|
||||
Continue
|
||||
</Button>
|
||||
</Box>
|
||||
</WizardLayout>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── 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) => (
|
||||
<WizardLayout {...args}>
|
||||
<Typography variant="h4" sx={{ mb: 1 }}>
|
||||
Date and time
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 4 }}>
|
||||
When would you like the service to take place?
|
||||
</Typography>
|
||||
<PlaceholderCard title="Date picker" height={200} />
|
||||
<PlaceholderCard title="Time selector" />
|
||||
<Divider sx={{ my: 4 }} />
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Button variant="contained" size="large">
|
||||
Continue
|
||||
</Button>
|
||||
</Box>
|
||||
</WizardLayout>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── List + Map ──────────────────────────────────────────────────────────────
|
||||
|
||||
/** Provider search — scrollable card list with map panel */
|
||||
export const ListMap: Story = {
|
||||
args: {
|
||||
variant: 'list-map',
|
||||
navigation: nav,
|
||||
showBackLink: true,
|
||||
},
|
||||
render: (args) => (
|
||||
<WizardLayout
|
||||
{...args}
|
||||
secondaryPanel={
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-sage-200)',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
Map placeholder
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Typography variant="h5" sx={{ mb: 0.5 }}>
|
||||
Choose a funeral provider
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||
Explore providers near you
|
||||
</Typography>
|
||||
<PlaceholderCard title="Search bar + filters" height={48} />
|
||||
<Typography variant="caption" color="text.secondary" sx={{ mb: 2, display: 'block' }}>
|
||||
Showing results from 5 providers
|
||||
</Typography>
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<PlaceholderCard key={i} title={`Provider ${i + 1}`} />
|
||||
))}
|
||||
</WizardLayout>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── List + Detail ───────────────────────────────────────────────────────────
|
||||
|
||||
/** Package selection — list left, detail panel right */
|
||||
export const ListDetail: Story = {
|
||||
args: {
|
||||
variant: 'list-detail',
|
||||
navigation: nav,
|
||||
showBackLink: true,
|
||||
},
|
||||
render: (args) => (
|
||||
<WizardLayout
|
||||
{...args}
|
||||
secondaryPanel={
|
||||
<Box>
|
||||
<Typography variant="h5" sx={{ mb: 1 }}>
|
||||
Everyday Funeral Package
|
||||
</Typography>
|
||||
<Typography variant="h4" color="primary.main" sx={{ mb: 3 }}>
|
||||
$2,700
|
||||
</Typography>
|
||||
<Button variant="contained" size="large" fullWidth sx={{ mb: 3 }}>
|
||||
Select Package
|
||||
</Button>
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
{['Essentials', 'Complimentary Items', 'Extras'].map((section) => (
|
||||
<Box key={section} sx={{ mb: 3 }}>
|
||||
<Typography variant="label" sx={{ mb: 1, display: 'block' }}>
|
||||
{section}
|
||||
</Typography>
|
||||
{Array.from({ length: 3 }).map((_, i) => (
|
||||
<PlaceholderCard key={i} title={`Item ${i + 1}`} height={40} />
|
||||
))}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<PlaceholderCard title="Provider card (compact)" height={80} />
|
||||
<Typography variant="h6" sx={{ mb: 2 }}>
|
||||
Packages
|
||||
</Typography>
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<PlaceholderCard key={i} title={`Package ${i + 1} — $${900 * (i + 1)}`} />
|
||||
))}
|
||||
</WizardLayout>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── 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) => (
|
||||
<WizardLayout
|
||||
{...args}
|
||||
secondaryPanel={
|
||||
<Box>
|
||||
<Typography variant="h5" sx={{ mb: 0.5 }}>
|
||||
Coffins
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||
Browse our selection of bespoke designer coffins.
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr', md: '1fr 1fr 1fr' },
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<PlaceholderCard key={i} title={`Coffin ${i + 1}`} height={160} />
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Typography variant="h6" sx={{ mb: 2 }}>
|
||||
Categories
|
||||
</Typography>
|
||||
{['Materials', 'Colour', 'Environmental', 'Religious'].map((cat) => (
|
||||
<PlaceholderCard key={cat} title={cat} height={40} />
|
||||
))}
|
||||
<Typography variant="h6" sx={{ mt: 3, mb: 2 }}>
|
||||
Price
|
||||
</Typography>
|
||||
<PlaceholderCard title="Price range slider" height={48} />
|
||||
</WizardLayout>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── 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) => (
|
||||
<WizardLayout
|
||||
{...args}
|
||||
secondaryPanel={
|
||||
<Box>
|
||||
<Typography variant="h4" sx={{ mb: 0.5 }}>
|
||||
West Chapel
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
||||
Wentworth, NSW
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||
Capacity: 120 guests
|
||||
</Typography>
|
||||
<Typography variant="body1" sx={{ mb: 2 }}>
|
||||
A beautiful heritage chapel set in peaceful gardens, offering a serene setting for
|
||||
farewell services.
|
||||
</Typography>
|
||||
<Typography variant="h5" color="primary.main" sx={{ mb: 2 }}>
|
||||
$900
|
||||
</Typography>
|
||||
<Button variant="contained" size="large" fullWidth>
|
||||
Add to package
|
||||
</Button>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-sage-200)',
|
||||
borderRadius: 2,
|
||||
height: 300,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
mb: 2,
|
||||
}}
|
||||
>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
Venue image placeholder
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-sage-200)',
|
||||
borderRadius: 1,
|
||||
width: 60,
|
||||
height: 48,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</WizardLayout>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── No Navigation ──────────────────────────────────────────────────────────
|
||||
|
||||
/** Minimal — no nav, no help bar (for embedded use) */
|
||||
export const Minimal: Story = {
|
||||
args: {
|
||||
variant: 'centered-form',
|
||||
hideHelpBar: true,
|
||||
},
|
||||
render: (args) => (
|
||||
<WizardLayout {...args}>
|
||||
<Typography variant="h4" sx={{ mb: 1 }}>
|
||||
Embedded form
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
No navigation or help bar — for iframe or modal contexts.
|
||||
</Typography>
|
||||
</WizardLayout>
|
||||
),
|
||||
};
|
||||
Reference in New Issue
Block a user