Compare commits

...

1 Commits

Author SHA1 Message Date
akcodez
1aa2092bb0 stashing away work 2026-01-21 10:54:52 -08:00
7 changed files with 795 additions and 0 deletions

View File

@@ -0,0 +1,257 @@
import { PageGrid, PageGridRow, PageGridCol } from "shared/components/PageGrid/page-grid";
import { CardsIconGrid } from "shared/patterns/CardsIconGrid";
import { Divider } from "shared/components/Divider";
export const frontmatter = {
seo: {
title: 'CardsIconGrid Pattern Showcase',
description: "A comprehensive showcase of the CardsIconGrid pattern component demonstrating light and dark mode variations in the XRPL.org Design System.",
}
};
// Sample icon SVG for demonstration
const SAMPLE_ICON = "/img/icons/card-icon-placeholder.svg";
// Sample cards data - Green variant
const greenCards = [
{
icon: SAMPLE_ICON,
iconAlt: "Digital Wallets icon",
label: "Digital Wallets",
href: "#wallets",
variant: "green" as const,
},
{
icon: SAMPLE_ICON,
iconAlt: "B2B Payment Rails icon",
label: "B2B Payment Rails",
href: "#payments",
variant: "green" as const,
},
{
icon: SAMPLE_ICON,
iconAlt: "Compliance-First Payments icon",
label: "Compliance-First Payments",
href: "#compliance",
variant: "green" as const,
},
{
icon: SAMPLE_ICON,
iconAlt: "Merchant Settlement icon",
label: "Merchant Settlement",
href: "#settlement",
variant: "green" as const,
},
{
icon: SAMPLE_ICON,
iconAlt: "Cross-Border Payments icon",
label: "Cross-Border Payments",
href: "#cross-border",
variant: "green" as const,
},
{
icon: SAMPLE_ICON,
iconAlt: "Treasury Management icon",
label: "Treasury Management",
href: "#treasury",
variant: "green" as const,
},
];
// Sample cards data - Neutral variant
const neutralCards = [
{
icon: SAMPLE_ICON,
iconAlt: "Documentation icon",
label: "Documentation",
href: "#docs",
variant: "neutral" as const,
},
{
icon: SAMPLE_ICON,
iconAlt: "Tutorials icon",
label: "Tutorials",
href: "#tutorials",
variant: "neutral" as const,
},
{
icon: SAMPLE_ICON,
iconAlt: "API Reference icon",
label: "API Reference",
href: "#api",
variant: "neutral" as const,
},
];
export default function CardsIconGridShowcase() {
return (
<div className="landing">
<div className="overflow-hidden">
{/* Hero Section */}
<section className="py-26 text-center">
<div className="col-lg-8 mx-auto">
<h6 className="eyebrow mb-3">Pattern Showcase</h6>
<h1 className="mb-4">CardsIconGrid Pattern</h1>
<p className="longform">
A section pattern that displays a heading, optional description, and a responsive grid
of CardIcon components. Follows the "CardIconGrid" design from Figma.
</p>
</div>
</section>
{/* Design Tokens Reference */}
<PageGrid className="py-10">
<PageGridRow>
<PageGridCol span={{ base: 4, md: 8, lg: 12 }}>
<h2 className="h4 mb-6">Design Specifications</h2>
<div className="d-flex flex-wrap gap-8">
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Typography</h6>
<ul className="mb-0">
<li><strong>Heading:</strong> heading-md (Tobias Light)</li>
<li><strong>Description:</strong> body-l (Booton Light)</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Grid Layout</h6>
<ul className="mb-0">
<li><strong>Mobile:</strong> 1 column</li>
<li><strong>Tablet:</strong> 2 columns</li>
<li><strong>Desktop:</strong> 3 columns</li>
</ul>
</div>
<div style={{ flex: '1 1 250px' }}>
<h6 className="mb-3">Colors</h6>
<ul className="mb-0">
<li><strong>Light Mode:</strong> $black (#141414)</li>
<li><strong>Dark Mode:</strong> $white (#FFFFFF)</li>
</ul>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<Divider />
{/* 6 Cards Example - Green Variant */}
<section>
<CardsIconGrid
heading="Unlock new business models with embedded payments"
description="Streamline development and build powerful RWA tokenization solutions with XRP Ledger's comprehensive developer toolset."
cards={greenCards}
/>
</section>
<Divider />
{/* 3 Cards Example - Neutral Variant */}
<section>
<CardsIconGrid
heading="Developer Resources"
description="Everything you need to start building on the XRP Ledger."
cards={neutralCards}
/>
</section>
<Divider />
{/* Without Description */}
<PageGrid className="py-10">
<PageGridRow>
<PageGridCol span={{ base: 4, md: 8, lg: 12 }}>
<h2 className="h4 mb-4">Without Description</h2>
<p className="mb-0">
The description prop is optional. When omitted, only the heading appears above the cards.
</p>
</PageGridCol>
</PageGridRow>
</PageGrid>
<section>
<CardsIconGrid
heading="Funding & Support Programs"
cards={greenCards.slice(0, 3)}
/>
</section>
<Divider />
{/* Code Examples */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={{ base: 4, md: 8, lg: 10 }}>
<h2 className="h4 mb-6">Code Examples</h2>
<h5 className="mb-4">Basic Usage</h5>
<div className="p-4 mb-8 br-4" style={{ backgroundColor: '#1a1a1a', fontFamily: 'monospace', fontSize: '14px' }}>
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', color: '#f8f8f2' }}>{`import { CardsIconGrid } from 'shared/patterns/CardsIconGrid';
<CardsIconGrid
heading="Unlock new business models"
description="Build powerful solutions with XRPL."
cards={[
{
icon: "/icons/wallet.svg",
label: "Digital Wallets",
href: "/docs/wallets",
variant: "green"
},
{
icon: "/icons/payments.svg",
label: "B2B Payment Rails",
href: "/docs/payments",
variant: "green"
},
{
icon: "/icons/compliance.svg",
label: "Compliance-First Payments",
href: "/docs/compliance",
variant: "green"
}
]}
/>`}</pre>
</div>
<h5 className="mb-4">Without Description</h5>
<div className="p-4 mb-8 br-4" style={{ backgroundColor: '#1a1a1a', fontFamily: 'monospace', fontSize: '14px' }}>
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', color: '#f8f8f2' }}>{`<CardsIconGrid
heading="Developer Resources"
cards={[
{ icon: "/icons/docs.svg", label: "Documentation", href: "/docs", variant: "neutral" },
{ icon: "/icons/tutorials.svg", label: "Tutorials", href: "/tutorials", variant: "neutral" },
{ icon: "/icons/api.svg", label: "API Reference", href: "/api", variant: "neutral" }
]}
/>`}</pre>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
<Divider />
{/* Design References */}
<PageGrid className="py-26">
<PageGridRow>
<PageGridCol span={{ base: 4, md: 8, lg: 12 }}>
<h2 className="h4 mb-6">Design References</h2>
<div className="d-flex flex-column gap-3">
<div>
<strong>Figma:</strong>{' '}
<a href="https://www.figma.com/design/Ojj6UpFBw3HMb0QqRaKxAU/Section-Cards---Icon?node-id=30071-3082&m=dev" target="_blank" rel="noopener noreferrer">
Section Cards - Icon Grid
</a>
</div>
<div>
<strong>Documentation:</strong>{' '}
<code>shared/patterns/CardsIconGrid/CardsIconGrid.md</code>
</div>
</div>
</PageGridCol>
</PageGridRow>
</PageGrid>
</div>
</div>
);
}

View File

@@ -0,0 +1,141 @@
# CardsIconGrid Pattern
A section pattern that displays a heading, optional description, and a responsive grid of `CardIcon` components. Follows the "CardIconGrid" design from Figma.
## Features
- Responsive grid layout (1 column mobile, 2 tablet, 3 desktop)
- Heading with `heading-md` typography (Tobias Light)
- Optional description with `body-l` typography (Booton Light)
- Proper spacing using `PageGrid` for container and alignment
- Full dark mode support
- Uses the existing `CardIcon` component for cards
## Usage
```tsx
import { CardsIconGrid } from 'shared/patterns/CardsIconGrid';
<CardsIconGrid
heading="Unlock new business models with embedded payments"
description="Streamline development and build powerful RWA tokenization solutions with XRP Ledger's comprehensive developer toolset."
cards={[
{
icon: "/icons/wallet.svg",
label: "Digital Wallets",
href: "/docs/wallets",
variant: "green"
},
{
icon: "/icons/payments.svg",
label: "B2B Payment Rails",
href: "/docs/payments",
variant: "green"
},
{
icon: "/icons/compliance.svg",
label: "Compliance-First Payments",
href: "/docs/compliance",
variant: "green"
}
]}
/>
```
## Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `heading` | `React.ReactNode` | Yes | Section heading text |
| `description` | `React.ReactNode` | No | Section description text (optional) |
| `cards` | `CardsIconGridCardConfig[]` | Yes | Array of card configurations (uses `CardIconProps`) |
| `className` | `string` | No | Additional CSS class names |
### CardsIconGridCardConfig
Each card in the `cards` array accepts all props from `CardIconProps`:
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `icon` | `string` | Yes | Icon image source (URL or path) |
| `iconAlt` | `string` | No | Alt text for the icon image |
| `label` | `string` | Yes | Card label text |
| `variant` | `'neutral' \| 'green'` | No | Color variant (default: 'neutral') |
| `href` | `string` | No | Link destination - renders as `<a>` |
| `onClick` | `() => void` | No | Click handler - renders as `<button>` |
| `disabled` | `boolean` | No | Disabled state |
## Responsive Behavior
| Breakpoint | Grid Columns | Vertical Padding |
|------------|--------------|------------------|
| Mobile (< 768px) | 1 column | 48px |
| Tablet (768px - 1199px) | 2 columns | 64px |
| Desktop (≥ 1200px) | 3 columns | 80px |
## Design Tokens
### Colors
| Mode | Element | Color |
|------|---------|-------|
| Light | Heading | `$black` (#141414) |
| Light | Description | `$black` (#141414) |
| Dark | Heading | `$white` (#FFFFFF) |
| Dark | Description | `$white` (#FFFFFF) |
### Spacing
- Header gap (heading to description): 8px mobile/tablet, 16px desktop
- Section gap (header to cards): 24px mobile, 32px tablet, 40px desktop
- Cards column gap: 24px mobile, 8px tablet/desktop
- Cards row gap: 24px mobile, 32px tablet, 40px desktop
## CSS Classes
| Class | Description |
|-------|-------------|
| `.bds-cards-icon-grid` | Base section container |
| `.bds-cards-icon-grid__header` | Header wrapper for heading and description |
| `.bds-cards-icon-grid__heading` | Section heading (uses `.h-md`) |
| `.bds-cards-icon-grid__description` | Section description (uses `.body-l`) |
| `.bds-cards-icon-grid__cards` | Cards grid container |
| `.bds-cards-icon-grid__card-wrapper` | Individual card wrapper |
## Card Variants
The pattern supports both CardIcon variants:
### Green Variant
```tsx
cards={[
{ icon: "/icons/wallet.svg", label: "Digital Wallets", href: "/wallets", variant: "green" }
]}
```
### Neutral Variant
```tsx
cards={[
{ icon: "/icons/docs.svg", label: "Documentation", href: "/docs", variant: "neutral" }
]}
```
## Without Description
The description prop is optional:
```tsx
<CardsIconGrid
heading="Funding & Support Programs"
cards={[...]}
/>
```
## Figma Reference
- Design: [Section Cards - Icon Grid](https://www.figma.com/design/Ojj6UpFBw3HMb0QqRaKxAU/Section-Cards---Icon?node-id=30071-3082&m=dev)
## Showcase
View the pattern showcase at: `/about/cards-icon-grid-showcase`

View File

@@ -0,0 +1,180 @@
// BDS CardsIconGrid Pattern Styles
// Brand Design System - Section with heading, optional description, and grid of CardIcon components
//
// Naming Convention: BEM with 'bds' namespace
// .bds-cards-icon-grid - Base section container
// .bds-cards-icon-grid__header - Header wrapper for heading and description
// .bds-cards-icon-grid__heading - Section heading (uses .h-md)
// .bds-cards-icon-grid__description - Section description (uses .body-l)
// .bds-cards-icon-grid__cards - Cards grid container
// .bds-cards-icon-grid__card-wrapper - Individual card wrapper
//
// Design tokens from Figma:
// Light Mode:
// - Heading: Neutral Black (#141414) → $black
// - Description: Neutral Black (#141414) → $black
//
// Dark Mode:
// - Heading: Neutral White (#FFFFFF) → $white
// - Description: Neutral White (#FFFFFF) → $white
//
// - Header content max-width: 808px (approximately 8 columns at desktop)
// - Gap between heading and description: 8px mobile/tablet, 16px desktop
// - Gap between cards: 8px (matches $bds-grid-gutter)
// =============================================================================
// Design Tokens (from Figma)
// =============================================================================
$bds-grid-gutter: 8px;
// Spacing - Header gap (between heading and description)
$bds-cards-icon-grid-header-gap-sm: 8px; // Mobile: 8px
$bds-cards-icon-grid-header-gap-md: 8px; // Tablet: 8px
$bds-cards-icon-grid-header-gap-lg: 16px; // Desktop: 16px
// Spacing - Section gap (between header and cards)
$bds-cards-icon-grid-section-gap-sm: 24px; // Mobile
$bds-cards-icon-grid-section-gap-md: 32px; // Tablet
$bds-cards-icon-grid-section-gap-lg: 40px; // Desktop
// Spacing - Cards gap
$bds-cards-icon-grid-cards-gap-sm: 24px; // Mobile: 24px vertical stack
$bds-cards-icon-grid-cards-gap-md: 8px; // Tablet: 8px
$bds-cards-icon-grid-cards-gap-lg: 8px; // Desktop: 8px
// Spacing - Row gap (between rows of cards)
$bds-cards-icon-grid-row-gap-sm: 24px; // Mobile
$bds-cards-icon-grid-row-gap-md: 32px; // Tablet
$bds-cards-icon-grid-row-gap-lg: 40px; // Desktop
// Spacing - Section padding (vertical)
$bds-cards-icon-grid-padding-y-sm: 48px; // Mobile
$bds-cards-icon-grid-padding-y-md: 64px; // Tablet
$bds-cards-icon-grid-padding-y-lg: 80px; // Desktop
// Colors - Light Mode (default)
$bds-cards-icon-grid-heading-color: $black; // #141414 - Neutral black
$bds-cards-icon-grid-description-color: $black; // #141414 - Neutral black
// Colors - Dark Mode
$bds-cards-icon-grid-heading-color-dark: $white; // #FFFFFF - Neutral white
$bds-cards-icon-grid-description-color-dark: $white; // #FFFFFF - Neutral white
// =============================================================================
// Section Container
// =============================================================================
.bds-cards-icon-grid {
width: 100%;
padding-top: $bds-cards-icon-grid-padding-y-sm;
padding-bottom: $bds-cards-icon-grid-padding-y-sm;
@include media-breakpoint-up(md) {
padding-top: $bds-cards-icon-grid-padding-y-md;
padding-bottom: $bds-cards-icon-grid-padding-y-md;
}
@include media-breakpoint-up(lg) {
padding-top: $bds-cards-icon-grid-padding-y-lg;
padding-bottom: $bds-cards-icon-grid-padding-y-lg;
}
}
// =============================================================================
// Header Section
// =============================================================================
.bds-cards-icon-grid__header {
display: flex;
flex-direction: column;
gap: $bds-cards-icon-grid-header-gap-sm;
@include media-breakpoint-up(md) {
gap: $bds-cards-icon-grid-header-gap-md;
}
@include media-breakpoint-up(lg) {
gap: $bds-cards-icon-grid-header-gap-lg;
}
}
.bds-cards-icon-grid__heading {
margin: 0;
// Typography handled by .h-md class from _font.scss
}
.bds-cards-icon-grid__description {
margin: 0;
// Typography handled by .body-l class from _font.scss
}
// =============================================================================
// Cards Grid
// =============================================================================
.bds-cards-icon-grid__cards {
display: grid;
grid-template-columns: 1fr;
gap: $bds-cards-icon-grid-cards-gap-sm;
width: 100%;
margin-top: $bds-cards-icon-grid-section-gap-sm;
@include media-breakpoint-up(md) {
grid-template-columns: repeat(2, 1fr);
column-gap: $bds-cards-icon-grid-cards-gap-md;
row-gap: $bds-cards-icon-grid-row-gap-md;
margin-top: $bds-cards-icon-grid-section-gap-md;
}
@include media-breakpoint-up(lg) {
grid-template-columns: repeat(3, 1fr);
column-gap: $bds-cards-icon-grid-cards-gap-lg;
row-gap: $bds-cards-icon-grid-row-gap-lg;
margin-top: $bds-cards-icon-grid-section-gap-lg;
}
}
.bds-cards-icon-grid__card-wrapper {
display: flex;
min-width: 0;
width: 100%;
// Ensure CardIcon fills the wrapper
.bds-card-icon {
width: 100%;
}
}
// =============================================================================
// Light Mode Styles
// =============================================================================
html.light {
.bds-cards-icon-grid {
background-color: $white;
}
.bds-cards-icon-grid__heading {
color: $bds-cards-icon-grid-heading-color;
}
.bds-cards-icon-grid__description {
color: $bds-cards-icon-grid-description-color;
}
}
// =============================================================================
// Dark Mode Styles
// =============================================================================
html.dark {
.bds-cards-icon-grid__heading {
color: $bds-cards-icon-grid-heading-color-dark;
}
.bds-cards-icon-grid__description {
color: $bds-cards-icon-grid-description-color-dark;
}
}

View File

@@ -0,0 +1,121 @@
import React from 'react';
import clsx from 'clsx';
import { CardIcon, CardIconProps } from '../../components/CardIcon';
import { PageGrid } from '../../components/PageGrid/page-grid';
/**
* Configuration for a single card in the CardsIconGrid pattern
*/
export type CardsIconGridCardConfig = CardIconProps;
/**
* Props for the CardsIconGrid pattern component
*/
export interface CardsIconGridProps extends React.ComponentPropsWithoutRef<'section'> {
/** Section heading text */
heading: React.ReactNode;
/** Section description text (optional) */
description?: React.ReactNode;
/** Array of card configurations (uses CardIconProps) */
cards: readonly CardsIconGridCardConfig[];
}
/**
* Generate a unique key for a card based on its props
*/
const getCardKey = (card: CardsIconGridCardConfig, index: number): string => {
if (card.href) return `card-${card.href}-${index}`;
if (card.label) return `card-${card.label.toString().slice(0, 20)}-${index}`;
return `card-${index}`;
};
/**
* CardsIconGrid Pattern Component
*
* A section pattern that displays a heading, optional description, and a responsive grid
* of CardIcon components. Follows the "CardIconGrid" pattern from Figma.
*
* Features:
* - Responsive grid layout (1 column mobile, 2 tablet, 3 desktop)
* - Heading with `heading-md` typography (Tobias Light)
* - Optional description with `body-l` typography (Booton Light)
* - Proper spacing using PageGrid for container and alignment
* - Full dark mode support
*
* @example
* ```tsx
* <CardsIconGrid
* heading="Unlock new business models with embedded payments"
* description="Streamline development and build powerful solutions."
* cards={[
* {
* icon: "/icons/wallet.svg",
* label: "Digital Wallets",
* href: "/docs/wallets",
* variant: "green"
* },
* {
* icon: "/icons/payments.svg",
* label: "B2B Payment Rails",
* href: "/docs/payments",
* variant: "green"
* }
* ]}
* />
* ```
*/
export const CardsIconGrid = React.forwardRef<HTMLElement, CardsIconGridProps>(
function CardsIconGrid(
{ className, heading, description, cards, ...rest },
ref
) {
return (
<section
ref={ref}
className={clsx('bds-cards-icon-grid', className)}
{...rest}
>
<PageGrid>
{/* Header content row */}
<PageGrid.Row>
<PageGrid.Col
span={{
base: 'fill',
md: 6,
lg: 8,
}}
>
<div className="bds-cards-icon-grid__header">
<h2 className="bds-cards-icon-grid__heading h-md">{heading}</h2>
{description && (
<p className="bds-cards-icon-grid__description body-l">{description}</p>
)}
</div>
</PageGrid.Col>
</PageGrid.Row>
{/* Cards grid row */}
<PageGrid.Row>
<PageGrid.Col span="fill">
<div className="bds-cards-icon-grid__cards">
{cards.map((card, index) => (
<div
key={getCardKey(card, index)}
className="bds-cards-icon-grid__card-wrapper"
>
<CardIcon {...card} />
</div>
))}
</div>
</PageGrid.Col>
</PageGrid.Row>
</PageGrid>
</section>
);
}
);
CardsIconGrid.displayName = 'CardsIconGrid';
export default CardsIconGrid;

View File

@@ -0,0 +1,3 @@
export { CardsIconGrid, type CardsIconGridProps, type CardsIconGridCardConfig } from './CardsIconGrid';
export { default } from './CardsIconGrid';

View File

@@ -19938,6 +19938,98 @@ html.light .bds-callout-media-banner--image-text-black .bds-callout-media-banner
color: #141414 !important;
}
.bds-cards-icon-grid {
width: 100%;
padding-top: 48px;
padding-bottom: 48px;
}
@media (min-width: 576px) {
.bds-cards-icon-grid {
padding-top: 64px;
padding-bottom: 64px;
}
}
@media (min-width: 992px) {
.bds-cards-icon-grid {
padding-top: 80px;
padding-bottom: 80px;
}
}
.bds-cards-icon-grid__header {
display: flex;
flex-direction: column;
gap: 8px;
}
@media (min-width: 576px) {
.bds-cards-icon-grid__header {
gap: 8px;
}
}
@media (min-width: 992px) {
.bds-cards-icon-grid__header {
gap: 16px;
}
}
.bds-cards-icon-grid__heading {
margin: 0;
}
.bds-cards-icon-grid__description {
margin: 0;
}
.bds-cards-icon-grid__cards {
display: grid;
grid-template-columns: 1fr;
gap: 24px;
width: 100%;
margin-top: 24px;
}
@media (min-width: 576px) {
.bds-cards-icon-grid__cards {
grid-template-columns: repeat(2, 1fr);
column-gap: 8px;
row-gap: 32px;
margin-top: 32px;
}
}
@media (min-width: 992px) {
.bds-cards-icon-grid__cards {
grid-template-columns: repeat(3, 1fr);
column-gap: 8px;
row-gap: 40px;
margin-top: 40px;
}
}
.bds-cards-icon-grid__card-wrapper {
display: flex;
min-width: 0;
width: 100%;
}
.bds-cards-icon-grid__card-wrapper .bds-card-icon {
width: 100%;
}
html.light .bds-cards-icon-grid {
background-color: #FFFFFF;
}
html.light .bds-cards-icon-grid__heading {
color: #141414;
}
html.light .bds-cards-icon-grid__description {
color: #141414;
}
html.dark .bds-cards-icon-grid__heading {
color: #FFFFFF;
}
html.dark .bds-cards-icon-grid__description {
color: #FFFFFF;
}
pre {
color: #FFFFFF;
background-color: #232325;

View File

@@ -101,6 +101,7 @@ $line-height-base: 1.5;
@import "../shared/patterns/HeaderHeroSplitMedia/HeaderHeroSplitMedia.scss";
@import "../shared/patterns/CardsFeatured/CardsFeatured.scss";
@import "../shared/patterns/CalloutMediaBanner/CalloutMediaBanner.scss";
@import "../shared/patterns/CardsIconGrid/CardsIconGrid.scss";
@import "_code-tabs.scss";
@import "_code-walkthrough.scss";
@import "_diagrams.scss";