mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-09-29 16:38:00 +00:00
* BDS component polish: link visibility, static cards, uniform CTAs Component-level fixes and refinements across the 2026 brand components. Accessibility / contrast - StandardCard: pin description link colors. Every card variant has a light background in both themes, so theme-level link colors were wrong inside it — dark mode painted plain anchors white and BdsLink lilac-300, both of which wash out on a pale card. Links (including :visited) now use the card's own text color with an underline carrying the affordance; hover uses lilac-500, which clears 4.5:1 on all four card backgrounds. - Breadcrumbs: point the open dropdown menu at the breadcrumb color tokens so the trail, trigger, and menu read as one color in both themes, and give dark mode gray-6 (7.41:1, matching light mode's 7.23:1). Interaction - CardOffgrid: drop every hover affordance when a card has neither href nor onClick. Static cards no longer get a pointer cursor, the color-wipe overlay, or a pressed state — the tokenization and trading carousels pass link-less cards and were advertising a click target that did not exist. - CarouselFeatured: add a 'fade' transition alongside the default 'slide', for decks whose slides share a heading. Inactive slides are now inert, keeping focus and pointer events out of them in both styles. - Add a bds-reduced-motion mixin so components can collapse motion to an instant state change. Design consistency - ButtonGroup: add forceVariant and forceNoPadding overrides, letting a section opt out of the count-based variant defaults and render one uniform treatment. Existing consumers are unaffected. - FeatureTwoColumn: render every link as a tertiary button regardless of count, flush with the title and description. - CardImage: render an all-bullet subtitle as a real <ul> so wrapped text hangs under the first character and screen readers announce it as a list. - docs: give the node-installation card descriptions a paragraph break before their "Learn More" link. Dependencies - Bump @codemirror/state and view, add lang-javascript, lang-json, and lint, with overrides pinning state/view to one copy. Docs updated alongside each component. Built CSS regenerated with the production script to match the committed artifact's minified format. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * ButtonGroup: don't strip padding from forced filled variants `noPadding` was derived as `forceNoPadding || isMultiButton`, which was safe while the 3+ block layout was always tertiary. `forceVariant` also accepts `primary` and `secondary`, so a 3-button group forcing a filled variant had `padding: 0 !important` (Button.scss) applied with no way to opt out. Tie the implied no-padding to the resolved variant being tertiary. Behavior is unchanged for every caller that doesn't pass `forceVariant`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Stacked hero variant, fade-by-default carousel, component docs Layout - HeaderHeroPrimaryMedia: add a `stacked` prop that puts the headline, subtitle, and buttons in one column (full width at base, 7/8 at md, 9/12 at lg) instead of the default headline-left / CTA-right split. The headline and CTA block bottom-align against each other in the two-column layout, so the stacked variant undoes that. Both arrangements now share the same headline and CTA elements, so they can't drift apart. Used on the docs landing hero. Motion - CarouselFeatured: make `fade` the default transition. Slides that share a heading are the common case, and a horizontal wipe drags the identical heading across the screen only to set it back down. `slide` is now opt-in for decks whose panels are genuinely distinct. Nudge the crossfade to 260ms in / 200ms out. The home page carousel opts into fade explicitly; developer-funding now inherits it. Fixes - CardTextIconCard: pass `headingAs` through to `cardContent`, which was accepting the prop but never receiving it. Docs - Expand the Button, CardImage, CardTextIcon, PageGrid, CalloutMediaBanner, LogoSquareGrid, and HeaderHeroPrimaryMedia references. Built CSS regenerated with the production script. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
286 lines
7.8 KiB
Markdown
286 lines
7.8 KiB
Markdown
# 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 `<ul>` (`bds-card-image__subtitle--list`) with one `<li>` per line.
|
|
- Anything else renders as a single `<p>`, where `white-space: pre-line` means
|
|
`\n` still produces a visible line break.
|
|
|
|
```tsx
|
|
// Renders as a bulleted list
|
|
<CardImage subtitle={"• Fast settlement\n• Low fees\n• Built-in DEX"} ... />
|
|
|
|
// Renders as a paragraph with a line break
|
|
<CardImage subtitle={"Fast settlement.\nLow fees."} ... />
|
|
```
|
|
|
|
## 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
|
|
<CardImage fullBleed image="/images/hero.png" ... />
|
|
<CardImage backgroundColor="#F3F1EB" image="/images/logo.svg" ... />
|
|
```
|
|
|
|
## 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';
|
|
|
|
<CardImage
|
|
image="/images/docs-hero.png"
|
|
imageAlt="Documentation illustration"
|
|
title="Documentation"
|
|
subtitle="Access everything you need to get started working with the XRPL."
|
|
buttonLabel="Get Started"
|
|
href="/docs"
|
|
/>
|
|
```
|
|
|
|
### Card with Click Handler
|
|
|
|
```tsx
|
|
<CardImage
|
|
image="/images/feature.png"
|
|
imageAlt="Feature illustration"
|
|
title="New Feature"
|
|
subtitle="Learn about our latest feature and how it can help you build better applications."
|
|
buttonLabel="Learn More"
|
|
onClick={() => console.log('clicked')}
|
|
/>
|
|
```
|
|
|
|
### Disabled Card
|
|
|
|
```tsx
|
|
<CardImage
|
|
image="/images/coming-soon.png"
|
|
imageAlt="Coming soon"
|
|
title="Coming Soon"
|
|
subtitle="This feature is not yet available."
|
|
buttonLabel="Unavailable"
|
|
disabled
|
|
/>
|
|
```
|
|
|
|
### Card with Custom Class
|
|
|
|
```tsx
|
|
<CardImage
|
|
image="/images/hero.png"
|
|
imageAlt="Hero image"
|
|
title="Custom Styled"
|
|
subtitle="Card with additional custom styling."
|
|
buttonLabel="Explore"
|
|
href="/explore"
|
|
className="my-custom-card"
|
|
/>
|
|
```
|
|
|
|
## 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.
|