Files
Calvin 5021556c8e BDS component polish: link contrast, static cards, uniform CTAs, stacked hero (#3851)
* 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>
2026-08-18 16:49:10 -07:00

161 lines
5.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CardTextIconCard Component
A card component featuring an icon, heading, and description. Built from Section Cards - Icon and Section Cards - Text Grid Figma designs.
## Overview
CardTextIconCard displays an icon at the top, followed by a heading and description. The description accepts `ReactNode`, so it can include hyperlinks and other rich content. No buttons; links are inline within the description.
## Features
- **Icon + Text Layout**: Icon container, heading, and description in a vertical stack (optional)
- **Rich Description**: `description` accepts `ReactNode` for inline links and formatted content
- **Aspect Ratio Foundation**: Optional `aspectRatio` prop for future responsive sizing
- **Light/Dark Mode**: Full theming support
- **Responsive Design**: Adaptive icon size and spacing across breakpoints
## Usage
### Basic Usage
```tsx
<CardTextIconCard
icon="/icons/docs.svg"
iconAlt="Documentation"
heading="Documentation"
description="Access everything you need to get started with the XRPL."
/>
```
### With Inline Link in Description
```tsx
<CardTextIconCard
icon="/icons/docs.svg"
heading="Documentation"
description={
<>
Learn more in our{' '}
<a href="/docs">documentation</a>.
</>
}
/>
```
Inline `<a>` tags in the description share the card’s description styles in `CardTextIconCard.scss`. In **light** mode, global `html.light` link rules can compete with those styles; use **`BdsLink`** with the design-system `bds-link` class if you need consistent BDS link behavior, or see `CardTextIconCard.scss` for `.bds-card-text-icon-card__description` link styling.
### With Aspect Ratio
```tsx
<CardTextIconCard
icon="/icons/docs.svg"
heading="Documentation"
description="Access everything you need."
aspectRatio={4 / 3}
/>
```
## Props
### CardTextIconCardProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `icon` | `string` | - | Icon image URL |
| `iconAlt` | `string` | `''` | Alt text for the icon image |
| `heading` | `string` | Required | Card heading |
| `headingAs` | `'h1'`–`'h6'` | `'h3'` | Semantic heading element, so cards enter the document outline. Use a level that fits the page hierarchy (typically one below the section heading). |
| `description` | `React.ReactNode` | Required | Card description; accepts rich content (e.g., text with inline links) |
| `aspectRatio` | `number` | - | Optional ratio for future use; applied via CSS variable |
| `gridColSpan` | `ResponsiveValue<PageGridSpanValue>` | - | When provided, the card renders as `PageGrid.Col as="li"` with this span, becoming the grid column itself. See [Grid integration](#grid-integration). |
| `height` | `number` | - | Explicit `height` attribute for the icon image |
| `width` | `number` | - | Explicit `width` attribute for the icon image |
| `className` | `string` | - | Additional CSS classes |
## Grid integration
The card has two rendering modes:
- **Standalone (default)** — renders a plain `<div class="bds-card-text-icon-card">`.
Use this when you're placing the card inside your own layout.
- **Grid column** — when `gridColSpan` is provided, the card renders as
`PageGrid.Col as="li"` with the modifier class
`bds-card-text-icon-card--grid-col`. The card *is* the grid column, so it must
sit inside a `PageGrid.Row as="ul"`.
This is how [CardsTextGrid](../../sections/CardsTextGrid/README.md) and
[CardsIconGrid](../../sections/CardsIconGrid/README.md) consume it — both pass
`gridColSpan={{ base: 4, md: 4, lg: 6 }}`.
```tsx
<PageGrid.Row as="ul">
<CardTextIconCard
heading="Documentation"
description="Access everything you need."
gridColSpan={{ base: 4, md: 4, lg: 6 }}
/>
</PageGrid.Row>
```
### Heading levels
`headingAs` defaults to `'h3'`. Because these cards are usually rendered under a
section heading, set it explicitly when `h3` would skip a level:
```tsx
<CardTextIconCard
heading="Documentation"
headingAs="h4"
description="Access everything you need."
/>
```
## Component Structure
```tsx
<>
<div className="bds-card-text-icon-card__icon">
{icon && (
<img
src={icon}
alt={iconAlt}
{...(iconHeight != null && { height: iconHeight })}
{...(iconWidth != null && { width: iconWidth })}
className="bds-card-text-icon-card__icon-img"
/>
)}
<strong className="bds-card-text-icon-card__heading sh-md-r">{heading}</strong>
</div>
<p className="bds-card-text-icon-card__description body-l">
{description}
</p>
</>
```
## Responsive Sizing
| Breakpoint | Icon Size | Padding | Gap |
|------------|-----------|---------|-----|
| Base (< 576px) | 32px | 16px | 16px |
| MD (576px - 991px) | 36px | 20px | 20px |
| LG (≥ 992px) | 40px | 32px | 24px |
## Files
- `CardTextIconCard.tsx` - React component with TypeScript
- `CardTextIconCard.scss` - Styles with BEM naming
- `index.ts` - Barrel exports
- `README.md` - This file
## Import
```tsx
import { CardTextIconCard } from 'shared/components/CardTextIcon';
// or
import { CardTextIconCard, type CardTextIconCardProps } from 'shared/components/CardTextIcon';
```
## Design System
Part of the Brand Design System (BDS) with `bds-` namespace prefix.