mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-10-01 09:27:59 +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>
405 lines
13 KiB
SCSS
405 lines
13 KiB
SCSS
// BDS CardImage Component Styles
|
|
// Brand Design System - Responsive card with image, title, subtitle, and CTA button
|
|
//
|
|
// Naming Convention: BEM with 'bds' namespace
|
|
// .bds-card-image - Base card container
|
|
// .bds-card-image--hovered - Hover state (scales image only)
|
|
// .bds-card-image--disabled - Disabled state
|
|
// .bds-card-image__image-container - Gray background image wrapper
|
|
// .bds-card-image__image - The actual image element
|
|
// .bds-card-image__content - Title/subtitle/button wrapper
|
|
// .bds-card-image__text - Title and subtitle wrapper
|
|
// .bds-card-image__title - Card title (uses .sh-md-r)
|
|
// .bds-card-image__subtitle - Card subtitle (uses .body-l)
|
|
//
|
|
// Note: This file is imported within xrpl.scss after Bootstrap and project
|
|
// variables are loaded, so $grid-breakpoints, colors, and mixins are available.
|
|
|
|
// =============================================================================
|
|
// Design Tokens (from Figma)
|
|
// =============================================================================
|
|
// Note: Uses centralized spacing tokens from _spacing.scss where applicable.
|
|
// Component-specific values (heights, dimensions) remain local.
|
|
|
|
// Card dimensions - LG (Large) variant (default, ≥992px)
|
|
$bds-card-image-height-lg: 620px;
|
|
$bds-card-image-image-height-lg: 400px;
|
|
|
|
// Card dimensions - MD (Medium) variant (576px - 991px)
|
|
$bds-card-image-height-md: 560px;
|
|
$bds-card-image-image-height-md: 280px;
|
|
|
|
// Card dimensions - SM (Small) variant (<576px)
|
|
$bds-card-image-height-sm: 536px;
|
|
$bds-card-image-image-height-sm: 268px;
|
|
|
|
// Spacing - LG (Large) variant (default, ≥992px)
|
|
$bds-card-image-gap-lg: $bds-space-2xl; // 24px - Gap between image and content
|
|
$bds-card-image-title-gap-lg: $bds-space-md; // 12px - Gap between title and subtitle
|
|
$bds-card-image-content-padding: $bds-space-sm; // 8px - Horizontal padding for content
|
|
$bds-card-image-button-margin-lg: $bds-space-3xl; // 32px - Margin above button
|
|
|
|
// Spacing - MD (Medium) variant (576px - 991px)
|
|
$bds-card-image-gap-md: $bds-space-lg; // 16px - Gap between image and content
|
|
$bds-card-image-title-gap-md: $bds-space-sm; // 8px - Gap between title and subtitle
|
|
$bds-card-image-button-margin-md: $bds-space-2xl; // 24px - Margin above button
|
|
|
|
// Spacing - SM (Small) variant (<576px)
|
|
$bds-card-image-gap-sm: $bds-space-lg; // 16px - Gap between image and content
|
|
$bds-card-image-title-gap-sm: $bds-space-sm; // 8px - Gap between title and subtitle
|
|
$bds-card-image-button-margin-sm: $bds-space-2xl; // 24px - Margin above button
|
|
|
|
// Bulleted subtitle hanging indent (width of the bullet + its trailing space)
|
|
$bds-card-image-bullet-indent: 0.75em;
|
|
|
|
// Colors
|
|
$bds-card-image-bg: $white;
|
|
$bds-card-image-image-bg: $gray-100;
|
|
$bds-card-image-text-color: #141414; // Neutral black from Figma
|
|
|
|
// Animation - uses centralized tokens from _animations.scss
|
|
$bds-card-image-transition-duration: 150ms; // Component-specific (faster than default)
|
|
$bds-card-image-transition-timing: $bds-transition-timing;
|
|
|
|
// =============================================================================
|
|
// Base Card Styles
|
|
// =============================================================================
|
|
|
|
.bds-card-image {
|
|
// Card container
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: $bds-card-image-gap-lg; // Gap between image container and content
|
|
width: 100%; // Fill available width (4-column span on LG+)
|
|
background-color: $bds-card-image-bg;
|
|
cursor: pointer;
|
|
|
|
// Suppress the default <a> underline (the card renders as an <a> when `href` is set)
|
|
// so the title/subtitle never pick up the global anchor hover underline.
|
|
&,
|
|
&:hover,
|
|
&:focus,
|
|
&:active {
|
|
text-decoration: none;
|
|
}
|
|
|
|
// When inside a grid column, fill the column width
|
|
.bds-grid__col & {
|
|
flex: 1 1 auto;
|
|
width: 100%;
|
|
}
|
|
|
|
// Focus styles
|
|
&:focus {
|
|
outline: 2px solid $bds-card-image-text-color;
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
&:focus:not(:focus-visible) {
|
|
outline: none;
|
|
}
|
|
|
|
&:focus-visible {
|
|
outline: 2px solid $bds-card-image-text-color;
|
|
outline-offset: 2px;
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Image Container
|
|
// =============================================================================
|
|
// Image container has fixed height per breakpoint.
|
|
// Image within maintains its aspect ratio using object-fit: contain.
|
|
|
|
.bds-card-image__image-container {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100%;
|
|
height: $bds-card-image-image-height-lg;
|
|
background-color: var(--bds-card-image-bg, $bds-card-image-image-bg);
|
|
overflow: hidden;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.bds-card-image__image {
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
width: auto;
|
|
height: auto;
|
|
object-fit: contain; // Maintain image aspect ratio (1:1 for this design)
|
|
object-position: center;
|
|
transition: transform $bds-card-image-transition-duration $bds-card-image-transition-timing;
|
|
}
|
|
|
|
// =============================================================================
|
|
// Full Bleed Modifier
|
|
// =============================================================================
|
|
// When fullBleed is true, image fills entire container with object-fit: cover
|
|
// and uses aspect-ratio for responsive sizing
|
|
|
|
.bds-card-image--full-bleed {
|
|
// Override fixed heights - use auto height with aspect ratio
|
|
height: auto;
|
|
width: 100%; // Ensure card fills parent container
|
|
|
|
.bds-card-image__image-container {
|
|
// Use aspect-ratio instead of fixed height for responsive scaling
|
|
width: 100%;
|
|
height: auto;
|
|
// 1:1 aspect ratio for all breakpoints (from Figma designs)
|
|
aspect-ratio: 1 / 1;
|
|
}
|
|
|
|
.bds-card-image__image {
|
|
width: 100%;
|
|
height: 100%;
|
|
max-width: none;
|
|
max-height: none;
|
|
object-fit: cover;
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Content Area
|
|
// =============================================================================
|
|
|
|
.bds-card-image__content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between; // Distribute space between text and button
|
|
flex: 1;
|
|
padding: 0 $bds-card-image-content-padding; // Horizontal padding only
|
|
padding-bottom: $bds-card-image-content-padding;
|
|
min-height: 0; // Allow flex shrinking
|
|
}
|
|
|
|
.bds-card-image__text {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: $bds-card-image-title-gap-lg;
|
|
flex-shrink: 0; // Don't shrink text container
|
|
}
|
|
|
|
.bds-card-image__title {
|
|
// Typography handled by .sh-md-r class from _font.scss
|
|
color: $bds-card-image-text-color;
|
|
margin: 0;
|
|
text-align: left; // Always left-align, regardless of parent
|
|
// Title should be 1 line only
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.bds-card-image__subtitle {
|
|
// Typography handled by .body-l class from _font.scss
|
|
color: $bds-card-image-text-color;
|
|
margin: 0;
|
|
text-align: left; // Always left-align, regardless of parent
|
|
display: -webkit-box;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
// Honor explicit `\n` in the subtitle string as a line break, while still
|
|
// collapsing runs of spaces and wrapping normally.
|
|
white-space: pre-line;
|
|
}
|
|
|
|
// Bulleted subtitle: rendered as a <ul> by the component when every `\n`-separated
|
|
// line starts with a bullet marker. The marker sits in its own 1em box so wrapped
|
|
// text hangs-indented under the first character rather than under the bullet.
|
|
.bds-card-image__subtitle--list {
|
|
display: block; // Override the -webkit-box above, which would break list layout
|
|
list-style: none;
|
|
padding: 0;
|
|
white-space: normal; // Lines are already split into <li> elements
|
|
}
|
|
|
|
.bds-card-image__subtitle-item {
|
|
padding-left: $bds-card-image-bullet-indent;
|
|
text-indent: -$bds-card-image-bullet-indent; // Pull the marker back to the left edge
|
|
|
|
&::before {
|
|
content: "\2022"; // •
|
|
display: inline-block;
|
|
width: $bds-card-image-bullet-indent;
|
|
text-indent: 0;
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Button Positioning
|
|
// =============================================================================
|
|
|
|
.bds-card-image__content .bds-btn {
|
|
align-self: flex-start;
|
|
flex-shrink: 0; // Don't shrink button
|
|
margin-top: $bds-card-image-button-margin-sm; // Mobile: 24px
|
|
|
|
@include media-breakpoint-up(md) {
|
|
margin-top: $bds-card-image-button-margin-md; // Tablet: 24px
|
|
}
|
|
|
|
@include media-breakpoint-up(lg) {
|
|
margin-top: $bds-card-image-button-margin-lg; // Desktop: 32px
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Hover State - Image Animation Only
|
|
// =============================================================================
|
|
// When the card is hovered, scale the image (button hover state is independent)
|
|
|
|
.bds-card-image--hovered:not(.bds-card-image--disabled),
|
|
.bds-card-image:hover:not(.bds-card-image--disabled) {
|
|
// Scale image by 10% on hover
|
|
.bds-card-image__image {
|
|
transform: scale(1.1);
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Focus State
|
|
// =============================================================================
|
|
// Focus styles are handled by base card styles (outline)
|
|
// Button focus state is independent
|
|
|
|
// =============================================================================
|
|
// Disabled State
|
|
// =============================================================================
|
|
|
|
.bds-card-image--disabled {
|
|
cursor: not-allowed;
|
|
pointer-events: none;
|
|
|
|
.bds-card-image__title,
|
|
.bds-card-image__subtitle {
|
|
color: $gray-500;
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Responsive Styles - MD (Medium) Variant
|
|
// =============================================================================
|
|
|
|
@include media-breakpoint-down(lg) {
|
|
.bds-card-image {
|
|
gap: $bds-card-image-gap-md; // Gap between image container and content for MD
|
|
}
|
|
|
|
.bds-card-image__image-container {
|
|
height: $bds-card-image-image-height-md;
|
|
}
|
|
|
|
.bds-card-image__content {
|
|
padding: 0 $bds-card-image-content-padding; // Horizontal padding only
|
|
}
|
|
|
|
.bds-card-image__text {
|
|
gap: $bds-card-image-title-gap-md;
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Responsive Styles - SM (Small) Variant
|
|
// =============================================================================
|
|
|
|
@include media-breakpoint-down(sm) {
|
|
.bds-card-image {
|
|
gap: $bds-card-image-gap-sm; // Gap between image container and content for SM
|
|
}
|
|
|
|
.bds-card-image__image-container {
|
|
height: $bds-card-image-image-height-sm;
|
|
}
|
|
|
|
.bds-card-image__content {
|
|
padding: 0 $bds-card-image-content-padding; // Horizontal padding only
|
|
}
|
|
|
|
.bds-card-image__text {
|
|
gap: $bds-card-image-title-gap-sm;
|
|
}
|
|
|
|
}
|
|
|
|
// =============================================================================
|
|
// Light Mode Styles
|
|
// =============================================================================
|
|
|
|
html.light {
|
|
.bds-card-image {
|
|
&:focus {
|
|
outline-color: $bds-card-image-text-color;
|
|
}
|
|
|
|
&:focus-visible {
|
|
outline-color: $bds-card-image-text-color;
|
|
}
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Dark Mode Styles (from Figma design tokens)
|
|
// =============================================================================
|
|
// Design tokens:
|
|
// - Card background: neutral/black (#141414) → $gray-900
|
|
// - Image container: neutral/500 (#72777E) → $gray-500
|
|
// - Title: neutral/white (#FFFFFF) → $white
|
|
// - Subtitle: neutral/200 (#E6EAF0) → $gray-200
|
|
// - Focus outline: neutral/white (#FFFFFF) → $white
|
|
// - Hover overlay: 15% black (rgba(114,119,126,0.15))
|
|
// - Pressed overlay: 45% black (rgba(114,119,126,0.45))
|
|
// - Disabled button bg: neutral/500 (#72777E) → $gray-500
|
|
// - Disabled button text: neutral/300 (#CAD4DF) → $gray-300
|
|
|
|
html.dark {
|
|
.bds-card-image {
|
|
background-color: $gray-900;
|
|
border-color: $gray-900; // Border blends with background in dark mode
|
|
|
|
// Focus styles - white outline
|
|
&:focus {
|
|
outline: 2px solid $white;
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
&:focus-visible {
|
|
outline: 2px solid $white;
|
|
outline-offset: 2px;
|
|
}
|
|
}
|
|
|
|
// Image container - neutral/500 (#72777E)
|
|
.bds-card-image__image-container {
|
|
background-color: var(--bds-card-image-bg, $gray-500);
|
|
}
|
|
|
|
// Title - neutral/white (#FFFFFF)
|
|
.bds-card-image__title {
|
|
color: $white;
|
|
}
|
|
|
|
// Subtitle - neutral/200 (#E6EAF0)
|
|
.bds-card-image__subtitle {
|
|
color: $gray-200;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Dark Mode - Disabled State
|
|
// ---------------------------------------------------------------------------
|
|
.bds-card-image--disabled {
|
|
// Text colors remain but muted
|
|
.bds-card-image__title {
|
|
color: $white;
|
|
}
|
|
|
|
.bds-card-image__subtitle {
|
|
color: $gray-200;
|
|
}
|
|
|
|
// Button in disabled state uses gray-500 bg and gray-300 text
|
|
// (handled by Button component's disabled styles)
|
|
}
|
|
}
|