Add CardsIconGrid and CardsTextGrid Section Patterns (#3505)

* CardsIconGrid and CardsTextGrid + showcase pages

* cleaning up code

* more code clean up

* using the spacing tokens for values
This commit is contained in:
Calvin
2026-02-18 16:19:01 -08:00
committed by Calvin Jhunjhuwala
parent c8bed8b698
commit fe1aa0ecb1
19 changed files with 1083 additions and 29929 deletions

View File

@@ -0,0 +1,140 @@
import * as React from "react";
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 section pattern with icon cards, responsive grid layout, and aspect ratios.",
},
};
const SAMPLE_ICON = "/img/navbar/docs.svg";
const sampleCards = [
{
icon: SAMPLE_ICON,
iconAlt: "Digital Wallets",
heading: "Digital Wallets",
description:
"Offer fast, low-fee stablecoin payments between users and applications.",
},
{
icon: SAMPLE_ICON,
iconAlt: "Tutorials",
heading: "Tutorials",
description:
"Step-by-step guides to help you build on the XRP Ledger.",
},
{
icon: SAMPLE_ICON,
iconAlt: "API Reference",
heading: "API Reference",
description:
"Comprehensive API documentation for all XRPL methods.",
},
{
icon: SAMPLE_ICON,
iconAlt: "Use Cases",
heading: "Use Cases",
description:
"Explore real-world applications built on the XRP Ledger.",
},
{
icon: SAMPLE_ICON,
iconAlt: "Community",
heading: "Community",
description:
"Join the global community of XRPL developers and enthusiasts.",
},
{
icon: SAMPLE_ICON,
iconAlt: "Resources",
heading: "Resources",
description:
"Tools, libraries, and resources to accelerate your development.",
},
];
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 with a header (heading + description) and a grid
of CardTextIconCard components. Uses PageGrid.Row as="ul" with
cards as PageGrid.Col as="li". Aspect ratios: sm 3:2, md/lg 4:3.
Three cards per row at all breakpoints.
</p>
</div>
</section>
<Divider color="gray" />
{/* Full Example */}
<CardsIconGrid
heading="Unlock New Business Models with Embedded Payments"
description="XRPL Payments supports modern fintech use cases with plug-and-play APIs or partner-led deployments."
cards={sampleCards}
/>
<Divider color="gray" />
{/* Shorter Description */}
<CardsIconGrid
heading="Key Features"
description="Everything you need to build amazing applications on XRPL."
cards={sampleCards.slice(0, 3)}
/>
<Divider color="gray" />
{/* Without Description */}
<CardsIconGrid
heading="Platform Capabilities"
cards={sampleCards.slice(0, 2)}
/>
<Divider color="gray" />
{/* With Inline Link in Description */}
<CardsIconGrid
heading="Resources with Links"
description="Learn more about our documentation and community."
cards={[
{
icon: SAMPLE_ICON,
iconAlt: "Documentation",
heading: "Documentation",
description: (
<>
Access everything you need. Learn more in our{" "}
<a href="/docs">documentation</a>.
</>
),
},
{
icon: SAMPLE_ICON,
iconAlt: "Community",
heading: "Community",
description: (
<>
Join the global community.{" "}
<a href="/community">Get involved</a>.
</>
),
},
]}
/>
<Divider color="gray" />
</div>
</div>
);
}

View File

