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:
98
src/components/atoms/Card/Card.tsx
Normal file
98
src/components/atoms/Card/Card.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user