mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-07-31 02:50:16 +00:00
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:
committed by
Calvin Jhunjhuwala
parent
c8bed8b698
commit
fe1aa0ecb1
130
shared/components/CardTextIcon/CardTextIconCard.scss
Normal file
130
shared/components/CardTextIcon/CardTextIconCard.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
121
shared/components/CardTextIcon/CardTextIconCard.tsx
Normal file
121
shared/components/CardTextIcon/CardTextIconCard.tsx
Normal 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;
|
||||
116
shared/components/CardTextIcon/README.md
Normal file
116
shared/components/CardTextIcon/README.md
Normal 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.
|
||||
1
shared/components/CardTextIcon/index.ts
Normal file
1
shared/components/CardTextIcon/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { CardTextIconCard, type CardTextIconCardProps } from './CardTextIconCard';
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
87
shared/patterns/CardsIconGrid/CardsIconGrid.scss
Normal file
87
shared/patterns/CardsIconGrid/CardsIconGrid.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
66
shared/patterns/CardsIconGrid/CardsIconGrid.tsx
Normal file
66
shared/patterns/CardsIconGrid/CardsIconGrid.tsx
Normal 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;
|
||||
53
shared/patterns/CardsIconGrid/README.md
Normal file
53
shared/patterns/CardsIconGrid/README.md
Normal 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
|
||||
4
shared/patterns/CardsIconGrid/index.ts
Normal file
4
shared/patterns/CardsIconGrid/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
CardsIconGrid,
|
||||
type CardsIconGridProps,
|
||||
} from './CardsIconGrid';
|
||||
87
shared/patterns/CardsTextGrid/CardsTextGrid.scss
Normal file
87
shared/patterns/CardsTextGrid/CardsTextGrid.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
62
shared/patterns/CardsTextGrid/CardsTextGrid.tsx
Normal file
62
shared/patterns/CardsTextGrid/CardsTextGrid.tsx
Normal 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;
|
||||
53
shared/patterns/CardsTextGrid/README.md
Normal file
53
shared/patterns/CardsTextGrid/README.md
Normal 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
|
||||
4
shared/patterns/CardsTextGrid/index.ts
Normal file
4
shared/patterns/CardsTextGrid/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
CardsTextGrid,
|
||||
type CardsTextGridProps,
|
||||
} from './CardsTextGrid';
|
||||
Reference in New Issue
Block a user