Files
Parsons/src/components/atoms/Chip/Chip.stories.tsx
Richie 047d913960 format: Apply Prettier to existing codebase
Formatting-only changes across all component and story files.
No logic or behaviour changes — only whitespace, line breaks, and trailing commas.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:42:16 +11:00

384 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Chip } from './Chip';
import { Card } from '../Card';
import { Typography } from '../Typography';
import Box from '@mui/material/Box';
import LocalOfferIcon from '@mui/icons-material/LocalOffer';
import FaceIcon from '@mui/icons-material/Face';
import CheckIcon from '@mui/icons-material/Check';
import FilterListIcon from '@mui/icons-material/FilterList';
import ChurchIcon from '@mui/icons-material/Church';
import LocalFloristIcon from '@mui/icons-material/LocalFlorist';
import DirectionsCarIcon from '@mui/icons-material/DirectionsCar';
import RestaurantIcon from '@mui/icons-material/Restaurant';
import MusicNoteIcon from '@mui/icons-material/MusicNote';
import PhotoCameraIcon from '@mui/icons-material/PhotoCamera';
const meta: Meta<typeof Chip> = {
title: 'Atoms/Chip',
component: Chip,
tags: ['autodocs'],
parameters: {
layout: 'centered',
},
argTypes: {
variant: {
control: 'select',
options: ['filled', 'outlined'],
description: 'Visual style variant',
table: { defaultValue: { summary: 'filled' } },
},
color: {
control: 'select',
options: ['default', 'primary'],
description: 'Colour intent',
table: { defaultValue: { summary: 'default' } },
},
size: {
control: 'select',
options: ['small', 'medium'],
description: 'Size preset',
table: { defaultValue: { summary: 'medium' } },
},
selected: {
control: 'boolean',
description: 'Selected/active state',
table: { defaultValue: { summary: 'false' } },
},
clickable: {
control: 'boolean',
description: 'Whether the chip is clickable',
},
},
};
export default meta;
type Story = StoryObj<typeof Chip>;
// ─── Default ────────────────────────────────────────────────────────────────
/** Default chip — filled variant, neutral colour */
export const Default: Story = {
args: {
label: 'Chip label',
},
};
// ─── Variants ───────────────────────────────────────────────────────────────
/** Both visual variants with default and primary colour */
export const Variants: Story = {
render: () => (
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap', alignItems: 'center' }}>
<Chip label="Filled default" />
<Chip label="Filled primary" color="primary" />
<Chip variant="outlined" label="Outlined default" />
<Chip variant="outlined" label="Outlined primary" color="primary" />
</Box>
),
};
// ─── Sizes ──────────────────────────────────────────────────────────────────
/** Small and medium sizes side by side */
export const Sizes: Story = {
render: () => (
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
<Chip size="small" label="Small" icon={<LocalOfferIcon />} />
<Chip size="medium" label="Medium" icon={<LocalOfferIcon />} />
<Chip size="small" variant="outlined" label="Small outlined" icon={<LocalOfferIcon />} />
<Chip size="medium" variant="outlined" label="Medium outlined" icon={<LocalOfferIcon />} />
</Box>
),
};
// ─── With Icons ─────────────────────────────────────────────────────────────
/** Chips with leading icons */
export const WithIcons: Story = {
name: 'With Icons',
render: () => (
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap', alignItems: 'center' }}>
<Chip icon={<ChurchIcon />} label="Chapel" />
<Chip icon={<LocalFloristIcon />} label="Flowers" color="primary" />
<Chip icon={<DirectionsCarIcon />} label="Transport" variant="outlined" />
<Chip icon={<FaceIcon />} label="Family" variant="outlined" color="primary" />
</Box>
),
};
// ─── Clickable ──────────────────────────────────────────────────────────────
/** Clickable chips respond to click events (for filtering/toggling) */
export const Clickable: Story = {
render: () => (
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap', alignItems: 'center' }}>
<Chip label="Clickable default" onClick={() => {}} />
<Chip label="Clickable primary" color="primary" onClick={() => {}} />
<Chip label="Clickable outlined" variant="outlined" onClick={() => {}} />
<Chip
label="Clickable outlined primary"
variant="outlined"
color="primary"
onClick={() => {}}
/>
</Box>
),
};
// ─── Deletable ──────────────────────────────────────────────────────────────
/** Deletable chips show a close icon */
export const Deletable: Story = {
render: () => (
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap', alignItems: 'center' }}>
<Chip label="Remove me" onDelete={() => {}} />
<Chip label="Brand deletable" color="primary" onDelete={() => {}} />
<Chip label="Outlined deletable" variant="outlined" onDelete={() => {}} />
<Chip label="With icon" icon={<LocalOfferIcon />} onDelete={() => {}} />
</Box>
),
};
// ─── Selected State ─────────────────────────────────────────────────────────
/** Selected state promotes chip to brand colour */
export const Selected: Story = {
render: () => (
<Box sx={{ display: 'flex', gap: 2, flexDirection: 'column' }}>
<Box>
<Typography variant="label" sx={{ mb: 1 }}>
Filled
</Typography>
<Box sx={{ display: 'flex', gap: 1.5 }}>
<Chip label="Not selected" onClick={() => {}} />
<Chip label="Selected" selected onClick={() => {}} />
</Box>
</Box>
<Box>
<Typography variant="label" sx={{ mb: 1 }}>
Outlined
</Typography>
<Box sx={{ display: 'flex', gap: 1.5 }}>
<Chip variant="outlined" label="Not selected" onClick={() => {}} />
<Chip variant="outlined" label="Selected" selected onClick={() => {}} />
</Box>
</Box>
</Box>
),
};
// ─── Interactive: Filter Chips ──────────────────────────────────────────────
/**
* Toggle filter pattern — commonly used for service category filtering.
* Click chips to toggle selection.
*/
export const FilterChips: Story = {
name: 'Interactive — Filter Chips',
render: () => {
const categories = [
{ label: 'Chapel', icon: <ChurchIcon /> },
{ label: 'Flowers', icon: <LocalFloristIcon /> },
{ label: 'Transport', icon: <DirectionsCarIcon /> },
{ label: 'Catering', icon: <RestaurantIcon /> },
{ label: 'Music', icon: <MusicNoteIcon /> },
{ label: 'Photography', icon: <PhotoCameraIcon /> },
];
const FilterDemo = () => {
const [selected, setSelected] = useState<Set<string>>(new Set(['Chapel', 'Flowers']));
const toggle = (label: string) => {
setSelected((prev) => {
const next = new Set(prev);
if (next.has(label)) next.delete(label);
else next.add(label);
return next;
});
};
return (
<Box sx={{ maxWidth: 450 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<FilterListIcon sx={{ color: 'text.secondary', fontSize: 20 }} />
<Typography variant="label">Filter services</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
{categories.map(({ label, icon }) => (
<Chip
key={label}
label={label}
icon={selected.has(label) ? <CheckIcon /> : icon}
selected={selected.has(label)}
onClick={() => toggle(label)}
variant="outlined"
/>
))}
</Box>
<Typography variant="body2" color="text.secondary" sx={{ mt: 2 }}>
Selected: {selected.size === 0 ? 'None' : Array.from(selected).join(', ')}
</Typography>
</Box>
);
};
return <FilterDemo />;
},
};
// ─── Interactive: Removable Tags ────────────────────────────────────────────
/**
* Removable tag pattern — for selected items that can be dismissed.
* Click the × icon to remove a tag.
*/
export const RemovableTags: Story = {
name: 'Interactive — Removable Tags',
render: () => {
const TagDemo = () => {
const [tags, setTags] = useState([
'White roses',
'Organ music',
'Prayer cards',
'Memorial video',
'Guest book',
]);
const remove = (tag: string) => {
setTags((prev) => prev.filter((t) => t !== tag));
};
return (
<Box sx={{ maxWidth: 450 }}>
<Typography variant="label" sx={{ mb: 1 }}>
Selected additions ({tags.length})
</Typography>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', minHeight: 32 }}>
{tags.length === 0 ? (
<Typography variant="body2" color="text.secondary">
No items selected
</Typography>
) : (
tags.map((tag) => (
<Chip key={tag} label={tag} color="primary" onDelete={() => remove(tag)} />
))
)}
</Box>
</Box>
);
};
return <TagDemo />;
},
};
// ─── In Context: Service Option ─────────────────────────────────────────────
/**
* Chips used inside a ServiceOption-style card layout,
* showing service tags and category labels.
*/
export const InServiceOption: Story = {
name: 'In Context — Service Option',
render: () => (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, maxWidth: 400 }}>
<Card interactive>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'start', mb: 1 }}>
<Typography variant="h5">Chapel Ceremony</Typography>
<Typography variant="display3" color="primary">
$1,200
</Typography>
</Box>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Traditional chapel service with celebrant and music of your choosing.
</Typography>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<Chip size="small" icon={<ChurchIcon />} label="Indoor" />
<Chip size="small" icon={<MusicNoteIcon />} label="Music included" />
<Chip size="small" label="60 minutes" />
</Box>
</Card>
<Card interactive>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'start', mb: 1 }}>
<Typography variant="h5">Graveside Service</Typography>
<Typography variant="display3" color="primary">
$900
</Typography>
</Box>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Intimate outdoor farewell at the burial site.
</Typography>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<Chip size="small" label="Outdoor" />
<Chip size="small" label="30 minutes" />
<Chip size="small" color="primary" label="Popular" />
</Box>
</Card>
</Box>
),
};
// ─── Complete Matrix ────────────────────────────────────────────────────────
/** Full variant × colour × size × state matrix for visual QA */
export const CompleteMatrix: Story = {
name: 'Complete Matrix',
render: () => {
const variants = ['filled', 'outlined'] as const;
const colors = ['default', 'primary'] as const;
const sizes = ['medium', 'small'] as const;
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
{variants.map((variant) => (
<Box key={variant}>
<Typography variant="label" sx={{ mb: 1, textTransform: 'capitalize' }}>
{variant}
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
{sizes.map((size) => (
<Box key={size} sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
<Box sx={{ width: 70, fontSize: 12, color: 'text.secondary' }}>{size}</Box>
{colors.map((color) => (
<React.Fragment key={color}>
<Chip variant={variant} color={color} size={size} label={color} />
<Chip
variant={variant}
color={color}
size={size}
label={`${color} + icon`}
icon={<LocalOfferIcon />}
/>
<Chip
variant={variant}
color={color}
size={size}
label={`${color} delete`}
onDelete={() => {}}
/>
</React.Fragment>
))}
</Box>
))}
</Box>
</Box>
))}
<Box>
<Typography variant="label" sx={{ mb: 1 }}>
Selected state
</Typography>
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
<Chip selected label="Filled selected" onClick={() => {}} />
<Chip selected variant="outlined" label="Outlined selected" onClick={() => {}} />
<Chip selected label="With icon" icon={<CheckIcon />} onClick={() => {}} />
</Box>
</Box>
</Box>
);
},
};