Copy of the Funeral Arranger design system components, theme, tokens, and Storybook config from the original Parsons project. Pre-upgrade baseline with React 18, MUI v5, Storybook 8. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
180 lines
5.8 KiB
TypeScript
180 lines
5.8 KiB
TypeScript
import { useState } from 'react';
|
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
import { Switch } from './Switch';
|
|
import { Typography } from '../Typography';
|
|
import { Card } from '../Card';
|
|
import Box from '@mui/material/Box';
|
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
|
import FormGroup from '@mui/material/FormGroup';
|
|
|
|
const meta: Meta<typeof Switch> = {
|
|
title: 'Atoms/Switch',
|
|
component: Switch,
|
|
tags: ['autodocs'],
|
|
parameters: {
|
|
layout: 'centered',
|
|
design: {
|
|
type: 'figma',
|
|
url: 'https://www.figma.com/design/XUDUrw4yMkEexBCCYHXUvT/Parsons?node-id=2322-42538',
|
|
},
|
|
},
|
|
argTypes: {
|
|
checked: {
|
|
control: 'boolean',
|
|
description: 'Whether the switch is on',
|
|
},
|
|
disabled: {
|
|
control: 'boolean',
|
|
description: 'Disable the switch',
|
|
table: { defaultValue: { summary: 'false' } },
|
|
},
|
|
},
|
|
};
|
|
|
|
export default meta;
|
|
type Story = StoryObj<typeof Switch>;
|
|
|
|
// ─── Default ────────────────────────────────────────────────────────────────
|
|
|
|
/** Default switch — unchecked */
|
|
export const Default: Story = {
|
|
args: {},
|
|
};
|
|
|
|
// ─── States ─────────────────────────────────────────────────────────────────
|
|
|
|
/** All visual states */
|
|
export const States: Story = {
|
|
render: () => (
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
|
<FormControlLabel control={<Switch />} label="Unchecked" />
|
|
<FormControlLabel control={<Switch defaultChecked />} label="Checked" />
|
|
<FormControlLabel control={<Switch disabled />} label="Disabled unchecked" />
|
|
<FormControlLabel control={<Switch disabled defaultChecked />} label="Disabled checked" />
|
|
</Box>
|
|
),
|
|
};
|
|
|
|
// ─── Interactive: Service Add-ons ───────────────────────────────────────────
|
|
|
|
/**
|
|
* Realistic arrangement form pattern — toggle add-on services.
|
|
*/
|
|
export const ServiceAddOns: Story = {
|
|
name: 'Interactive — Service Add-ons',
|
|
render: () => {
|
|
const AddOnDemo = () => {
|
|
const [addOns, setAddOns] = useState({
|
|
catering: true,
|
|
flowers: true,
|
|
music: false,
|
|
memorial: false,
|
|
guestBook: false,
|
|
});
|
|
|
|
const toggle = (key: keyof typeof addOns) => {
|
|
setAddOns((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
};
|
|
|
|
const items = [
|
|
{
|
|
key: 'catering' as const,
|
|
label: 'Catering',
|
|
desc: 'Light refreshments after the service',
|
|
price: '$450',
|
|
},
|
|
{
|
|
key: 'flowers' as const,
|
|
label: 'Floral arrangements',
|
|
desc: 'Seasonal flowers for the chapel',
|
|
price: '$280',
|
|
},
|
|
{
|
|
key: 'music' as const,
|
|
label: 'Live music',
|
|
desc: 'Organist or solo musician',
|
|
price: '$350',
|
|
},
|
|
{
|
|
key: 'memorial' as const,
|
|
label: 'Memorial video',
|
|
desc: 'Photo slideshow with music',
|
|
price: '$200',
|
|
},
|
|
{
|
|
key: 'guestBook' as const,
|
|
label: 'Guest book',
|
|
desc: 'Leather-bound memorial guest book',
|
|
price: '$85',
|
|
},
|
|
];
|
|
|
|
const total = items.reduce(
|
|
(sum, item) => (addOns[item.key] ? sum + parseInt(item.price.replace('$', ''), 10) : sum),
|
|
0,
|
|
);
|
|
|
|
return (
|
|
<Box sx={{ maxWidth: 420 }}>
|
|
<Typography variant="h4" sx={{ mb: 2 }}>
|
|
Service add-ons
|
|
</Typography>
|
|
<FormGroup>
|
|
{items.map((item) => (
|
|
<Card key={item.key} variant="outlined" padding="compact" sx={{ mb: 1 }}>
|
|
<Box
|
|
sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}
|
|
>
|
|
<Box sx={{ flex: 1 }}>
|
|
<Typography variant="label">{item.label}</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
{item.desc}
|
|
</Typography>
|
|
</Box>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
<Typography variant="label" color="text.secondary">
|
|
{item.price}
|
|
</Typography>
|
|
<Switch checked={addOns[item.key]} onChange={() => toggle(item.key)} />
|
|
</Box>
|
|
</Box>
|
|
</Card>
|
|
))}
|
|
</FormGroup>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
mt: 2,
|
|
pt: 2,
|
|
borderTop: 1,
|
|
borderColor: 'divider',
|
|
}}
|
|
>
|
|
<Typography variant="labelLg">Total add-ons</Typography>
|
|
<Typography variant="labelLg" color="primary">
|
|
${total}
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
return <AddOnDemo />;
|
|
},
|
|
};
|
|
|
|
// ─── With Labels ────────────────────────────────────────────────────────────
|
|
|
|
/** FormControlLabel pairing — the recommended usage pattern */
|
|
export const WithLabels: Story = {
|
|
name: 'With Labels',
|
|
render: () => (
|
|
<FormGroup>
|
|
<FormControlLabel control={<Switch defaultChecked />} label="Email notifications" />
|
|
<FormControlLabel control={<Switch />} label="SMS notifications" />
|
|
<FormControlLabel control={<Switch defaultChecked />} label="Save arrangement progress" />
|
|
</FormGroup>
|
|
),
|
|
};
|