@@ -0,0 +1,121 @@
import { CardsTextGrid } from "shared/patterns/CardsTextGrid";
import { Divider } from "shared/components/Divider";
export const frontmatter = {
seo: {
title: "CardsTextGrid Pattern Showcase",
description:
"A comprehensive showcase of the CardsTextGrid section pattern with icon cards, responsive grid layout, and aspect ratios.",
},
};
const sampleCards = [
{
heading: "Fast Settlement and Low Fees",
description:
"Settle transactions in 3-5 seconds for a fraction of a cent, ideal for large-scale, high-volume RWA tokenization.",
},
{
heading: "Tutorials",
description:
"Step-by-step guides to help you build on the XRP Ledger.",
},
{
heading: "API Reference",
description:
"Comprehensive API documentation for all XRPL methods.",
},
{
heading: "Use Cases",
description:
"Explore real-world applications built on the XRP Ledger.",
},
{
heading: "Community",
description:
"Join the global community of XRPL developers and enthusiasts.",
},
{
heading: "Resources",
description:
"Tools, libraries, and resources to accelerate your development.",
},
];
export default function CardsTextGridShowcase() {
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">CardsTextGrid Pattern</h1>
<p className="longform">
A section pattern with a header (heading + description) and a grid
of CardTextIconCard components. Uses PageGrid.Row as="ul" with
cards as PageGrid.Col as="li". Aspect ratios: sm 16:9, md 3:2, lg
3:1. Three cards per row at base+md, two cards per row at lg.
</p>
</div>
</section>
<Divider color="gray" />
{/* Full Example */}
<CardsTextGrid
heading="Explore XRPL Developer Tools"
description="XRP Ledger is a compliance-focused blockchain where financial applications come to life. Choose a tool to get started."
cards={sampleCards}
/>
<Divider color="gray" />
{/* Shorter Description */}
<CardsTextGrid
heading="Key Features"
description="Everything you need to build amazing applications on XRPL."
cards={sampleCards.slice(0, 3)}
/>
<Divider color="gray" />
{/* Without Description */}
<CardsTextGrid
heading="Platform Capabilities"
cards={sampleCards.slice(0, 2)}
/>
<Divider color="gray" />
{/* With Inline Link in Description */}
<CardsTextGrid
heading="Resources with Links"
description="Learn more about our documentation and community."
cards={[
{
heading: "Documentation",
description: (
<>
Access everything you need. Learn more in our{" "}
<a href="/docs">documentation</a>.
</>
),
},
{
heading: "Community",
description: (
<>
Join the global community.{" "}
<a href="/community">Get involved</a>.
</>
),
},
]}
/>
<Divider color="gray" />
</div>
</div>
);
}

View File

