Compare commits

...

1 Commits

Author SHA1 Message Date
akcodez
d6ce246420 Add FeatureSingleTopic pattern component with associated styles
- Introduced the FeatureSingleTopic component, enhancing layout flexibility with responsive design.
- Added SCSS styles for various screen sizes, including media queries for improved spacing and alignment.
- Implemented dark mode styles for better accessibility and visual consistency.
2026-02-03 13:29:24 -08:00
7 changed files with 1359 additions and 0 deletions

View File

@@ -0,0 +1,386 @@
import { PageGrid, PageGridRow, PageGridCol } from 'shared/components/PageGrid/page-grid';
import { FeatureSingleTopic } from 'shared/patterns/FeatureSingleTopic';
export const frontmatter = {
seo: {
title: 'FeatureSingleTopic Pattern Showcase',
description: 'Interactive showcase of the FeatureSingleTopic pattern with all variants, orientations, and button configurations.',
},
};
export default function FeatureSingleTopicShowcase() {
// Placeholder image
const placeholderImage = '/img/demo-bg.png';
return (
<div className="landing">
<div className="overflow-hidden">
{/* Hero Section */}
<section className="my-5 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Pattern Showcase</h6>
<h1 className="mb-4">FeatureSingleTopic Pattern</h1>
<p className="longform">
A feature section pattern that pairs a title and description with a media element
in a two-column layout. Supports two variants (default and accentSurface) and
left/right orientation for flexible content positioning.
</p>
</div>
</section>
{/* Variant Section */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Variants</h2>
<p className="mb-4">
The component supports two variants that control the title section background:
</p>
<ul className="mb-6">
<li><strong>default:</strong> No background on title section</li>
<li><strong>accentSurface:</strong> Gray background (#E6EAF0) on title section</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Default Variant */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Default Variant</strong> - <code>variant="default"</code>
<br />
<small className="text-muted">No background on title section. Clean, minimal look.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="default"
orientation="left"
title="Developer Spotlight"
description="Are you building a peer-to-peer payments solution, integrating stablecoins, or exploring RLUSD on the XRP Ledger?"
media={{ src: placeholderImage, alt: "Feature illustration" }}
links={[
{ label: "Get Started", href: "#start" },
{ label: "Learn More", href: "#learn" }
]}
/>
</div>
{/* AccentSurface Variant */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>AccentSurface Variant</strong> - <code>variant="accentSurface"</code>
<br />
<small className="text-muted">
Gray background (<code>$gray-200</code> / #E6EAF0) on title section.
</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="accentSurface"
orientation="left"
title="Developer Spotlight"
description="Are you building a peer-to-peer payments solution, integrating stablecoins, or exploring RLUSD on the XRP Ledger?"
media={{ src: placeholderImage, alt: "Feature illustration" }}
links={[
{ label: "Get Started", href: "#start" },
{ label: "Learn More", href: "#learn" }
]}
/>
</div>
{/* Orientation Section */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Orientation Variants</h2>
<p className="mb-6">
Control image/content position with the <code>orientation</code> prop.
Use alternating orientations for visual variety on pages with multiple sections.
</p>
<div className="mb-4 p-3" style={{ backgroundColor: '#f0f3f7', borderRadius: '8px' }}>
<strong>📱 Responsive Behavior:</strong>
<ul className="mb-0 mt-2">
<li><code>orientation="left"</code>: Image left, content right on desktop; image above content on mobile</li>
<li><code>orientation="right"</code>: Image right, content left on desktop; content above image on mobile</li>
</ul>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* Orientation Left */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Orientation Left (Default)</strong> - <code>orientation="left"</code>
<br />
<small className="text-muted">
Desktop: Image left, content right | Mobile: Image above content
</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="default"
orientation="left"
title="Image on Left"
description="This layout places the image on the left side and content on the right on desktop screens."
media={{ src: placeholderImage, alt: "Left orientation" }}
links={[
{ label: "Primary Action", href: "#primary" },
{ label: "Secondary", href: "#secondary" }
]}
/>
</div>
{/* Orientation Right */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>Orientation Right</strong> - <code>orientation="right"</code>
<br />
<small className="text-muted">
Desktop: Image right, content left | Mobile: Content above image
</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="accentSurface"
orientation="right"
title="Image on Right"
description="This layout places the image on the right side and content on the left on desktop screens."
media={{ src: placeholderImage, alt: "Right orientation" }}
links={[
{ label: "Primary Action", href: "#primary" },
{ label: "Secondary", href: "#secondary" }
]}
/>
</div>
{/* Button Behavior Section */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Button Behavior</h2>
<p className="mb-4">
The component uses the ButtonGroup pattern which automatically adjusts button rendering based on the number of links provided:
</p>
<ul className="mb-6">
<li><strong>1 link:</strong> Primary button</li>
<li><strong>2 links:</strong> Primary + Tertiary buttons (responsive layout)</li>
<li><strong>3+ links:</strong> All Tertiary buttons in block layout</li>
</ul>
</PageGridCol>
</PageGridRow>
</PageGrid>
{/* 1 Link */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>1 Link</strong> - Primary Button
<br />
<small className="text-muted">Single action rendered as a primary (filled) button.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="default"
orientation="left"
title="Single Action"
description="This example shows a single call-to-action button."
media={{ src: placeholderImage, alt: "Single button" }}
links={[
{ label: "Get Started", href: "#start" }
]}
/>
</div>
{/* 2 Links */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>2 Links</strong> - Primary + Tertiary Side by Side
<br />
<small className="text-muted">Primary and tertiary buttons displayed side by side on desktop.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="default"
orientation="left"
title="Two Actions"
description="This example shows primary and tertiary buttons side by side."
media={{ src: placeholderImage, alt: "Two buttons" }}
links={[
{ label: "Primary Link", href: "#primary" },
{ label: "Tertiary Link", href: "#tertiary" }
]}
/>
</div>
{/* 3 Links */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>3 Links</strong> - Primary + Tertiary + Secondary
<br />
<small className="text-muted">Primary + tertiary side by side on first row, secondary button below.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="default"
orientation="left"
title="Three Actions"
description="This example shows the 3-link layout with secondary button."
media={{ src: placeholderImage, alt: "Three buttons" }}
links={[
{ label: "Primary Link", href: "#primary" },
{ label: "Tertiary Link", href: "#tertiary" },
{ label: "Secondary Link", href: "#secondary" }
]}
/>
</div>
{/* 5 Links */}
<div className="mb-5">
<PageGrid>
<PageGridRow>
<PageGridCol span={12}>
<div className="mb-3">
<strong>5 Links</strong> - Primary + Tertiary + Stacked Tertiary
<br />
<small className="text-muted">Primary + tertiary side by side on first row, remaining tertiary buttons stacked below.</small>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="accentSurface"
orientation="left"
title="Multiple Actions"
description="This example shows the 5-link layout with stacked tertiary buttons."
media={{ src: placeholderImage, alt: "Multiple buttons" }}
links={[
{ label: "Primary Link", href: "#primary" },
{ label: "Tertiary Link", href: "#tertiary" },
{ label: "Tertiary Link", href: "#link3" },
{ label: "Tertiary Link", href: "#link4" },
{ label: "Tertiary Link", href: "#link5" }
]}
/>
</div>
{/* Alternating Pattern Example */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Alternating Pattern</h2>
<p className="mb-6">
Use alternating orientations and variants to create visual rhythm on feature-heavy pages.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<FeatureSingleTopic
variant="default"
orientation="left"
title="First Feature"
description="Banks, asset managers, PSPs, and fintechs use XRPL to build financial products."
links={[{ label: "Learn More", href: "#learn" }]}
media={{ src: placeholderImage, alt: "First feature" }}
/>
<FeatureSingleTopic
variant="accentSurface"
orientation="right"
title="Second Feature"
description="Build powerful applications on XRPL with comprehensive documentation and tools."
links={[
{ label: "Get Started", href: "#start" },
{ label: "Documentation", href: "#docs" }
]}
media={{ src: placeholderImage, alt: "Second feature" }}
/>
<FeatureSingleTopic
variant="default"
orientation="left"
title="Third Feature"
description="Scale your business with blockchain technology and enterprise-grade solutions."
links={[
{ label: "Contact Sales", href: "#contact" },
{ label: "View Plans", href: "#plans" }
]}
media={{ src: placeholderImage, alt: "Third feature" }}
/>
{/* Design References */}
<PageGrid className="my-5">
<PageGridRow>
<PageGridCol span={12}>
<h2 className="h4 mb-6">Design References</h2>
<div className="d-flex flex-column gap-3">
<div>
<strong>Figma Design (Default):</strong>{' '}
<a href="https://www.figma.com/design/sg6T5EptbN0V2olfCSHzcx/Section-Feature---Single-Topic?node-id=18030-2250&m=dev" target="_blank" rel="noopener noreferrer">
Section Feature - Single Topic (Default Variant)
</a>
</div>
<div>
<strong>Figma Design (AccentSurface):</strong>{' '}
<a href="https://www.figma.com/design/sg6T5EptbN0V2olfCSHzcx/Section-Feature---Single-Topic?node-id=18030-2251&m=dev" target="_blank" rel="noopener noreferrer">
Section Feature - Single Topic (AccentSurface Variant)
</a>
</div>
<div>
<strong>Component Location:</strong>{' '}
<code>shared/patterns/FeatureSingleTopic/</code>
</div>
<div>
<strong>Color Tokens:</strong>{' '}
<code>styles/_colors.scss</code>
</div>
<div>
<strong>Typography:</strong>{' '}
<code>styles/_font.scss</code>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
</div>
);
}

View File

@@ -0,0 +1,313 @@
// FeatureSingleTopic Pattern Styles
// =============================================================================
// A feature section pattern with single topic layout for title and media.
// Supports variants (default, accentSurface) and orientation (left, right).
// Based on Figma: 1280px desktop design with 706px image + content area
// =============================================================================
// Design Tokens
// =============================================================================
// Background colors from _colors.scss
$bds-single-topic-bg: $white; // #FFFFFF (Neutral-white)
$bds-single-topic-title-bg: $gray-200; // #E6EAF0 (Neutral-200) for accentSurface variant
// Text colors from _colors.scss
$bds-single-topic-title-color: $black; // #141414 (Neutral-black)
$bds-single-topic-description-color: $gray-500; // #72777E (Neutral-500)
// Spacing - Desktop (≥992px) - based on Figma 1280px design
$bds-single-topic-desktop-py: 40px; // Vertical padding from Figma
$bds-single-topic-desktop-content-gap: 8px; // Gap between image and content columns
$bds-single-topic-desktop-content-pl: 8px; // Content left padding
$bds-single-topic-desktop-description-gap: 40px; // Gap between description and CTA
$bds-single-topic-desktop-cta-gap: 16px; // Gap between CTA rows
$bds-single-topic-desktop-title-padding: 16px; // Title section padding for accentSurface
$bds-single-topic-desktop-height: 565px; // Fixed height from Figma design
// Spacing - Tablet (576px - 991px)
$bds-single-topic-tablet-py: 32px;
$bds-single-topic-tablet-content-gap: 32px; // Gap between image and content on tablet
$bds-single-topic-tablet-content-min-height: 320px; // Min height for content on tablet
// Spacing - Mobile (<576px)
$bds-single-topic-mobile-py: 24px;
$bds-single-topic-mobile-content-gap: 24px; // Gap between image and content on mobile
$bds-single-topic-mobile-content-min-height: 280px; // Min height for content on mobile
// =============================================================================
// Base Styles
// =============================================================================
.bds-feature-single-topic {
width: 100%;
background-color: $bds-single-topic-bg;
// Container - uses PageGrid with vertical padding
&__container {
padding-top: $bds-single-topic-mobile-py;
padding-bottom: $bds-single-topic-mobile-py;
@include media-breakpoint-up(md) {
padding-top: $bds-single-topic-tablet-py;
padding-bottom: $bds-single-topic-tablet-py;
}
@include media-breakpoint-up(lg) {
padding-top: $bds-single-topic-desktop-py;
padding-bottom: $bds-single-topic-desktop-py;
}
}
// Row - align items stretch so columns match height
// Use row-gap for spacing between image and content on mobile/tablet
&__row {
align-items: stretch;
row-gap: $bds-single-topic-mobile-content-gap;
@include media-breakpoint-up(md) {
row-gap: $bds-single-topic-tablet-content-gap;
}
@include media-breakpoint-up(lg) {
row-gap: 0;
// Fixed height from Figma design
height: $bds-single-topic-desktop-height;
}
}
// Media column
&__media-col {
order: 1;
@include media-breakpoint-up(lg) {
height: 100%;
}
}
// Content column - flex container with left padding on desktop
&__content-col {
order: 2;
display: flex;
flex-direction: column;
@include media-breakpoint-up(lg) {
padding-left: $bds-single-topic-desktop-content-pl;
height: 100%;
}
}
// Media container
&__media {
width: 100%;
overflow: hidden;
}
// Media image - responsive aspect ratios per Figma
&__media-img {
width: 100%;
object-fit: cover;
object-position: center;
// Mobile: 343/193 aspect ratio
aspect-ratio: 343 / 193;
@include media-breakpoint-up(md) {
// Tablet: 16/9 aspect ratio
aspect-ratio: 16 / 9;
}
@include media-breakpoint-up(lg) {
// Desktop: 701/561 aspect ratio (fills the 565px height)
aspect-ratio: 701 / 561;
height: $bds-single-topic-desktop-height;
}
}
// Content wrapper - uses space-between to push title to top, description/CTA to bottom
&__content {
display: flex;
flex-direction: column;
height: 100%;
gap: $bds-single-topic-mobile-content-gap;
// Min height on mobile to prevent squished content
min-height: $bds-single-topic-mobile-content-min-height;
justify-content: space-between;
@include media-breakpoint-up(md) {
gap: $bds-single-topic-tablet-content-gap;
min-height: $bds-single-topic-tablet-content-min-height;
}
@include media-breakpoint-up(lg) {
min-height: auto; // Desktop uses fixed height from row
gap: 0; // space-between handles the gap on desktop
}
}
// Title section - at the top
&__title-section {
flex-shrink: 0;
}
// Title - Heading MD from styles/_font.scss
// Font: Tobias (secondary/monospace), Size: 40px, Weight: 300, Line-height: 46px, Letter-spacing: -1px
&__title {
@include type(heading-md);
color: $bds-single-topic-title-color;
margin: 0;
}
// Description section - at the bottom, contains description + CTA
&__description-section {
display: flex;
flex-direction: column;
gap: $bds-single-topic-mobile-content-gap;
@include media-breakpoint-up(lg) {
gap: $bds-single-topic-desktop-description-gap;
}
}
// Description - Label L from styles/_font.scss
// Font: Booton (primary/sans-serif), Size: 16px, Weight: 300 (light), Line-height: 23.2px
&__description {
@include type(label-l);
color: $bds-single-topic-description-color;
margin: 0;
}
// CTA wrapper - contains buttons
&__cta {
display: flex;
flex-direction: column;
align-items: flex-start; // Prevent buttons from stretching full width
gap: $bds-single-topic-desktop-cta-gap;
}
// CTA row - primary + tertiary side by side
&__cta-row {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
@include media-breakpoint-up(md) {
flex-direction: row;
align-items: center;
gap: 24px; // 24px gap between primary and tertiary per Figma
}
}
}
// =============================================================================
// Variant Modifiers
// =============================================================================
// Default variant - no background on title section
.bds-feature-single-topic--default {
.bds-feature-single-topic__title-section {
background-color: transparent;
padding: 0;
}
}
// AccentSurface variant - gray background on title section
.bds-feature-single-topic--accentSurface {
.bds-feature-single-topic__title-section {
background-color: $bds-single-topic-title-bg;
padding: $bds-single-topic-desktop-title-padding;
@include media-breakpoint-up(lg) {
height: 200px;
}
}
}
// =============================================================================
// Orientation Modifiers
// =============================================================================
// Left orientation (default) - image on left, content on right on desktop
// On mobile/tablet: content appears ABOVE image (column-reverse)
.bds-feature-single-topic--left {
.bds-feature-single-topic__row {
@include media-breakpoint-down(lg) {
flex-direction: column-reverse;
}
}
.bds-feature-single-topic__media-col {
@include media-breakpoint-up(lg) {
order: 1;
}
}
.bds-feature-single-topic__content-col {
@include media-breakpoint-up(lg) {
order: 2;
}
}
}
// Right orientation - image on right, content on left on desktop
// On mobile/tablet: image appears ABOVE content (default column order)
.bds-feature-single-topic--right {
.bds-feature-single-topic__media-col {
@include media-breakpoint-up(lg) {
order: 2;
}
}
.bds-feature-single-topic__content-col {
@include media-breakpoint-up(lg) {
order: 1;
}
}
}
// =============================================================================
// Dark Mode Theme Overrides
// =============================================================================
// Dark mode design tokens from Figma
$bds-single-topic-dark-bg: $black; // #141414 (Neutral/black)
$bds-single-topic-dark-title-bg: $gray-300; // #CAD4DF (Neutral/300default) for accentSurface variant
$bds-single-topic-dark-title-color: $black; // #141414 - title stays black on light background
$bds-single-topic-dark-description-color: $white; // #FFFFFF - description is white in dark mode
html.dark {
.bds-feature-single-topic {
background-color: $bds-single-topic-dark-bg;
&__title {
color: $white; // White title on dark background for default variant
}
&__description {
color: $bds-single-topic-dark-description-color;
}
}
// Default variant in dark mode - title is white on dark background
.bds-feature-single-topic--default {
.bds-feature-single-topic__title-section {
background-color: transparent;
}
.bds-feature-single-topic__title {
color: $white;
}
}
// AccentSurface variant in dark mode - title section has light background
.bds-feature-single-topic--accentSurface {
.bds-feature-single-topic__title-section {
background-color: $bds-single-topic-dark-title-bg;
}
// Title stays black on the light gray background
.bds-feature-single-topic__title {
color: $bds-single-topic-dark-title-color;
}
}
}

View File

@@ -0,0 +1,233 @@
import React from 'react';
import clsx from 'clsx';
import { PageGrid } from '../../components/PageGrid/page-grid';
import { Button } from '../../components/Button/Button';
import { ButtonConfig, validateButtonGroup } from '../ButtonGroup/ButtonGroup';
export interface FeatureSingleTopicLink {
/** Link label text */
label: string;
/** Link URL */
href: string;
}
export interface FeatureSingleTopicProps {
/** Background variant for the title section
* - 'default': No background on title section
* - 'accentSurface': Gray background (#E6EAF0) on title section
*/
variant?: 'default' | 'accentSurface';
/** Content arrangement - controls position of image relative to content
* - 'left': Image on left, content on right
* - 'right': Image on right, content on left
*/
orientation?: 'left' | 'right';
/** Feature title text (heading-md typography) */
title: string;
/** Feature description text (label-l typography) */
description?: string;
/** Array of links (1-5 links supported)
* - 1 link: renders as primary button
* - 2 links: renders as primary + tertiary buttons side by side
* - 3 links: primary + tertiary side by side, secondary button below
* - 4-5 links: primary + tertiary side by side, remaining as tertiary stacked
*/
links?: FeatureSingleTopicLink[];
/** Feature media (image) configuration */
media: {
src: string;
alt: string;
};
/** Additional CSS classes */
className?: string;
}
/**
* FeatureSingleTopic Pattern
*
* A feature section pattern that pairs a title/description with a media element
* in a two-column layout. Supports two variants: default (no title background)
* and accentSurface (gray background on title section).
*
* Layout based on Figma 1280px design:
* - Desktop: Side-by-side with image 7 columns, content 5 columns
* - Mobile/Tablet: Stacked layout (full width)
*/
export const FeatureSingleTopic: React.FC<FeatureSingleTopicProps> = ({
variant = 'default',
orientation = 'left',
title,
description,
links = [],
media,
className,
}) => {
// Button color is always green for this component
const buttonColor = 'green';
const forceColor = false;
// Convert links to ButtonConfig format
const buttonConfigs: ButtonConfig[] = links.map(link => ({
label: link.label,
href: link.href,
forceColor: forceColor,
}));
// Validate buttons (supports up to 5 links)
const buttonValidation = validateButtonGroup(buttonConfigs, 5);
// Log warnings in development mode
if (process.env.NODE_ENV === 'development' && buttonValidation.warnings.length > 0) {
buttonValidation.warnings.forEach(warning => console.warn(warning));
}
// Build root class names
const rootClasses = clsx(
'bds-feature-single-topic',
`bds-feature-single-topic--${variant}`,
`bds-feature-single-topic--${orientation}`,
className
);
// Render title section
const renderTitleSection = () => (
<div className="bds-feature-single-topic__title-section">
<h2 className="bds-feature-single-topic__title">{title}</h2>
</div>
);
// Render CTA buttons based on count
// - 1 link: primary button
// - 2 links: primary + tertiary side by side
// - 3 links: primary + tertiary side by side, secondary button below
// - 4-5 links: primary + tertiary side by side, remaining as tertiary stacked
const renderCTA = () => {
const buttons = buttonValidation.buttons;
if (!buttonValidation.isValid || buttons.length === 0) return null;
// 1 button: just primary
if (buttons.length === 1) {
return (
<div className="bds-feature-single-topic__cta">
<Button variant="primary" color={buttonColor} href={buttons[0].href}>
{buttons[0].label}
</Button>
</div>
);
}
// 2 buttons: primary + tertiary side by side
if (buttons.length === 2) {
return (
<div className="bds-feature-single-topic__cta">
<div className="bds-feature-single-topic__cta-row">
<Button variant="primary" color={buttonColor} href={buttons[0].href}>
{buttons[0].label}
</Button>
<Button variant="tertiary" color={buttonColor} href={buttons[1].href} forceNoPadding>
{buttons[1].label}
</Button>
</div>
</div>
);
}
// 3 buttons: primary + tertiary side by side, secondary button below
if (buttons.length === 3) {
return (
<div className="bds-feature-single-topic__cta">
<div className="bds-feature-single-topic__cta-row">
<Button variant="primary" color={buttonColor} href={buttons[0].href}>
{buttons[0].label}
</Button>
<Button variant="tertiary" color={buttonColor} href={buttons[1].href} forceNoPadding>
{buttons[1].label}
</Button>
</div>
<Button variant="secondary" color={buttonColor} href={buttons[2].href}>
{buttons[2].label}
</Button>
</div>
);
}
// 4-5 buttons: primary + tertiary side by side, remaining as tertiary stacked
const remainingButtons = buttons.slice(2);
return (
<div className="bds-feature-single-topic__cta">
<div className="bds-feature-single-topic__cta-row">
<Button variant="primary" color={buttonColor} href={buttons[0].href}>
{buttons[0].label}
</Button>
<Button variant="tertiary" color={buttonColor} href={buttons[1].href} forceNoPadding>
{buttons[1].label}
</Button>
</div>
{remainingButtons.map((button, index) => (
<Button
key={index}
variant="tertiary"
color={buttonColor}
href={button.href}
forceNoPadding
>
{button.label}
</Button>
))}
</div>
);
};
// Render description and CTA section
const renderDescriptionSection = () => (
<div className="bds-feature-single-topic__description-section">
{description && (
<p className="bds-feature-single-topic__description">{description}</p>
)}
{renderCTA()}
</div>
);
// Render content section (title at top, description/CTA at bottom)
const renderContent = () => (
<div className="bds-feature-single-topic__content">
{renderTitleSection()}
{renderDescriptionSection()}
</div>
);
// Render media section
const renderMedia = () => (
<div className="bds-feature-single-topic__media">
<img
src={media.src}
alt={media.alt}
className="bds-feature-single-topic__media-img"
/>
</div>
);
return (
<section className={rootClasses}>
<PageGrid className="bds-feature-single-topic__container" containerType="standard">
<PageGrid.Row className="bds-feature-single-topic__row">
<PageGrid.Col
span={{ base: 4, md: 8, lg: 7 }}
className="bds-feature-single-topic__media-col"
>
{renderMedia()}
</PageGrid.Col>
<PageGrid.Col
span={{ base: 4, md: 8, lg: 5 }}
className="bds-feature-single-topic__content-col"
>
{renderContent()}
</PageGrid.Col>
</PageGrid.Row>
</PageGrid>
</section>
);
};
export default FeatureSingleTopic;

View File

@@ -0,0 +1,175 @@
# FeatureSingleTopic Pattern
A feature section pattern that pairs a title/description with a media element in a two-column layout. Supports two variants (default, accentSurface) and two orientations (left, right).
## Features
- Responsive two-column layout (image + content) that stacks on smaller screens
- Two background variants: default (no background) and accentSurface (gray title background)
- Two orientations: left (image left) and right (image right)
- Flexible button layout supporting 1-5 links with automatic variant assignment
- Responsive image aspect ratios per Figma design
- Full dark mode support
- Uses PageGrid for consistent spacing
## Basic Usage
```tsx
import { FeatureSingleTopic } from 'shared/patterns/FeatureSingleTopic';
<FeatureSingleTopic
variant="default"
orientation="left"
title="Developer Spotlight"
description="Are you building a peer-to-peer payments solution?"
media={{
src: "/img/feature-image.png",
alt: "Feature image"
}}
links={[
{ label: "Get Started", href: "/start" },
{ label: "Learn More", href: "/learn" }
]}
/>
```
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `variant` | `'default' \| 'accentSurface'` | `'default'` | Background variant for title section |
| `orientation` | `'left' \| 'right'` | `'left'` | Image position relative to content |
| `title` | `string` | *required* | Feature title (heading-md typography) |
| `description` | `string` | - | Feature description (label-l typography) |
| `links` | `FeatureSingleTopicLink[]` | `[]` | Array of CTA links (1-5 supported) |
| `media` | `{ src: string; alt: string }` | *required* | Image configuration |
| `className` | `string` | - | Additional CSS classes |
### FeatureSingleTopicLink
```tsx
interface FeatureSingleTopicLink {
label: string;
href: string;
}
```
## Button Behavior
The component automatically determines button variants based on count:
| Count | Layout |
|-------|--------|
| 1 link | Primary button |
| 2 links | Primary + Tertiary side by side |
| 3 links | Primary + Tertiary side by side, Secondary button below |
| 4-5 links | Primary + Tertiary side by side, remaining Tertiary stacked |
## Variants
### Default
No background on the title section. Clean, minimal look.
```tsx
<FeatureSingleTopic variant="default" ... />
```
### AccentSurface
Gray background (#E6EAF0 light / #CAD4DF dark) on the title section.
```tsx
<FeatureSingleTopic variant="accentSurface" ... />
```
## Orientation
### Left (default)
Image on left, content on right on desktop. On mobile/tablet, content appears above image.
### Right
Image on right, content on left on desktop. On mobile/tablet, image appears above content.
## Responsive Behavior
### Desktop (≥992px)
- Side-by-side layout: 7-column image, 5-column content
- Fixed height: 565px
- Image aspect ratio: 701/561
### Tablet (768px - 991px)
- Stacked layout with 32px gap between sections
- Image aspect ratio: 16/9
- Content min-height: 320px
### Mobile (<768px)
- Stacked layout with 24px gap between sections
- Image aspect ratio: 343/193
- Content min-height: 280px
## CSS Classes
```
.bds-feature-single-topic // Section container
.bds-feature-single-topic--default // Default variant modifier
.bds-feature-single-topic--accentSurface // AccentSurface variant modifier
.bds-feature-single-topic--left // Left orientation modifier
.bds-feature-single-topic--right // Right orientation modifier
.bds-feature-single-topic__container // PageGrid container
.bds-feature-single-topic__row // PageGrid row
.bds-feature-single-topic__media-col // Media column
.bds-feature-single-topic__content-col // Content column
.bds-feature-single-topic__media // Media wrapper
.bds-feature-single-topic__media-img // Image element
.bds-feature-single-topic__content // Content wrapper
.bds-feature-single-topic__title-section // Title section
.bds-feature-single-topic__title // Title element
.bds-feature-single-topic__description-section // Description + CTA wrapper
.bds-feature-single-topic__description // Description element
.bds-feature-single-topic__cta // CTA buttons wrapper
.bds-feature-single-topic__cta-row // Primary + Tertiary row
```
## Typography Tokens
- **Title**: Uses `heading-md` type token (Tobias Light font)
- Desktop: 40px / 46px line-height / -1px letter-spacing
- **Description**: Uses `label-l` type token (Booton Light font)
- Desktop: 16px / 23.2px line-height
## Dark Mode
Full dark mode support with `html.dark` selector:
- **Section background**: #141414 (black)
- **Title (default variant)**: #FFFFFF (white)
- **Title (accentSurface)**: #141414 (black) on #CAD4DF background
- **Description**: #FFFFFF (white)
## Files
- `FeatureSingleTopic.tsx` - Main pattern component
- `FeatureSingleTopic.scss` - Styles with responsive breakpoints
- `index.ts` - Barrel exports
- `README.md` - This documentation
## Design References
- **Figma Design**: [Section Feature - Single Topic](https://www.figma.com/design/sg6T5EptbN0V2olfCSHzcx/Section-Feature---Single-Topic?node-id=18030-2250&m=dev)
- **Showcase Page**: `/about/feature-single-topic-showcase`
- **Component Location**: `shared/patterns/FeatureSingleTopic/`
## Related Components
- **Button**: Used for CTA buttons
- **PageGrid**: Used for responsive grid layout
## Version History
- **February 2026**: Initial implementation
- Two variants (default, accentSurface)
- Two orientations (left, right)
- Responsive image aspect ratios
- 1-5 link support with automatic button variant assignment
- Full dark mode support

View File

@@ -0,0 +1,3 @@
export { FeatureSingleTopic, type FeatureSingleTopicProps, type FeatureSingleTopicLink } from './FeatureSingleTopic';
export { default } from './FeatureSingleTopic';

View File

@@ -21457,6 +21457,254 @@ html.dark .bds-feature-two-column--green {
}
}
.bds-feature-single-topic {
width: 100%;
background-color: #FFFFFF;
}
.bds-feature-single-topic__container {
padding-top: 24px;
padding-bottom: 24px;
}
@media (min-width: 576px) {
.bds-feature-single-topic__container {
padding-top: 32px;
padding-bottom: 32px;
}
}
@media (min-width: 992px) {
.bds-feature-single-topic__container {
padding-top: 40px;
padding-bottom: 40px;
}
}
.bds-feature-single-topic__row {
align-items: stretch;
row-gap: 24px;
}
@media (min-width: 576px) {
.bds-feature-single-topic__row {
row-gap: 32px;
}
}
@media (min-width: 992px) {
.bds-feature-single-topic__row {
row-gap: 0;
height: 565px;
}
}
.bds-feature-single-topic__media-col {
order: 1;
}
@media (min-width: 992px) {
.bds-feature-single-topic__media-col {
height: 100%;
}
}
.bds-feature-single-topic__content-col {
order: 2;
display: flex;
flex-direction: column;
}
@media (min-width: 992px) {
.bds-feature-single-topic__content-col {
padding-left: 8px;
height: 100%;
}
}
.bds-feature-single-topic__media {
width: 100%;
overflow: hidden;
}
.bds-feature-single-topic__media-img {
width: 100%;
object-fit: cover;
object-position: center;
aspect-ratio: 343/193;
}
@media (min-width: 576px) {
.bds-feature-single-topic__media-img {
aspect-ratio: 16/9;
}
}
@media (min-width: 992px) {
.bds-feature-single-topic__media-img {
aspect-ratio: 701/561;
height: 565px;
}
}
.bds-feature-single-topic__content {
display: flex;
flex-direction: column;
height: 100%;
gap: 24px;
min-height: 280px;
justify-content: space-between;
}
@media (min-width: 576px) {
.bds-feature-single-topic__content {
gap: 32px;
min-height: 320px;
}
}
@media (min-width: 992px) {
.bds-feature-single-topic__content {
min-height: auto;
gap: 0;
}
}
.bds-feature-single-topic__title-section {
flex-shrink: 0;
}
.bds-feature-single-topic__title {
font-family: "Tobias", "Noto Serif", monospace;
font-weight: 300;
font-size: 32px;
line-height: 40px;
letter-spacing: 0px;
margin-bottom: 16px;
}
@media (min-width: 576px) {
.bds-feature-single-topic__title {
font-size: 36px;
line-height: 45px;
letter-spacing: -0.5px;
margin-bottom: 16px;
}
}
@media (min-width: 992px) {
.bds-feature-single-topic__title {
font-size: 40px;
line-height: 46px;
letter-spacing: -1px;
margin-bottom: 16px;
}
}
.bds-feature-single-topic__title {
color: #141414;
margin: 0;
}
.bds-feature-single-topic__description-section {
display: flex;
flex-direction: column;
gap: 24px;
}
@media (min-width: 992px) {
.bds-feature-single-topic__description-section {
gap: 40px;
}
}
.bds-feature-single-topic__description {
font-family: "Booton", "Noto Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: 300;
font-size: 14px;
line-height: 20.1px;
letter-spacing: 0px;
margin-bottom: 16px;
}
@media (min-width: 576px) {
.bds-feature-single-topic__description {
font-size: 14px;
line-height: 20.1px;
letter-spacing: 0px;
margin-bottom: 16px;
}
}
@media (min-width: 992px) {
.bds-feature-single-topic__description {
font-size: 16px;
line-height: 23.2px;
letter-spacing: 0px;
margin-bottom: 16px;
}
}
.bds-feature-single-topic__description {
color: #72777E;
margin: 0;
}
.bds-feature-single-topic__cta {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.bds-feature-single-topic__cta-row {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
@media (min-width: 576px) {
.bds-feature-single-topic__cta-row {
flex-direction: row;
align-items: center;
gap: 24px;
}
}
.bds-feature-single-topic--default .bds-feature-single-topic__title-section {
background-color: transparent;
padding: 0;
}
.bds-feature-single-topic--accentSurface .bds-feature-single-topic__title-section {
background-color: #E6EAF0;
padding: 16px;
}
@media (min-width: 992px) {
.bds-feature-single-topic--accentSurface .bds-feature-single-topic__title-section {
height: 200px;
}
}
@media (max-width: 991.98px) {
.bds-feature-single-topic--left .bds-feature-single-topic__row {
flex-direction: column-reverse;
}
}
@media (min-width: 992px) {
.bds-feature-single-topic--left .bds-feature-single-topic__media-col {
order: 1;
}
}
@media (min-width: 992px) {
.bds-feature-single-topic--left .bds-feature-single-topic__content-col {
order: 2;
}
}
@media (min-width: 992px) {
.bds-feature-single-topic--right .bds-feature-single-topic__media-col {
order: 2;
}
}
@media (min-width: 992px) {
.bds-feature-single-topic--right .bds-feature-single-topic__content-col {
order: 1;
}
}
html.dark .bds-feature-single-topic {
background-color: #141414;
}
html.dark .bds-feature-single-topic__title {
color: #FFFFFF;
}
html.dark .bds-feature-single-topic__description {
color: #FFFFFF;
}
html.dark .bds-feature-single-topic--default .bds-feature-single-topic__title-section {
background-color: transparent;
}
html.dark .bds-feature-single-topic--default .bds-feature-single-topic__title {
color: #FFFFFF;
}
html.dark .bds-feature-single-topic--accentSurface .bds-feature-single-topic__title-section {
background-color: #CAD4DF;
}
html.dark .bds-feature-single-topic--accentSurface .bds-feature-single-topic__title {
color: #141414;
}
.bds-cards-two-column {
width: 100%;
background-color: #FFFFFF;

View File

@@ -112,6 +112,7 @@ $line-height-base: 1.5;
@import "../shared/patterns/CardsFeatured/CardsFeatured.scss";
@import "../shared/patterns/LogoRectangleGrid/LogoRectangleGrid.scss";
@import "../shared/patterns/FeatureTwoColumn/FeatureTwoColumn.scss";
@import "../shared/patterns/FeatureSingleTopic/FeatureSingleTopic.scss";
@import "../shared/patterns/CardsTwoColumn/CardsTwoColumn.scss";
@import "../shared/patterns/StandardCardGroupSection/_standard-card-group-section.scss";
@import "_code-tabs.scss";