Add Card atom component

- Create card component tokens (borderRadius, padding, shadow, border, background)
- Build Card component with elevated/outlined variants, interactive hover, padding presets
- Add MUI theme overrides using card tokens (shadow.md resting, border for outlined)
- Create 8 Storybook stories: Default, Variants, Interactive, PaddingPresets,
  PriceCardPreview, ServiceOptionPreview, WithImage, RichContent
- Regenerate token pipeline outputs (7 new card tokens)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 15:31:10 +11:00
parent e4f9edd97f
commit 7169a6559b
7 changed files with 467 additions and 1 deletions

View File

@@ -0,0 +1,98 @@
import React from 'react';
import MuiCard from '@mui/material/Card';
import type { CardProps as MuiCardProps } from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
// ─── Types ───────────────────────────────────────────────────────────────────
/** Props for the FA Card component */
export interface CardProps extends Omit<MuiCardProps, 'raised' | 'variant'> {
/** Visual style: "elevated" uses shadow, "outlined" uses border */
variant?: 'elevated' | 'outlined';
/** Adds hover shadow lift and pointer cursor for clickable cards */
interactive?: boolean;
/** Padding preset: "default" (24px), "compact" (16px), "none" (no wrapper) */
padding?: 'default' | 'compact' | 'none';
/** Content to render inside the card */
children?: React.ReactNode;
}
// ─── Component ───────────────────────────────────────────────────────────────
/**
* Content container for the FA design system.
*
* Wraps MUI Card with FA brand tokens, two visual variants (elevated/outlined),
* optional hover interactivity, and padding presets.
*
* Variant mapping from design:
* - `elevated` (default) — shadow.md resting, white background
* - `outlined` — neutral border, no shadow, white background
*
* Use `interactive` for clickable cards (PriceCard, ServiceOption) —
* adds shadow.lg hover lift and cursor pointer.
*
* Use `padding="none"` when composing with CardMedia or custom layouts
* that need full-bleed content.
*/
export const Card = React.forwardRef<HTMLDivElement, CardProps>(
(
{
variant = 'elevated',
interactive = false,
padding = 'default',
children,
sx,
...props
},
ref,
) => {
// Map FA variant names to MUI Card variant
const muiVariant = variant === 'outlined' ? 'outlined' : undefined;
return (
<MuiCard
ref={ref}
variant={muiVariant}
elevation={0}
sx={[
// Interactive: hover lift + pointer
interactive && {
cursor: 'pointer',
'&:hover': {
boxShadow: 'var(--fa-card-shadow-hover)',
},
},
// Focus-visible for keyboard accessibility on interactive cards
interactive && {
'&:focus-visible': {
outline: (theme: Record<string, any>) =>
`2px solid ${theme.palette.primary.main}`,
outlineOffset: '2px',
},
},
...(Array.isArray(sx) ? sx : [sx]),
]}
{...props}
>
{padding !== 'none' ? (
<CardContent
sx={{
p: padding === 'compact' ? 4 : 6,
'&:last-child': {
pb: padding === 'compact' ? 4 : 6,
},
}}
>
{children}
</CardContent>
) : (
children
)}
</MuiCard>
);
},
);
Card.displayName = 'Card';
export default Card;