@@ -66,6 +66,8 @@ module.exports = {
/^container/, // All container classes
/^row$/, // Row class
/^col-/, // Column classes
/^bds-grid__col/, // PageGrid column classes (dynamic span values)
/^bds-grid__offset/, // PageGrid offset classes
/^g-/, // Gap utilities
/^p-/, // Padding utilities
/^m-/, // Margin utilities

View File

@@ -0,0 +1,130 @@
// BDS CardTextIconCard Pattern Styles
// Brand Design System - Card with icon, heading, and description
//
// Naming Convention: BEM with 'bds' namespace
// .bds-card-text-icon-card - Base card container
// .bds-card-text-icon-card__icon - Icon container
// .bds-card-text-icon-card__icon-img - Icon image
// .bds-card-text-icon-card__heading - Card heading
// .bds-card-text-icon-card__description - Card description (supports ReactNode/links)
//
// Built from Section Cards - Icon and Section Cards - Text Grid Figma designs
// =============================================================================
// Design Tokens
// =============================================================================
// Icon sizes (responsive)
$bds-card-text-icon-icon-size-sm: 32px;
$bds-card-text-icon-icon-size-md: 36px;
$bds-card-text-icon-icon-size-lg: 40px;
// Padding (uses BDS card presets from _spacing.scss)
$bds-card-text-icon-padding-sm: $bds-padding-card-sm;
$bds-card-text-icon-padding-md: $bds-padding-card-md;
$bds-card-text-icon-padding-lg: $bds-padding-card-lg;
// Gaps (between icon, heading, description)
$bds-card-text-icon-gap-sm: $bds-padding-card-sm;
$bds-card-text-icon-gap-md: $bds-padding-card-md;
$bds-card-text-icon-gap-lg: $bds-padding-card-lg;
// =============================================================================
// Card Component
// =============================================================================
.bds-card-text-icon-card {
display: flex !important;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
text-decoration: none;
box-sizing: border-box;
border-left: 1px solid $gray-300;
margin-bottom: $bds-space-2xl;
// When used standalone (not as grid column), take full width
&:not(.bds-card-text-icon-card--grid-col) {
width: 100%;
}
padding: $bds-card-text-icon-padding-sm;
gap: $bds-card-text-icon-gap-sm;
@include media-breakpoint-up(md) {
padding: $bds-card-text-icon-padding-md;
gap: $bds-card-text-icon-gap-md;
margin-bottom: $bds-space-3xl;
}
@include media-breakpoint-up(lg) {
padding: $bds-card-text-icon-padding-lg;
gap: $bds-card-text-icon-gap-lg;
margin-bottom: $bds-space-4xl;
}
// Aspect ratio foundation - when CSS variable is set via aspectRatio prop
&[style*="--bds-card-text-icon-aspect-ratio"] {
aspect-ratio: var(--bds-card-text-icon-aspect-ratio);
}
@include bds-theme-mode(dark) {
color: $white;
}
}
// =============================================================================
// Icon
// =============================================================================
.bds-card-text-icon-card__icon {
width: 100%;
display: flex;
flex-direction: column;
gap: $bds-space-lg;
&-img {
object-fit: contain;
display: block;
flex-shrink: 0;
width: $bds-card-text-icon-icon-size-sm;
height: $bds-card-text-icon-icon-size-sm;
@include media-breakpoint-up(md) {
width: $bds-card-text-icon-icon-size-md;
height: $bds-card-text-icon-icon-size-md;
}
@include media-breakpoint-up(lg) {
width: $bds-card-text-icon-icon-size-lg;
height: $bds-card-text-icon-icon-size-lg;
}
}
}
// =============================================================================
// Heading
// =============================================================================
.bds-card-text-icon-card__heading {
margin: 0;
color: inherit;
}
// =============================================================================
// Description (supports ReactNode - links, formatted text, etc.)
// =============================================================================
.bds-card-text-icon-card__description {
margin: 0;
color: inherit;
min-width: 0;
position: relative;
a {
color: inherit;
text-decoration: underline;
text-underline-offset: 2px;
&:hover {
text-decoration-thickness: 2px;
}
}
}

View File

@@ -0,0 +1,121 @@
import React from 'react';
import clsx from 'clsx';
import { PageGrid } from '../PageGrid/page-grid';
import type { ResponsiveValue, PageGridSpanValue } from '../PageGrid/page-grid';
export interface CardTextIconCardProps {
/** Icon image URL */
icon?: string;
/** Alt text for the icon image */
iconAlt?: string;
/** Card heading */
heading: string;
/** Card description; accepts rich content (e.g., text with inline links) */
description: React.ReactNode | string;
/** Optional aspect ratio for future use; applied via CSS variable */
aspectRatio?: number;
/** When provided, renders as PageGrid.Col as="li" with this span—card becomes the grid column */
gridColSpan?: ResponsiveValue<PageGridSpanValue>;
/** Additional CSS classes */
className?: string;
/** Optional height and width for the icon image */
height?: number;
width?: number;
}
/**
* CardTextIconCard Component
*
* A card component featuring an icon, heading, and description.
* Built from Section Cards - Icon and Section Cards - Text Grid Figma designs.
*
* The description accepts ReactNode so it can include hyperlinks and other rich content.
*
* @example
* // Basic usage
* <CardTextIconCard
* icon="/icons/docs.svg"
* iconAlt="Documentation"
* heading="Documentation"
* description="Access everything you need to get started with the XRPL."
* />
*
* @example
* // With inline link in description
* <CardTextIconCard
* icon="/icons/docs.svg"
* heading="Documentation"
* description={
* <>
* Learn more in our{' '}
* <a href="/docs">documentation</a>.
* </>
* }
* />
*/
const cardContent = (
heading: string,
description: React.ReactNode | string,
icon?: string,
iconAlt?: string,
iconHeight?: number,
iconWidth?: number
) => (
<>
<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>
</>
);
export const CardTextIconCard: React.FC<CardTextIconCardProps> = ({
icon,
iconAlt = '',
heading,
description,
aspectRatio,
gridColSpan,
className,
height,
width
}) => {
const style = aspectRatio
? ({ '--bds-card-text-icon-aspect-ratio': aspectRatio } as React.CSSProperties)
: undefined;
if (gridColSpan) {
return (
<PageGrid.Col
as="li"
span={gridColSpan}
className={clsx('bds-card-text-icon-card', 'bds-card-text-icon-card--grid-col', className)}
style={style}
>
{cardContent(heading, description, icon, iconAlt, height, width)}
</PageGrid.Col>
);
}
return (
<div
className={clsx('bds-card-text-icon-card', className)}
style={style}
>
{cardContent(heading, description, icon, iconAlt, height, width)}
</div>
);
};
export default CardTextIconCard;

View File

@@ -0,0 +1,116 @@
# 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>.
</>
}
/>
```
### 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` | Optional | Icon image URL |
| `iconAlt` | `string` | `''` | Optional | Alt text for the icon image |
| `heading` | `string` | Required | Card 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 |
| `className` | `string` | - | Additional CSS classes |
## 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) | 56px | 16px | 16px |
| MD (576px - 991px) | 60px | 20px | 20px |
| LG (≥ 992px) | 64px | 24px | 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.

