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:
309
src/components/atoms/Card/Card.stories.tsx
Normal file
309
src/components/atoms/Card/Card.stories.tsx
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { Card } from './Card';
|
||||||
|
import { Typography } from '../Typography';
|
||||||
|
import { Button } from '../Button';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
|
||||||
|
const meta: Meta<typeof Card> = {
|
||||||
|
title: 'Atoms/Card',
|
||||||
|
component: Card,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
variant: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['elevated', 'outlined'],
|
||||||
|
description: 'Visual style variant',
|
||||||
|
table: { defaultValue: { summary: 'elevated' } },
|
||||||
|
},
|
||||||
|
interactive: {
|
||||||
|
control: 'boolean',
|
||||||
|
description: 'Adds hover shadow lift and pointer cursor',
|
||||||
|
table: { defaultValue: { summary: 'false' } },
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['default', 'compact', 'none'],
|
||||||
|
description: 'Padding preset',
|
||||||
|
table: { defaultValue: { summary: 'default' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof Card>;
|
||||||
|
|
||||||
|
// ─── Default ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Default card — elevated with standard padding */
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
<Typography variant="h4" gutterBottom>
|
||||||
|
Funeral package
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" color="text.secondary">
|
||||||
|
A comprehensive service including chapel ceremony, transport, and
|
||||||
|
preparation. Suitable for families seeking a traditional farewell.
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div style={{ width: 400 }}>
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── Variants ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Both visual variants side by side */
|
||||||
|
export const Variants: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', gap: 24, maxWidth: 800 }}>
|
||||||
|
<Card variant="elevated" sx={{ flex: 1 }}>
|
||||||
|
<Typography variant="h5" gutterBottom>
|
||||||
|
Elevated
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Uses shadow for depth. Default variant for most content cards.
|
||||||
|
</Typography>
|
||||||
|
</Card>
|
||||||
|
<Card variant="outlined" sx={{ flex: 1 }}>
|
||||||
|
<Typography variant="h5" gutterBottom>
|
||||||
|
Outlined
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Uses a subtle border. Good for less prominent or grouped content.
|
||||||
|
</Typography>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── Interactive ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Interactive cards with hover elevation (click/hover to see effect) */
|
||||||
|
export const Interactive: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', gap: 24, maxWidth: 800 }}>
|
||||||
|
<Card
|
||||||
|
interactive
|
||||||
|
sx={{ flex: 1 }}
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => alert('Card clicked')}
|
||||||
|
>
|
||||||
|
<Typography variant="h5" gutterBottom>
|
||||||
|
Elevated + Interactive
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Hover to see the shadow lift. Click to trigger action.
|
||||||
|
</Typography>
|
||||||
|
</Card>
|
||||||
|
<Card
|
||||||
|
variant="outlined"
|
||||||
|
interactive
|
||||||
|
sx={{ flex: 1 }}
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => alert('Card clicked')}
|
||||||
|
>
|
||||||
|
<Typography variant="h5" gutterBottom>
|
||||||
|
Outlined + Interactive
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Outlined cards can also be interactive with hover effects.
|
||||||
|
</Typography>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── Padding Presets ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** All three padding options */
|
||||||
|
export const PaddingPresets: Story = {
|
||||||
|
name: 'Padding Presets',
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', gap: 24, maxWidth: 900, alignItems: 'start' }}>
|
||||||
|
<Card padding="default" sx={{ flex: 1 }}>
|
||||||
|
<Typography variant="labelLg" gutterBottom>
|
||||||
|
Default (24px)
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Standard spacing for desktop cards.
|
||||||
|
</Typography>
|
||||||
|
</Card>
|
||||||
|
<Card padding="compact" sx={{ flex: 1 }}>
|
||||||
|
<Typography variant="labelLg" gutterBottom>
|
||||||
|
Compact (16px)
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Tighter spacing for mobile or dense layouts.
|
||||||
|
</Typography>
|
||||||
|
</Card>
|
||||||
|
<Card padding="none" sx={{ flex: 1 }}>
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
|
<Typography variant="labelLg" gutterBottom>
|
||||||
|
None (manual)
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Full control — add your own padding.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── Price Card Preview ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preview of how Card will be used in the PriceCard molecule.
|
||||||
|
* Demonstrates realistic content composition with FA typography and brand colours.
|
||||||
|
*/
|
||||||
|
export const PriceCardPreview: Story = {
|
||||||
|
name: 'Price Card Preview',
|
||||||
|
render: () => (
|
||||||
|
<div style={{ width: 340 }}>
|
||||||
|
<Card interactive tabIndex={0}>
|
||||||
|
<Typography variant="overline" color="text.secondary" gutterBottom>
|
||||||
|
Essential
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="display3" color="primary" sx={{ mb: 1 }}>
|
||||||
|
$3,200
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||||
|
A respectful and simple service with chapel ceremony, transport, and
|
||||||
|
professional preparation.
|
||||||
|
</Typography>
|
||||||
|
<Button fullWidth size="large">
|
||||||
|
Select this package
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── Service Option Preview ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preview of how Card will be used in the ServiceOption molecule.
|
||||||
|
* Shows a selectable option card pattern.
|
||||||
|
*/
|
||||||
|
export const ServiceOptionPreview: Story = {
|
||||||
|
name: 'Service Option Preview',
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', gap: 16, maxWidth: 700 }}>
|
||||||
|
{[
|
||||||
|
{ title: 'Chapel service', desc: 'Traditional ceremony in our chapel' },
|
||||||
|
{ title: 'Graveside service', desc: 'Intimate outdoor farewell' },
|
||||||
|
{ title: 'Memorial service', desc: 'Celebration of life gathering' },
|
||||||
|
].map((option) => (
|
||||||
|
<Card
|
||||||
|
key={option.title}
|
||||||
|
variant="outlined"
|
||||||
|
interactive
|
||||||
|
padding="compact"
|
||||||
|
tabIndex={0}
|
||||||
|
sx={{ flex: 1 }}
|
||||||
|
>
|
||||||
|
<Typography variant="labelLg" gutterBottom>
|
||||||
|
{option.title}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{option.desc}
|
||||||
|
</Typography>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── With Image ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Card with full-bleed image using padding="none" */
|
||||||
|
export const WithImage: Story = {
|
||||||
|
name: 'With Image (No Padding)',
|
||||||
|
render: () => (
|
||||||
|
<div style={{ width: 340 }}>
|
||||||
|
<Card padding="none">
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: 180,
|
||||||
|
backgroundColor: 'action.hover',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Image placeholder
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
|
<Typography variant="h5" gutterBottom>
|
||||||
|
Parsons Chapel
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Our heritage-listed chapel seats up to 120 guests and features
|
||||||
|
modern audio-visual facilities.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── Nested Content ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Card with rich nested content to verify layout flexibility */
|
||||||
|
export const RichContent: Story = {
|
||||||
|
name: 'Rich Content',
|
||||||
|
render: () => (
|
||||||
|
<div style={{ width: 400 }}>
|
||||||
|
<Card>
|
||||||
|
<Typography variant="overline" color="text.secondary">
|
||||||
|
Package details
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h4" sx={{ mt: 1, mb: 2 }}>
|
||||||
|
Premium farewell
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
component="ul"
|
||||||
|
sx={{
|
||||||
|
pl: 4,
|
||||||
|
mb: 3,
|
||||||
|
'& li': { mb: 1 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<Typography variant="body2">Chapel ceremony (up to 120 guests)</Typography>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Typography variant="body2">Premium timber casket</Typography>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Typography variant="body2">Transport within 50km</Typography>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Typography variant="body2">Professional preparation</Typography>
|
||||||
|
</li>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||||
|
<Button size="large" sx={{ flex: 1 }}>
|
||||||
|
Select
|
||||||
|
</Button>
|
||||||
|
<Button variant="outlined" size="large" sx={{ flex: 1 }}>
|
||||||
|
Compare
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
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;
|
||||||
2
src/components/atoms/Card/index.ts
Normal file
2
src/components/atoms/Card/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { Card, type CardProps } from './Card';
|
||||||
|
export { default } from './Card';
|
||||||
@@ -223,6 +223,11 @@
|
|||||||
--fa-button-icon-gap-md: var(--fa-spacing-2); /** 8px icon-text gap */
|
--fa-button-icon-gap-md: var(--fa-spacing-2); /** 8px icon-text gap */
|
||||||
--fa-button-icon-gap-lg: var(--fa-spacing-2); /** 8px icon-text gap */
|
--fa-button-icon-gap-lg: var(--fa-spacing-2); /** 8px icon-text gap */
|
||||||
--fa-button-border-radius-default: var(--fa-border-radius-md); /** 8px — standard button rounding */
|
--fa-button-border-radius-default: var(--fa-border-radius-md); /** 8px — standard button rounding */
|
||||||
|
--fa-card-border-radius-default: var(--fa-border-radius-md); /** 8px — standard card rounding */
|
||||||
|
--fa-card-padding-default: var(--fa-spacing-6); /** 24px — standard card padding (desktop) */
|
||||||
|
--fa-card-padding-compact: var(--fa-spacing-4); /** 16px — compact card padding (mobile, tight layouts) */
|
||||||
|
--fa-card-shadow-default: var(--fa-shadow-md); /** Medium shadow — resting elevated card */
|
||||||
|
--fa-card-shadow-hover: var(--fa-shadow-lg); /** High shadow — interactive card on hover */
|
||||||
--fa-input-padding-x-default: var(--fa-spacing-3); /** 12px — inner horizontal padding matching Figma design */
|
--fa-input-padding-x-default: var(--fa-spacing-3); /** 12px — inner horizontal padding matching Figma design */
|
||||||
--fa-input-padding-y-sm: var(--fa-spacing-2); /** 8px — compact vertical padding for small size */
|
--fa-input-padding-y-sm: var(--fa-spacing-2); /** 8px — compact vertical padding for small size */
|
||||||
--fa-input-padding-y-md: var(--fa-spacing-3); /** 12px — standard vertical padding for medium size */
|
--fa-input-padding-y-md: var(--fa-spacing-3); /** 12px — standard vertical padding for medium size */
|
||||||
@@ -351,4 +356,6 @@
|
|||||||
--fa-typography-overline-sm-font-family: var(--fa-font-family-body);
|
--fa-typography-overline-sm-font-family: var(--fa-font-family-body);
|
||||||
--fa-typography-overline-sm-font-size: var(--fa-font-size-2xs); /** 11px — accessibility floor */
|
--fa-typography-overline-sm-font-size: var(--fa-font-size-2xs); /** 11px — accessibility floor */
|
||||||
--fa-typography-overline-sm-font-weight: var(--fa-font-weight-semibold);
|
--fa-typography-overline-sm-font-weight: var(--fa-font-weight-semibold);
|
||||||
|
--fa-card-border-default: var(--fa-color-border-default); /** Neutral border for outlined cards */
|
||||||
|
--fa-card-background-default: var(--fa-color-surface-raised); /** White — standard card background (raised surface) */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,13 @@ export const ButtonIconGapSm = "4px"; // 4px icon-text gap
|
|||||||
export const ButtonIconGapMd = "8px"; // 8px icon-text gap
|
export const ButtonIconGapMd = "8px"; // 8px icon-text gap
|
||||||
export const ButtonIconGapLg = "8px"; // 8px icon-text gap
|
export const ButtonIconGapLg = "8px"; // 8px icon-text gap
|
||||||
export const ButtonBorderRadiusDefault = "8px"; // 8px — standard button rounding
|
export const ButtonBorderRadiusDefault = "8px"; // 8px — standard button rounding
|
||||||
|
export const CardBorderRadiusDefault = "8px"; // 8px — standard card rounding
|
||||||
|
export const CardPaddingDefault = "24px"; // 24px — standard card padding (desktop)
|
||||||
|
export const CardPaddingCompact = "16px"; // 16px — compact card padding (mobile, tight layouts)
|
||||||
|
export const CardShadowDefault = "0 4px 6px rgba(0,0,0,0.07)"; // Medium shadow — resting elevated card
|
||||||
|
export const CardShadowHover = "0 10px 15px rgba(0,0,0,0.1)"; // High shadow — interactive card on hover
|
||||||
|
export const CardBorderDefault = "#e8e8e8"; // Neutral border for outlined cards
|
||||||
|
export const CardBackgroundDefault = "#ffffff"; // White — standard card background (raised surface)
|
||||||
export const InputHeightSm = "40px"; // Small — compact forms, admin layouts, matches Button medium height
|
export const InputHeightSm = "40px"; // Small — compact forms, admin layouts, matches Button medium height
|
||||||
export const InputHeightMd = "48px"; // Medium (default) — standard forms, matches Button large for alignment
|
export const InputHeightMd = "48px"; // Medium (default) — standard forms, matches Button large for alignment
|
||||||
export const InputPaddingXDefault = "12px"; // 12px — inner horizontal padding matching Figma design
|
export const InputPaddingXDefault = "12px"; // 12px — inner horizontal padding matching Figma design
|
||||||
|
|||||||
@@ -476,9 +476,21 @@ export const theme = createTheme({
|
|||||||
MuiCard: {
|
MuiCard: {
|
||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
root: {
|
root: {
|
||||||
borderRadius: parseInt(t.BorderRadiusMd, 10),
|
borderRadius: parseInt(t.CardBorderRadiusDefault, 10),
|
||||||
|
backgroundColor: t.CardBackgroundDefault,
|
||||||
|
boxShadow: t.CardShadowDefault,
|
||||||
|
transition: 'box-shadow 150ms ease-in-out',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
props: { variant: 'outlined' },
|
||||||
|
style: {
|
||||||
|
boxShadow: 'none',
|
||||||
|
borderColor: t.CardBorderDefault,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
MuiOutlinedInput: {
|
MuiOutlinedInput: {
|
||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
|
|||||||
31
tokens/component/card.json
Normal file
31
tokens/component/card.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"card": {
|
||||||
|
"$description": "Card component tokens — content container with elevation or border. Used by PriceCard, ServiceOption, and other molecule/organism compositions.",
|
||||||
|
"borderRadius": {
|
||||||
|
"$type": "dimension",
|
||||||
|
"$description": "Card corner radius.",
|
||||||
|
"default": { "$value": "{borderRadius.md}", "$description": "8px — standard card rounding" }
|
||||||
|
},
|
||||||
|
"padding": {
|
||||||
|
"$type": "dimension",
|
||||||
|
"$description": "Internal padding for card content area.",
|
||||||
|
"default": { "$value": "{spacing.6}", "$description": "24px — standard card padding (desktop)" },
|
||||||
|
"compact": { "$value": "{spacing.4}", "$description": "16px — compact card padding (mobile, tight layouts)" }
|
||||||
|
},
|
||||||
|
"shadow": {
|
||||||
|
"$description": "Elevation shadows for card variants.",
|
||||||
|
"default": { "$value": "{shadow.md}", "$description": "Medium shadow — resting elevated card" },
|
||||||
|
"hover": { "$value": "{shadow.lg}", "$description": "High shadow — interactive card on hover" }
|
||||||
|
},
|
||||||
|
"border": {
|
||||||
|
"$type": "color",
|
||||||
|
"$description": "Border colours for the outlined card variant.",
|
||||||
|
"default": { "$value": "{color.border.default}", "$description": "Neutral border for outlined cards" }
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"$type": "color",
|
||||||
|
"$description": "Card background colours.",
|
||||||
|
"default": { "$value": "{color.surface.raised}", "$description": "White — standard card background (raised surface)" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user