# CardImage Component Documentation
## Overview
The CardImage component is a responsive card implementation following the XRPL Brand Design System (BDS). It displays an image, title, subtitle, and call-to-action button with three responsive size variants that adapt to viewport width.
## Features
- **Three Responsive Variants**: LG (≥992px), MD (576px-991px), SM (<576px)
- **Interactive States**: Default, Hover, Focus, Pressed, Disabled
- **Button Animation on Card Hover**: Hovering the card triggers the button's hover animation
- **Flexible Usage**: Supports both link navigation and click handlers
- **Accessibility**: Keyboard navigation and screen reader support
## Props API
```typescript
interface CardImageProps {
/** Image source URL */
image: string;
/** Alt text for the image */
imageAlt: string;
/** Card title (1 line only) */
title: string;
/** Card subtitle (max 3 lines). `\n` renders as a line break; an all-bullet subtitle renders as a list. */
subtitle: string;
/** Button label text */
buttonLabel: string;
/** Link destination (renders card as clickable link) */
href?: string;
/** Click handler for the button */
onClick?: () => void;
/** Disabled state */
disabled?: boolean;
/** Optional className for custom styling */
className?: string;
/** When true, image fills entire container with object-fit: cover (no visible background) */
fullBleed?: boolean;
/** Custom background color for image container (defaults to gray-100) */
backgroundColor?: string;
}
```
### Default Values
- `disabled`: `false`
- `className`: `''`
- `fullBleed`: `false`
- `backgroundColor`: `undefined` (falls back to the gray-100 image background)
## Subtitle Formatting
`subtitle` is a plain string, but it is not rendered blindly:
- Lines are split on `\n`, and blank lines are dropped.
- If **every** line begins with a bullet marker (`•`, `-`, or `*`), the subtitle
renders as a `
` (`bds-card-image__subtitle--list`) with one `- ` per line.
- Anything else renders as a single `
`, where `white-space: pre-line` means
`\n` still produces a visible line break.
```tsx
// Renders as a bulleted list
// Renders as a paragraph with a line break
```
## Image Sizing
By default the image sits in a fixed-height container against a gray-100
background, preserving its aspect ratio.
- **`fullBleed`** — sets `bds-card-image--full-bleed`, which drops the card's
fixed height (`height: auto`) and makes the image fill the container using a
1:1 aspect ratio with `object-fit: cover`. Use this when the image is the whole
visual and you don't want letterboxing.
- **`backgroundColor`** — sets the `--bds-card-image-bg` custom property on the
image container, overriding the gray-100 default. Has no visible effect
alongside `fullBleed`, since the image covers the container.
```tsx
```
## Responsive Variants
### LG (Large) - Desktop (≥992px)
- Card height: 620px
- Image height: 400px (1:1 aspect ratio preserved)
- 3-column grid width
### MD (Medium) - Tablet (576px - 991px)
- Card height: 560px
- Image height: 280px
- 2-column grid width
### SM (Small) - Mobile (<576px)
- Card height: 536px
- Image height: 268px
- 1-column grid width (full width)
## States
### Default State
The default interactive state with the button showing its primary green background.
### Hover State
When the user hovers over the card:
- Button background fills from bottom-to-top (green-300 → green-200)
- Arrow icon line shrinks
- Gap between label and icon increases
### Focus State
When the card receives keyboard focus:
- Black border outline around the card
- Button shows focus styling
### Pressed State
When the button is being pressed:
- Button returns to default styling momentarily
### Disabled State
When `disabled={true}`:
- Card is non-interactive
- Button shows disabled styling (gray background, no icon)
- Text colors muted
- Cursor changes to `not-allowed`
## Usage Examples
### Basic Card with Link
```tsx
import { CardImage } from 'shared/components/CardImage';
```
### Card with Click Handler
```tsx
console.log('clicked')}
/>
```
### Disabled Card
```tsx
```
### Card with Custom Class
```tsx
```
## Design Tokens
### Spacing
| Token | Value | Description |
|-------|-------|-------------|
| Image-to-content gap | 24px | Gap between image and content area |
| Title-to-subtitle gap | 12px | Gap between title and subtitle |
| Content horizontal padding | 8px | Left/right padding for content |
| Button margin-top | 30px | Button locked to bottom |
| Border radius | 16px | Card corner radius |
### Colors
| Element | Light Mode | Dark Mode |
|---------|------------|-----------|
| Card background | #FFFFFF | Gray 800 |
| Card border | Gray 300 (#CAD4DF) | Gray 700 |
| Image background | Gray 100 (#F0F3F7) | Gray 700 |
| Text color | #141414 | White |
### Typography
| Element | Token | Specs |
|---------|-------|-------|
| Title | `.sh-md-l` | 28px/35px, -0.5px letter-spacing, light weight |
| Subtitle | `.body-l` | 18px/26.1px, -0.5px letter-spacing, light weight |
## How It Works
### Hover Animation
The card tracks hover state via React's `useState`. When hovered:
1. A `bds-card-image--hovered` class is added to the card
2. CSS rules target the nested Button component and apply hover styles
3. The button's `::before` pseudo-element animates (background fill)
4. The arrow icon line shrinks via `scaleX(0)`
This approach ensures the button animation triggers even when hovering areas of the card outside the button itself.
### Link Navigation
When `href` is provided:
- The entire card becomes clickable
- Clicking anywhere on the card navigates to the href
- The card is keyboard accessible (Enter/Space to activate)
### Button Integration
The component uses the existing BDS Button component with:
- `variant="primary"` - Solid green background
- `color="green"` - Green color theme
## Accessibility
### Keyboard Navigation
- **Tab**: Focus the card
- **Enter/Space**: Activate the card (navigate to href or trigger onClick)
### Screen Reader Support
- Card has appropriate `role="link"` when href is provided
- Image has descriptive alt text
- `aria-disabled` attribute for disabled state
## File Structure
```
shared/components/CardImage/
├── CardImage.tsx # Component implementation
├── CardImage.scss # Component styles
├── CardImage.md # This documentation
└── index.ts # Exports
```
## Related Components
- **Button**: Used internally for the CTA button
- **CardOffgrid**: Similar card component with different layout
## Browser Support
The component uses modern CSS features:
- CSS Grid/Flexbox
- CSS transforms and transitions
- `:focus-visible` pseudo-class
- `-webkit-line-clamp` for text truncation
All features are widely supported in modern browsers.