View File

@@ -0,0 +1 @@
export { CardTextIconCard, type CardTextIconCardProps } from './CardTextIconCard';

View File

@@ -186,4 +186,18 @@
@include bds-grid-generate-cols(12, 'xl');
@include bds-grid-generate-auto('xl');
@include bds-grid-generate-offsets(12, 'xl');
}
}
// Polymorphic as="li" — when Col renders as li, override list-item defaults
// so flex layout and width calculations apply correctly
li.bds-grid__col {
list-style: none;
display: block;
}
// Polymorphic as="ul" — when Row renders as ul, reset list styling
ul.bds-grid__row {
list-style: none;
padding-left: 0;
margin: 0;
}

View File

@@ -7,19 +7,24 @@ type PageGridElementProps = React.HTMLAttributes<HTMLDivElement>;
export type PageGridBreakpoint = "base" | "sm" | "md" | "lg" | "xl";
// Define the ResponsiveValue type using Partial<Record> for breakpoints
type ResponsiveValue<T> = T | Partial<Record<PageGridBreakpoint, T>>;
export type ResponsiveValue<T> = T | Partial<Record<PageGridBreakpoint, T>>;
export interface PageGridProps extends PageGridElementProps {
/** Container layout type - "standard" (default) or "wide" (1504px max-width, 144px padding at xl breakpoint) */
containerType?: "standard" | "wide";
}
export interface PageGridRowProps extends PageGridElementProps {}
export interface PageGridRowProps extends PageGridElementProps {
/** Polymorphic element - e.g. "ul" for semantic list markup */
as?: React.ElementType;
}
type PageGridSpanValue = number | "auto" | "fill";
export type PageGridSpanValue = number | "auto" | "fill";
type PageGridOffsetValue = number;
export interface PageGridColProps extends PageGridElementProps {
/** Polymorphic element - e.g. "li" for semantic list markup */
as?: React.ElementType;
span?: ResponsiveValue<PageGridSpanValue>;
offset?: ResponsiveValue<PageGridOffsetValue>;
}
@@ -85,17 +90,17 @@ PageGridRoot.displayName = "PageGrid";
// --- PageGrid.Row Component ---
const PageGridRow = React.forwardRef<HTMLDivElement, PageGridRowProps>(
({ className, ...rest }, ref) => (
<div ref={ref} className={clsx("bds-grid__row", className)} {...rest} />
const PageGridRow = React.forwardRef<HTMLElement, PageGridRowProps>(
({ as: Component = "div", className, ...rest }, ref) => (
<Component ref={ref} className={clsx("bds-grid__row", className)} {...rest} />
)
);
PageGridRow.displayName = "PageGridRow"; // Renamed display name for clarity
PageGridRow.displayName = "PageGridRow";
// --- PageGrid.Col Component ---
const PageGridCol = React.forwardRef<HTMLDivElement, PageGridColProps>((props, ref) => {
const { className, span, offset, ...rest } = props;
const PageGridCol = React.forwardRef<HTMLElement, PageGridColProps>((props, ref) => {
const { as: Component = "div", className, span, offset, ...rest } = props;
const spanClasses: string[] = [];
const offsetClasses: string[] = [];
@@ -151,10 +156,9 @@ const PageGridCol = React.forwardRef<HTMLDivElement, PageGridColProps>((props, r
// Ensure the base class is always applied for styling
return (
<div
<Component
ref={ref}
// Note: Added "bds-grid__col" base class for consistent column initialization
className={clsx("bds-grid__col", className, spanClasses, offsetClasses)}
className={clsx("bds-grid__col", className, spanClasses, offsetClasses)}
{...rest}
/>
);

View File

@@ -0,0 +1,87 @@
// BDS CardsIconGrid Pattern Styles
// Brand Design System - Section with heading, description, and grid of CardTextIconCard
//
// Naming Convention: BEM with 'bds' namespace
// .bds-cards-icon-grid - Base section container
// .bds-cards-icon-grid__header - Header section (heading + description)
// .bds-cards-icon-grid__list - Grid list (ul as PageGrid.Row)
// =============================================================================
// Design Tokens (uses BDS spacing from _spacing.scss)
// =============================================================================
$bds-cards-icon-grid-padding-base: $bds-space-2xl;
$bds-cards-icon-grid-padding-md: $bds-space-3xl;
$bds-cards-icon-grid-padding-lg: $bds-space-4xl;
$bds-cards-icon-grid-header-gap-base: $bds-gap-header-sm;
$bds-cards-icon-grid-header-gap-md: $bds-gap-header-md;
$bds-cards-icon-grid-header-gap-lg: $bds-gap-header-lg;
$bds-cards-icon-grid-header-margin-base: $bds-gap-section-sm;
$bds-cards-icon-grid-header-margin-md: $bds-gap-section-md;
$bds-cards-icon-grid-header-margin-lg: $bds-gap-section-lg;
// =============================================================================
// Section Container
// =============================================================================
.bds-cards-icon-grid {
padding-top: $bds-cards-icon-grid-padding-base;
@include media-breakpoint-up(md) {
padding-top: $bds-cards-icon-grid-padding-md;
}
@include media-breakpoint-up(lg) {
padding-top: $bds-cards-icon-grid-padding-lg;
}
ul {
padding-left: 0;
list-style: none;
}
@include bds-theme-mode(dark) {
color: $white;
}
}
// =============================================================================
// Header Section
// =============================================================================
.bds-cards-icon-grid__header {
display: flex;
flex-direction: column;
gap: $bds-cards-icon-grid-header-gap-base;
margin-bottom: $bds-cards-icon-grid-header-margin-base;
@include media-breakpoint-up(md) {
gap: $bds-cards-icon-grid-header-gap-md;
margin-bottom: $bds-cards-icon-grid-header-margin-md;
}
@include media-breakpoint-up(lg) {
gap: $bds-cards-icon-grid-header-gap-lg;
margin-bottom: $bds-cards-icon-grid-header-margin-lg;
}
h2, p {
margin-bottom: 0;
}
}
// =============================================================================
// List Grid - Aspect Ratios
// =============================================================================
.bds-cards-icon-grid__list > li {
aspect-ratio: 3 / 2;
@include media-breakpoint-up(md) {
aspect-ratio: 4 / 3;
}
@include media-breakpoint-up(lg) {
aspect-ratio: 4 / 3;
}
}

View File

@@ -0,0 +1,66 @@
import React from 'react';
import clsx from 'clsx';
import { PageGrid, PageGridRow, PageGridCol } from 'shared/components/PageGrid/page-grid';
import { CardTextIconCard, CardTextIconCardProps } from 'shared/components/CardTextIcon';
export interface CardsIconGridProps {
/** Section heading (required) */
heading: string;
/** Optional description text */
description?: string;
/** Array of card data to display */
cards: CardTextIconCardProps[];
/** Additional CSS classes */
className?: string;
}
/**
* CardsIconGrid Component
*
* A section pattern with a header (heading + description) and a grid of
* CardTextIconCard components. Uses PageGrid.Row as="ul" with cards as
* PageGrid.Col as="li". Aspect ratios: sm 3:2, md/lg 4:3.
*
* @example
* <CardsIconGrid
* heading="Explore Tools"
* description="Choose a tool to get started"
* cards={[
* { icon: "/icons/docs.svg", heading: "Documentation", description: "..." },
* ]}
* />
*/
export const CardsIconGrid: React.FC<CardsIconGridProps> = ({
heading,
description,
cards,
className,
}) => {
return (
<PageGrid className={clsx('bds-cards-icon-grid', className)}>
<PageGridRow>
<PageGridCol className="bds-cards-icon-grid__header" span={{ base: 12, md: 6, lg: 8 }}>
<h2 className="h-md">{heading}</h2>
{description && <p className="body-l">{description}</p>}
</PageGridCol>
</PageGridRow>
<PageGridRow as="ul" className="bds-cards-icon-grid__list list-none pl-0">
{cards.map((card, index) => (
<CardTextIconCard
key={card.heading || index}
icon={card.icon}
iconAlt={card.iconAlt}
heading={card.heading}
description={card.description}
height={card.height}
width={card.width}
gridColSpan={{ base: 4, md: 4, lg: 4 }}
/>
))}
</PageGridRow>
</PageGrid>
);
};
export default CardsIconGrid;

View File

@@ -0,0 +1,53 @@
# CardsIconGrid Pattern
A section pattern with a header (heading + description) and a grid of CardTextIconCard components.
## Overview
CardsIconGrid mirrors the LinkTextDirectory header structure and renders cards in a responsive grid using PageGrid.Row as="ul" and CardTextIconCard with gridColSpan. Each card is a PageGrid.Col as="li"—no div wrapper.
## Features
- Header with heading and optional description
- Responsive grid: 3 cards/row at all breakpoints (base, md, lg)
- Aspect ratios: sm 3:2, md/lg 4:3
- Light/dark mode support
## Usage
```tsx
import { CardsIconGrid } from 'shared/patterns/CardsIconGrid';
<CardsIconGrid
heading="Explore Tools"
description="Choose a tool to get started"
cards={[
{
icon: "/icons/docs.svg",
iconAlt: "Documentation",
heading: "Documentation",
description: "Access everything you need.",
},
]}
/>
```
## Props
| Prop | Type | Description |
|------|------|-------------|
| `heading` | `string` | Section heading |
| `description` | `string` | Optional description |
| `cards` | `CardTextIconCardData[]` | Array of card configs |
| `className` | `string` | Additional CSS classes |
## Grid Column Spans
- base: 4, md: 4, lg: 4 (3 cards per row)
## Files
- `CardsIconGrid.tsx` - React component
- `CardsIconGrid.scss` - Styles
- `index.ts` - Barrel exports
- `README.md` - This file

View File

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

View File

@@ -0,0 +1,87 @@
// BDS CardsTextGrid Pattern Styles
// Brand Design System - Section with heading, description, and grid of CardTextIconCard
//
// Naming Convention: BEM with 'bds' namespace
// .bds-cards-text-grid - Base section container
// .bds-cards-text-grid__header - Header section (heading + description)
// .bds-cards-text-grid__list - Grid list (ul as PageGrid.Row)
// =============================================================================
// Design Tokens (uses BDS spacing from _spacing.scss)
// =============================================================================
$bds-cards-text-grid-padding-base: $bds-space-2xl;
$bds-cards-text-grid-padding-md: $bds-space-3xl;
$bds-cards-text-grid-padding-lg: $bds-space-4xl;
$bds-cards-text-grid-header-gap-base: $bds-gap-header-sm;
$bds-cards-text-grid-header-gap-md: $bds-gap-header-md;
$bds-cards-text-grid-header-gap-lg: $bds-gap-header-lg;
$bds-cards-text-grid-header-margin-base: $bds-gap-section-sm;
$bds-cards-text-grid-header-margin-md: $bds-gap-section-md;
$bds-cards-text-grid-header-margin-lg: $bds-gap-section-lg;
// =============================================================================
// Section Container
// =============================================================================
.bds-cards-text-grid {
padding-top: $bds-cards-text-grid-padding-base;
@include media-breakpoint-up(md) {
padding-top: $bds-cards-text-grid-padding-md;
}
@include media-breakpoint-up(lg) {
padding-top: $bds-cards-text-grid-padding-lg;
}
ul {
padding-left: 0;
list-style: none;
}
@include bds-theme-mode(dark) {
color: $white;
}
}
// =============================================================================
// Header Section
// =============================================================================
.bds-cards-text-grid__header {
display: flex;
flex-direction: column;
gap: $bds-cards-text-grid-header-gap-base;
margin-bottom: $bds-cards-text-grid-header-margin-base;
@include media-breakpoint-up(md) {
gap: $bds-cards-text-grid-header-gap-md;
margin-bottom: $bds-cards-text-grid-header-margin-md;
}
@include media-breakpoint-up(lg) {
gap: $bds-cards-text-grid-header-gap-lg;
margin-bottom: $bds-cards-text-grid-header-margin-lg;
}
h2, p {
margin-bottom: 0;
}
}
// =============================================================================
// List Grid - Aspect Ratios
// =============================================================================
.bds-cards-text-grid__list > li {
aspect-ratio: 16 / 9;
@include media-breakpoint-up(md) {
aspect-ratio: 3 / 2;
}
@include media-breakpoint-up(lg) {
aspect-ratio: 3 / 1;
}
}

View File

@@ -0,0 +1,62 @@
import React from 'react';
import clsx from 'clsx';
import { PageGrid, PageGridRow, PageGridCol } from 'shared/components/PageGrid/page-grid';
import { CardTextIconCard, CardTextIconCardProps } from 'shared/components/CardTextIcon';
export interface CardsTextGridProps {
/** Section heading (required) */
heading: string;
/** Optional description text */
description?: string;
/** Array of card data to display */
cards: CardTextIconCardProps[];
/** Additional CSS classes */
className?: string;
}
/**
* CardsTextGrid Component
*
* A section pattern with a header (heading + description) and a grid of
* CardTextIconCard components. Uses PageGrid.Row as="ul" with cards as
* PageGrid.Col as="li". Aspect ratios: sm 16:9, md 3:2, lg 3:1.
*
* @example
* <CardsTextGrid
* heading="Explore Tools"
* description="Choose a tool to get started"
* cards={[
* { icon: "/icons/docs.svg", heading: "Documentation", description: "..." },
* ]}
* />
*/
export const CardsTextGrid: React.FC<CardsTextGridProps> = ({
heading,
description,
cards,
className,
}) => {
return (
<PageGrid className={clsx('bds-cards-text-grid', className)}>
<PageGridRow>
<PageGridCol className="bds-cards-text-grid__header" span={{ base: 12, md: 6, lg: 8 }}>
<h2 className="h-md">{heading}</h2>
{description && <p className="body-l">{description}</p>}
</PageGridCol>
</PageGridRow>
<PageGridRow as="ul" className="bds-cards-text-grid__list list-none pl-0">
{cards.map((card, index) => (
<CardTextIconCard
key={card.heading || index}
heading={card.heading}
description={card.description}
gridColSpan={{ base: 4, md: 4, lg: 6 }}
/>
))}
</PageGridRow>
</PageGrid>
);
};
export default CardsTextGrid;

View File

@@ -0,0 +1,53 @@
# CardsTextGrid Pattern
A section pattern with a header (heading + description) and a grid of CardTextIconCard components.
## Overview
CardsTextGrid mirrors the LinkTextDirectory header structure and renders cards in a responsive grid using PageGrid.Row as="ul" and CardTextIconCard with gridColSpan. Each card is a PageGrid.Col as="li"—no div wrapper.
## Features
- Header with heading and optional description
- Responsive grid: 3 cards/row at base+md, 2 cards/row at lg
- Aspect ratios: sm 16:9, md 3:2, lg 3:1
- Light/dark mode support
## Usage
```tsx
import { CardsTextGrid } from 'shared/patterns/CardsTextGrid';
<CardsTextGrid
heading="Explore Tools"
description="Choose a tool to get started"
cards={[
{
icon: "/icons/docs.svg",
iconAlt: "Documentation",
heading: "Documentation",
description: "Access everything you need.",
},
]}
/>
```
## Props
| Prop | Type | Description |
|------|------|-------------|
| `heading` | `string` | Section heading |
| `description` | `string` | Optional description |
| `cards` | `CardTextIconCardData[]` | Array of card configs |
| `className` | `string` | Additional CSS classes |
## Grid Column Spans
- base: 4, md: 4, lg: 6 (3 cards/row at base+md, 2 at lg)
## Files
- `CardsTextGrid.tsx` - React component
- `CardsTextGrid.scss` - Styles
- `index.ts` - Barrel exports
- `README.md` - This file

View File

@@ -0,0 +1,4 @@
export {
CardsTextGrid,
type CardsTextGridProps,
} from './CardsTextGrid';

File diff suppressed because one or more lines are too long

View File

@@ -122,6 +122,9 @@ $line-height-base: 1.5;
@import "../shared/patterns/LinkSmallGrid/LinkSmallGrid.scss";
@import "../shared/patterns/LinkTextCard/LinkTextCard.scss";
@import "../shared/patterns/LinkTextDirectory/LinkTextDirectory.scss";
@import "../shared/components/CardTextIcon/CardTextIconCard.scss";
@import "../shared/patterns/CardsIconGrid/CardsIconGrid.scss";
@import "../shared/patterns/CardsTextGrid/CardsTextGrid.scss";
@import "_code-tabs.scss";
@import "_code-walkthrough.scss";
@import "_diagrams.scss";