mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-01-18 05:35:19 +00:00
Compare commits
1 Commits
pattern/fe
...
pattern/lo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e61c71c94 |
530
about/logo-square-grid-showcase.page.tsx
Normal file
530
about/logo-square-grid-showcase.page.tsx
Normal file
File diff suppressed because one or more lines are too long
43
shared/patterns/ButtonGroup/ButtonGroup.scss
Normal file
43
shared/patterns/ButtonGroup/ButtonGroup.scss
Normal file
@@ -0,0 +1,43 @@
|
||||
// BDS ButtonGroup Component Styles
|
||||
// Brand Design System - Responsive button group pattern
|
||||
//
|
||||
// Naming Convention: BEM with 'bds' namespace
|
||||
// .bds-button-group - Base component
|
||||
// .bds-button-group--gap-none - No gap between buttons on tablet+ (0px)
|
||||
// .bds-button-group--gap-small - Small gap between buttons on tablet+ (8px)
|
||||
|
||||
// =============================================================================
|
||||
// Base Component Styles
|
||||
// =============================================================================
|
||||
|
||||
.bds-button-group {
|
||||
@extend .d-flex;
|
||||
@extend .flex-column;
|
||||
@extend .align-items-start;
|
||||
@extend .flex-wrap;
|
||||
gap: 8px;
|
||||
|
||||
// Tablet breakpoint - horizontal layout
|
||||
@include media-breakpoint-up(md) {
|
||||
flex-direction: row !important;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Gap Modifiers
|
||||
// =============================================================================
|
||||
|
||||
.bds-button-group--gap-none {
|
||||
// Tablet breakpoint - no gap
|
||||
@include media-breakpoint-up(md) {
|
||||
gap: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.bds-button-group--gap-small {
|
||||
// Tablet breakpoint - keep 8px gap
|
||||
@include media-breakpoint-up(md) {
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
90
shared/patterns/ButtonGroup/ButtonGroup.tsx
Normal file
90
shared/patterns/ButtonGroup/ButtonGroup.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { Button } from '../../components/Button/Button';
|
||||
|
||||
export interface ButtonConfig {
|
||||
label: string;
|
||||
href?: string;
|
||||
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
}
|
||||
|
||||
export interface ButtonGroupProps {
|
||||
/** Primary button configuration */
|
||||
primaryButton?: ButtonConfig;
|
||||
/** Tertiary button configuration */
|
||||
tertiaryButton?: ButtonConfig;
|
||||
/** Button color theme */
|
||||
color?: 'green' | 'black';
|
||||
/** Gap between buttons on tablet+ (0px or 8px) */
|
||||
gap?: 'none' | 'small';
|
||||
/** Additional CSS classes */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ButtonGroup Component
|
||||
*
|
||||
* A responsive button group container that displays primary and/or tertiary buttons.
|
||||
* Stacks vertically on mobile and horizontally on tablet+.
|
||||
*
|
||||
* @example
|
||||
* // Basic usage with both buttons
|
||||
* <ButtonGroup
|
||||
* primaryButton={{ label: "Get Started", href: "/start" }}
|
||||
* tertiaryButton={{ label: "Learn More", href: "/learn" }}
|
||||
* color="green"
|
||||
* />
|
||||
*
|
||||
* @example
|
||||
* // With custom gap
|
||||
* <ButtonGroup
|
||||
* primaryButton={{ label: "Action", onClick: handleClick }}
|
||||
* color="black"
|
||||
* gap="small"
|
||||
* />
|
||||
*/
|
||||
export const ButtonGroup: React.FC<ButtonGroupProps> = ({
|
||||
primaryButton,
|
||||
tertiaryButton,
|
||||
color = 'green',
|
||||
gap = 'small',
|
||||
className = '',
|
||||
}) => {
|
||||
// Don't render if no buttons are provided
|
||||
if (!primaryButton && !tertiaryButton) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const classNames = clsx(
|
||||
'bds-button-group',
|
||||
`bds-button-group--gap-${gap}`,
|
||||
className
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classNames}>
|
||||
{primaryButton && (
|
||||
<Button
|
||||
variant="primary"
|
||||
color={color}
|
||||
href={primaryButton.href}
|
||||
onClick={primaryButton?.onClick as (() => void) | undefined}
|
||||
>
|
||||
{primaryButton.label}
|
||||
</Button>
|
||||
)}
|
||||
{tertiaryButton && (
|
||||
<Button
|
||||
variant="tertiary"
|
||||
color={color}
|
||||
href={tertiaryButton.href}
|
||||
onClick={tertiaryButton?.onClick as (() => void) | undefined}
|
||||
>
|
||||
{tertiaryButton.label}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ButtonGroup;
|
||||
68
shared/patterns/ButtonGroup/README.md
Normal file
68
shared/patterns/ButtonGroup/README.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# ButtonGroup Component
|
||||
|
||||
A responsive button group container that displays primary and/or tertiary buttons. Stacks vertically on mobile and horizontally on tablet+.
|
||||
|
||||
## Features
|
||||
|
||||
- **Responsive Layout**: Vertical stack on mobile, horizontal row on tablet+
|
||||
- **Flexible Configuration**: Support for primary, tertiary, or both buttons
|
||||
- **Customizable Spacing**: Control gap between buttons on tablet+ (none or small)
|
||||
- **Theme Support**: Green or black color themes
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { ButtonGroup } from 'shared/patterns/ButtonGroup';
|
||||
|
||||
// Basic usage with both buttons
|
||||
<ButtonGroup
|
||||
primaryButton={{ label: "Get Started", href: "/start" }}
|
||||
tertiaryButton={{ label: "Learn More", href: "/learn" }}
|
||||
color="green"
|
||||
/>
|
||||
|
||||
// With no gap on tablet+
|
||||
<ButtonGroup
|
||||
primaryButton={{ label: "Action", onClick: handleClick }}
|
||||
color="black"
|
||||
gap="none"
|
||||
/>
|
||||
|
||||
// With small gap on tablet+ (4px - default)
|
||||
<ButtonGroup
|
||||
primaryButton={{ label: "Primary Action", href: "/action" }}
|
||||
tertiaryButton={{ label: "Secondary", href: "/secondary" }}
|
||||
gap="small"
|
||||
/>
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `primaryButton` | `ButtonConfig` | - | Primary button configuration |
|
||||
| `tertiaryButton` | `ButtonConfig` | - | Tertiary button configuration |
|
||||
| `color` | `'green' \| 'black'` | `'green'` | Button color theme |
|
||||
| `gap` | `'none' \| 'small'` | `'small'` | Gap between buttons on tablet+ (0px or 4px) |
|
||||
| `className` | `string` | `''` | Additional CSS classes |
|
||||
|
||||
### ButtonConfig
|
||||
|
||||
```tsx
|
||||
interface ButtonConfig {
|
||||
label: string;
|
||||
href?: string;
|
||||
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
}
|
||||
```
|
||||
|
||||
## Responsive Behavior
|
||||
|
||||
- **Mobile (<768px)**: Buttons stack vertically with 8px gap, aligned to start
|
||||
- **Tablet+ (≥768px)**: Buttons align horizontally, centered, with configurable gap (0px or 4px)
|
||||
|
||||
## CSS Classes
|
||||
|
||||
- `.bds-button-group` - Base component
|
||||
- `.bds-button-group--gap-none` - No gap on tablet+ (0px)
|
||||
- `.bds-button-group--gap-small` - Small gap on tablet+ (4px)
|
||||
2
shared/patterns/ButtonGroup/index.ts
Normal file
2
shared/patterns/ButtonGroup/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { ButtonGroup } from './ButtonGroup';
|
||||
export type { ButtonGroupProps, ButtonConfig } from './ButtonGroup';
|
||||
@@ -15,7 +15,8 @@
|
||||
// .bds-callout-media-banner__text - Text content container
|
||||
// .bds-callout-media-banner__heading - Heading element
|
||||
// .bds-callout-media-banner__subheading - Subheading element
|
||||
// .bds-callout-media-banner__actions - Button container
|
||||
//
|
||||
// Note: Button layout is handled by the ButtonGroup component
|
||||
|
||||
// =============================================================================
|
||||
// Design Tokens
|
||||
@@ -191,21 +192,8 @@ $bds-cmb-image-overlay: rgba(0, 0, 0, 0.3);
|
||||
|
||||
// =============================================================================
|
||||
// Action Buttons
|
||||
// ==================================================================== c=========
|
||||
|
||||
.bds-callout-media-banner__actions {
|
||||
@extend .d-flex;
|
||||
@extend .flex-column;
|
||||
@extend .align-items-start;
|
||||
@extend .flex-wrap;
|
||||
gap: 8px;
|
||||
// Tablet breakpoint
|
||||
@include media-breakpoint-up(md) {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0px;
|
||||
}
|
||||
}
|
||||
// =============================================================================
|
||||
// Note: Button layout is now handled by the ButtonGroup component
|
||||
|
||||
// =============================================================================
|
||||
// Color Variants (only applied when NO backgroundImage is provided)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { Button } from '../../components/Button/Button';
|
||||
import { PageGrid, PageGridCol, PageGridRow } from 'shared/components/PageGrid/page-grid';
|
||||
import { ButtonGroup } from '../ButtonGroup/ButtonGroup';
|
||||
|
||||
export interface CalloutMediaBannerProps {
|
||||
/** Color variant - determines background color (ignored if backgroundImage is provided) */
|
||||
@@ -116,30 +116,12 @@ export const CalloutMediaBanner: React.FC<CalloutMediaBannerProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Buttons */}
|
||||
{(primaryButton || tertiaryButton) && (
|
||||
<div className="bds-callout-media-banner__actions">
|
||||
{primaryButton && (
|
||||
<Button
|
||||
variant="primary"
|
||||
color={buttonColor}
|
||||
href={primaryButton.href}
|
||||
onClick={primaryButton?.onClick as (() => void) | undefined}
|
||||
>
|
||||
{primaryButton.label}
|
||||
</Button>
|
||||
)}
|
||||
{tertiaryButton && (
|
||||
<Button
|
||||
variant="tertiary"
|
||||
color={buttonColor}
|
||||
href={tertiaryButton.href}
|
||||
onClick={tertiaryButton?.onClick as (() => void) | undefined}
|
||||
>
|
||||
{tertiaryButton.label}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<ButtonGroup
|
||||
primaryButton={primaryButton}
|
||||
tertiaryButton={tertiaryButton}
|
||||
color={buttonColor}
|
||||
gap="none"
|
||||
/>
|
||||
</div>
|
||||
</PageGridCol>
|
||||
</PageGridRow>
|
||||
|
||||
115
shared/patterns/LogoSquareGrid/LogoSquareGrid.scss
Normal file
115
shared/patterns/LogoSquareGrid/LogoSquareGrid.scss
Normal file
@@ -0,0 +1,115 @@
|
||||
// BDS LogoSquareGrid Component Styles
|
||||
// Brand Design System - Logo grid pattern with optional header section
|
||||
//
|
||||
// Naming Convention: BEM with 'bds' namespace
|
||||
// .bds-logo-square-grid - Base component
|
||||
// .bds-logo-square-grid--gray - Gray variant (maps to TileLogo 'neutral')
|
||||
// .bds-logo-square-grid--green - Green variant (maps to TileLogo 'green')
|
||||
// .bds-logo-square-grid__header - Header section container
|
||||
// .bds-logo-square-grid__text - Text content container
|
||||
// .bds-logo-square-grid__heading - Heading element
|
||||
// .bds-logo-square-grid__description - Description element
|
||||
//
|
||||
// Note: Individual logo tiles are rendered using the TileLogo component
|
||||
// Note: Button layout is handled by the ButtonGroup component
|
||||
|
||||
// =============================================================================
|
||||
// Design Tokens
|
||||
// =============================================================================
|
||||
// Note: Color variants are now handled by the TileLogo component
|
||||
// LogoSquareGrid 'gray' maps to TileLogo 'neutral'
|
||||
// LogoSquareGrid 'green' maps to TileLogo 'green'
|
||||
|
||||
// Spacing tokens - responsive
|
||||
// Mobile (<768px)
|
||||
$bds-lsg-header-gap-mobile: 24px;
|
||||
$bds-lsg-text-gap-mobile: 8px;
|
||||
|
||||
// Tablet (768px-1023px)
|
||||
$bds-lsg-header-gap-tablet: 32px;
|
||||
|
||||
// Desktop (≥1024px)
|
||||
$bds-lsg-header-gap-desktop: 40px;
|
||||
$bds-lsg-text-gap-desktop: 16px;
|
||||
|
||||
// =============================================================================
|
||||
// Base Component Styles
|
||||
// =============================================================================
|
||||
|
||||
.bds-logo-square-grid {
|
||||
@extend .d-flex;
|
||||
@extend .flex-column;
|
||||
@extend .w-100;
|
||||
|
||||
// Mobile-first gap
|
||||
gap: $bds-lsg-header-gap-mobile;
|
||||
|
||||
// Tablet breakpoint
|
||||
@include media-breakpoint-up(md) {
|
||||
gap: $bds-lsg-header-gap-tablet;
|
||||
}
|
||||
|
||||
// Desktop breakpoint
|
||||
@include media-breakpoint-up(lg) {
|
||||
gap: $bds-lsg-header-gap-desktop;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Header Section
|
||||
// =============================================================================
|
||||
|
||||
.bds-logo-square-grid__header {
|
||||
@extend .d-flex;
|
||||
@extend .flex-column;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
// Mobile-first gap
|
||||
gap: $bds-lsg-header-gap-mobile;
|
||||
|
||||
// Tablet breakpoint
|
||||
@include media-breakpoint-up(md) {
|
||||
gap: $bds-lsg-header-gap-tablet;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
// Desktop breakpoint
|
||||
@include media-breakpoint-up(lg) {
|
||||
gap: $bds-lsg-header-gap-desktop;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Text Content
|
||||
// =============================================================================
|
||||
|
||||
.bds-logo-square-grid__text {
|
||||
@extend .d-flex;
|
||||
@extend .flex-column;
|
||||
|
||||
// Mobile-first gap
|
||||
gap: $bds-lsg-text-gap-mobile;
|
||||
|
||||
// Desktop breakpoint
|
||||
@include media-breakpoint-up(lg) {
|
||||
gap: $bds-lsg-text-gap-desktop;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// Action Buttons
|
||||
// =============================================================================
|
||||
// Note: Button layout is now handled by the ButtonGroup component
|
||||
|
||||
// =============================================================================
|
||||
// Logo Grid Row
|
||||
// =============================================================================
|
||||
// Note: Grid layout is now handled by PageGridRow/PageGridCol
|
||||
// Each tile uses PageGridCol with span={{ base: 2, lg: 3 }}
|
||||
// This gives us 2 columns on mobile (2/4) and 4 columns on desktop (3/12)
|
||||
// Tile rendering and styling is handled by the TileLogo component
|
||||
140
shared/patterns/LogoSquareGrid/LogoSquareGrid.tsx
Normal file
140
shared/patterns/LogoSquareGrid/LogoSquareGrid.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { PageGrid, PageGridCol, PageGridRow } from 'shared/components/PageGrid/page-grid';
|
||||
import { TileLogo } from '../../components/TileLogo/TileLogo';
|
||||
import { ButtonGroup } from '../ButtonGroup/ButtonGroup';
|
||||
|
||||
export interface LogoItem {
|
||||
/** Logo image source URL */
|
||||
src: string;
|
||||
/** Alt text for the logo image */
|
||||
alt: string;
|
||||
/** Optional link URL - makes the logo clickable */
|
||||
href?: string;
|
||||
/** Optional click handler - makes the logo a button */
|
||||
onClick?: () => void;
|
||||
/** Disabled state */
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface LogoSquareGridProps {
|
||||
/** Color variant - determines background color */
|
||||
variant?: 'gray' | 'green';
|
||||
/** Optional heading text */
|
||||
heading?: string;
|
||||
/** Optional description text */
|
||||
description?: string;
|
||||
/** Primary button configuration */
|
||||
primaryButton?: {
|
||||
label: string;
|
||||
href?: string;
|
||||
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
};
|
||||
/** Tertiary button configuration */
|
||||
tertiaryButton?: {
|
||||
label: string;
|
||||
href?: string;
|
||||
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
};
|
||||
/** Array of logo items to display in the grid */
|
||||
logos: LogoItem[];
|
||||
/** Additional CSS classes */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* LogoSquareGrid Component
|
||||
*
|
||||
* A responsive grid pattern for displaying company/partner logos with an optional header section.
|
||||
* Features square tiles arranged in a responsive grid with 2 color variants and dark mode support.
|
||||
*
|
||||
* @example
|
||||
* // Basic usage with gray variant
|
||||
* <LogoSquareGrid
|
||||
* variant="gray"
|
||||
* heading="Developer tools & APIs"
|
||||
* description="Streamline development with comprehensive tools."
|
||||
* logos={[
|
||||
* { src: "/logos/company1.svg", alt: "Company 1" },
|
||||
* { src: "/logos/company2.svg", alt: "Company 2" }
|
||||
* ]}
|
||||
* />
|
||||
*
|
||||
* @example
|
||||
* // With buttons and clickable logos
|
||||
* <LogoSquareGrid
|
||||
* variant="green"
|
||||
* heading="Our Partners"
|
||||
* description="Leading companies building on XRPL."
|
||||
* primaryButton={{ label: "View All Partners", href: "/partners" }}
|
||||
* tertiaryButton={{ label: "Become a Partner", href: "/partner-program" }}
|
||||
* logos={[
|
||||
* { src: "/logos/partner1.svg", alt: "Partner 1", href: "https://partner1.com" }
|
||||
* ]}
|
||||
* />
|
||||
*/
|
||||
export const LogoSquareGrid: React.FC<LogoSquareGridProps> = ({
|
||||
variant = 'gray',
|
||||
heading,
|
||||
description,
|
||||
primaryButton,
|
||||
tertiaryButton,
|
||||
logos,
|
||||
className = '',
|
||||
}) => {
|
||||
// Build class names using BEM with bds namespace
|
||||
const classNames = clsx(
|
||||
'bds-logo-square-grid',
|
||||
`bds-logo-square-grid--${variant}`,
|
||||
className
|
||||
);
|
||||
|
||||
// Determine if we should show the header section
|
||||
const hasHeader = !!(heading || description || primaryButton || tertiaryButton);
|
||||
|
||||
return (
|
||||
<PageGrid className="">
|
||||
<PageGridRow>
|
||||
<PageGridCol span={{ base: 4, md: 8, lg: 8 }}>
|
||||
{/* Header Section */}
|
||||
{hasHeader && (
|
||||
<div className="bds-logo-square-grid__header">
|
||||
{/* Text Content */}
|
||||
{(heading || description) && (
|
||||
<div className="bds-logo-square-grid__text">
|
||||
{heading && <h4 className="h-md mb-0">{heading}</h4>}
|
||||
{description && <p className="body-l mb-0">{description}</p>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Buttons */}
|
||||
<ButtonGroup
|
||||
primaryButton={primaryButton}
|
||||
tertiaryButton={tertiaryButton}
|
||||
color="green"
|
||||
gap="small"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</PageGridCol>
|
||||
</PageGridRow>
|
||||
<PageGridRow>
|
||||
{logos.map((logo, index) => (
|
||||
<PageGridCol key={index} span={{ base: 2, lg: 3 }}>
|
||||
<TileLogo
|
||||
shape="square"
|
||||
variant={variant === 'gray' ? 'neutral' : 'green'}
|
||||
logo={logo.src}
|
||||
alt={logo.alt}
|
||||
href={logo.href}
|
||||
onClick={logo.onClick}
|
||||
disabled={logo.disabled}
|
||||
/>
|
||||
</PageGridCol>
|
||||
))}
|
||||
</PageGridRow>
|
||||
</PageGrid>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogoSquareGrid;
|
||||
405
shared/patterns/LogoSquareGrid/README.md
Normal file
405
shared/patterns/LogoSquareGrid/README.md
Normal file
@@ -0,0 +1,405 @@
|
||||
# LogoSquareGrid Component
|
||||
|
||||
A responsive grid pattern for displaying company/partner logos with an optional header section. Built on top of the TileLogo component, featuring square tiles arranged in a responsive grid with 2 color variants and full dark mode support.
|
||||
|
||||
## Features
|
||||
|
||||
- **2 Color Variants**: Gray and Green backgrounds
|
||||
- **Responsive Grid**: Automatically adapts from 2 columns (mobile) to 4 columns (tablet/desktop)
|
||||
- **Optional Header**: Includes heading, description, and action buttons
|
||||
- **Clickable Logos**: Support for optional links on individual logos
|
||||
- **Dark Mode Support**: Full light and dark mode compatibility
|
||||
- **Square Tiles**: Maintains perfect square aspect ratio at all breakpoints
|
||||
- **Grid Integration**: Built-in PageGrid wrapper with standard container support
|
||||
|
||||
## Responsive Behavior
|
||||
|
||||
The component automatically adapts its grid layout based on viewport width:
|
||||
|
||||
| Breakpoint | Columns | Gap | Tile Size |
|
||||
|------------|---------|-----|-----------|
|
||||
| Mobile (< 768px) | 2 | 8px | ~183px |
|
||||
| Tablet (768px - 1023px) | 4 | 8px | ~178px |
|
||||
| Desktop (≥ 1024px) | 4 | 8px | ~298px |
|
||||
|
||||
## Color Variants
|
||||
|
||||
The LogoSquareGrid pattern uses two color variants that map directly to TileLogo component variants:
|
||||
|
||||
| LogoSquareGrid Variant | TileLogo Variant | Description |
|
||||
|------------------------|------------------|-------------|
|
||||
| `gray` | `neutral` | Subtle, professional appearance for general partner showcases |
|
||||
| `green` | `green` | Highlights featured or primary partners |
|
||||
|
||||
Colors are managed by the TileLogo component and automatically adapt between light and dark modes with proper hover states and animations.
|
||||
|
||||
## Props API
|
||||
|
||||
```typescript
|
||||
interface LogoItem {
|
||||
/** Logo image source URL */
|
||||
src: string;
|
||||
/** Alt text for the logo image */
|
||||
alt: string;
|
||||
/** Optional link URL - makes the logo clickable */
|
||||
href?: string;
|
||||
/** Optional click handler - makes the logo a button */
|
||||
onClick?: () => void;
|
||||
/** Disabled state */
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface LogoSquareGridProps {
|
||||
/** Color variant - determines background color */
|
||||
variant?: 'gray' | 'green';
|
||||
/** Optional heading text */
|
||||
heading?: string;
|
||||
/** Optional description text */
|
||||
description?: string;
|
||||
/** Primary button configuration */
|
||||
primaryButton?: {
|
||||
label: string;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
};
|
||||
/** Tertiary button configuration */
|
||||
tertiaryButton?: {
|
||||
label: string;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
};
|
||||
/** Array of logo items to display in the grid */
|
||||
logos: LogoItem[];
|
||||
/** Additional CSS classes */
|
||||
className?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Default Values
|
||||
|
||||
- `variant`: `'gray'`
|
||||
- `heading`: `undefined`
|
||||
- `description`: `undefined`
|
||||
- `primaryButton`: `undefined`
|
||||
- `tertiaryButton`: `undefined`
|
||||
- `className`: `''`
|
||||
|
||||
### Required Props
|
||||
|
||||
- `logos`: Array of logo items (required)
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Usage with Gray Variant
|
||||
|
||||
```tsx
|
||||
import { LogoSquareGrid } from 'shared/patterns/LogoSquareGrid';
|
||||
|
||||
<LogoSquareGrid
|
||||
variant="gray"
|
||||
logos={[
|
||||
{ src: "/img/logos/company1.svg", alt: "Company 1" },
|
||||
{ src: "/img/logos/company2.svg", alt: "Company 2" },
|
||||
{ src: "/img/logos/company3.svg", alt: "Company 3" },
|
||||
{ src: "/img/logos/company4.svg", alt: "Company 4" }
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
### With Header Section
|
||||
|
||||
```tsx
|
||||
<LogoSquareGrid
|
||||
variant="green"
|
||||
heading="Developer tools & APIs"
|
||||
description="Streamline development and build powerful RWA tokenization solutions with XRP Ledger's comprehensive developer toolset."
|
||||
primaryButton={{ label: "View Documentation", href: "/docs" }}
|
||||
tertiaryButton={{ label: "Explore Tools", href: "/tools" }}
|
||||
logos={[
|
||||
{ src: "/img/logos/tool1.svg", alt: "Tool 1" },
|
||||
{ src: "/img/logos/tool2.svg", alt: "Tool 2" },
|
||||
{ src: "/img/logos/tool3.svg", alt: "Tool 3" },
|
||||
{ src: "/img/logos/tool4.svg", alt: "Tool 4" },
|
||||
{ src: "/img/logos/tool5.svg", alt: "Tool 5" },
|
||||
{ src: "/img/logos/tool6.svg", alt: "Tool 6" },
|
||||
{ src: "/img/logos/tool7.svg", alt: "Tool 7" },
|
||||
{ src: "/img/logos/tool8.svg", alt: "Tool 8" }
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
### With Clickable Logos
|
||||
|
||||
```tsx
|
||||
<LogoSquareGrid
|
||||
variant="gray"
|
||||
heading="Our Partners"
|
||||
description="Leading companies building on XRPL."
|
||||
logos={[
|
||||
{
|
||||
src: "/img/logos/partner1.svg",
|
||||
alt: "Partner 1",
|
||||
href: "https://partner1.com"
|
||||
},
|
||||
{
|
||||
src: "/img/logos/partner2.svg",
|
||||
alt: "Partner 2",
|
||||
href: "https://partner2.com"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
### With Button Handlers
|
||||
|
||||
```tsx
|
||||
<LogoSquareGrid
|
||||
variant="green"
|
||||
heading="Interactive Partners"
|
||||
description="Click any logo to learn more."
|
||||
logos={[
|
||||
{
|
||||
src: "/img/logos/partner1.svg",
|
||||
alt: "Partner 1",
|
||||
onClick: () => openModal('partner1')
|
||||
},
|
||||
{
|
||||
src: "/img/logos/partner2.svg",
|
||||
alt: "Partner 2",
|
||||
onClick: () => openModal('partner2')
|
||||
}
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
### With Disabled State
|
||||
|
||||
```tsx
|
||||
<LogoSquareGrid
|
||||
variant="gray"
|
||||
heading="Coming Soon"
|
||||
description="New partners joining the ecosystem."
|
||||
logos={[
|
||||
{
|
||||
src: "/img/logos/partner1.svg",
|
||||
alt: "Partner 1",
|
||||
href: "/partners/partner1"
|
||||
},
|
||||
{
|
||||
src: "/img/logos/coming-soon.svg",
|
||||
alt: "Coming Soon",
|
||||
disabled: true
|
||||
}
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
### Without Header (Logo Grid Only)
|
||||
|
||||
```tsx
|
||||
<LogoSquareGrid
|
||||
variant="gray"
|
||||
logos={[
|
||||
{ src: "/img/logos/sponsor1.svg", alt: "Sponsor 1" },
|
||||
{ src: "/img/logos/sponsor2.svg", alt: "Sponsor 2" },
|
||||
{ src: "/img/logos/sponsor3.svg", alt: "Sponsor 3" },
|
||||
{ src: "/img/logos/sponsor4.svg", alt: "Sponsor 4" }
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
### With Single Button
|
||||
|
||||
```tsx
|
||||
<LogoSquareGrid
|
||||
variant="green"
|
||||
heading="Featured Integrations"
|
||||
description="Connect with leading platforms and services."
|
||||
primaryButton={{ label: "See All Integrations", href: "/integrations" }}
|
||||
logos={[
|
||||
{ src: "/img/logos/integration1.svg", alt: "Integration 1" },
|
||||
{ src: "/img/logos/integration2.svg", alt: "Integration 2" }
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
### With Click Handler
|
||||
|
||||
```tsx
|
||||
<LogoSquareGrid
|
||||
variant="gray"
|
||||
heading="Developer Resources"
|
||||
description="Access comprehensive tools and libraries."
|
||||
primaryButton={{
|
||||
label: "Get Started",
|
||||
onClick: () => console.log('Primary clicked')
|
||||
}}
|
||||
tertiaryButton={{
|
||||
label: "Learn More",
|
||||
href: "/learn"
|
||||
}}
|
||||
logos={[
|
||||
{ src: "/img/logos/resource1.svg", alt: "Resource 1" }
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
## Important Implementation Details
|
||||
|
||||
### Logo Image Requirements
|
||||
|
||||
For best results, logo images should:
|
||||
- Be SVG format for crisp scaling
|
||||
- Have transparent backgrounds
|
||||
- Be reasonably sized (width: 120-200px recommended)
|
||||
- Use monochrome or simple color schemes
|
||||
- Have consistent visual weight across all logos
|
||||
|
||||
### Grid Behavior
|
||||
|
||||
- The grid uses PageGridCol components for responsive layout
|
||||
- Each tile uses `span={{ base: 2, lg: 3 }}` (2 cols on mobile out of 4, 3 cols on desktop out of 12)
|
||||
- Tiles maintain a 1:1 aspect ratio using `aspect-ratio: 1`
|
||||
- Gaps between tiles are handled by PageGrid's built-in gutter system
|
||||
- Grid automatically wraps to new rows as needed
|
||||
|
||||
### Clickable Logo Behavior
|
||||
|
||||
Logo tiles leverage the TileLogo component's interactive capabilities:
|
||||
- **With `href` property**: Renders as a link (`<a>` tag) with window shade hover animation
|
||||
- **With `onClick` property**: Renders as a button with the same interactive states
|
||||
- **With `disabled` property**: Prevents interaction and applies disabled styling
|
||||
- **Interactive states**: Default, Hover, Focused, Pressed, and Disabled
|
||||
- **Animation**: Window shade effect that wipes from bottom to top on hover
|
||||
- All tiles automatically maintain focus states for keyboard accessibility
|
||||
|
||||
### Header Section Logic
|
||||
|
||||
The header section only renders if at least one of the following is provided:
|
||||
- `heading`
|
||||
- `description`
|
||||
- `primaryButton`
|
||||
- `tertiaryButton`
|
||||
|
||||
### Button Styling
|
||||
|
||||
- Both primary and tertiary buttons use green color scheme
|
||||
- Buttons stack vertically on mobile, horizontal on tablet+
|
||||
- Button spacing: 8px gap on mobile, 4px gap on tablet+
|
||||
- Button layout is handled by the shared ButtonGroup component
|
||||
|
||||
## Styling
|
||||
|
||||
### BEM Class Structure
|
||||
|
||||
```scss
|
||||
.bds-logo-square-grid // Base component
|
||||
.bds-logo-square-grid--gray // Gray variant (maps to TileLogo 'neutral')
|
||||
.bds-logo-square-grid--green // Green variant (maps to TileLogo 'green')
|
||||
.bds-logo-square-grid__header // Header section container
|
||||
.bds-logo-square-grid__text // Text content container
|
||||
.bds-logo-square-grid__heading // Heading element
|
||||
.bds-logo-square-grid__description // Description element
|
||||
```
|
||||
|
||||
**Note**: Individual logo tiles are rendered using the TileLogo component with its own BEM structure (`bds-tile-logo`). Grid layout is handled by PageGridRow and PageGridCol components. Button layout is handled by the ButtonGroup component (`bds-button-group`).
|
||||
|
||||
### Typography Tokens
|
||||
|
||||
- **Heading**: Uses `heading-md` type token (Tobias Light font)
|
||||
- Desktop: 40px / 46px line-height / -1px letter-spacing
|
||||
- Tablet: 36px / 45px line-height / -0.5px letter-spacing
|
||||
- Mobile: 32px / 40px line-height / 0px letter-spacing
|
||||
|
||||
- **Description**: Uses `body-l` type token (Booton Light font)
|
||||
- Desktop: 18px / 26.1px line-height / -0.5px letter-spacing
|
||||
- Tablet: 18px / 26.1px line-height / -0.5px letter-spacing
|
||||
- Mobile: 18px / 26.1px line-height / -0.5px letter-spacing
|
||||
|
||||
### Color Tokens
|
||||
|
||||
All colors are sourced from `styles/_colors.scss`:
|
||||
|
||||
```scss
|
||||
// Tile backgrounds
|
||||
$gray-200 // Gray variant (light mode)
|
||||
$gray-700 // Gray variant (dark mode)
|
||||
$green-200 // Green variant (light mode)
|
||||
$green-300 // Green variant (dark mode)
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Semantic HTML structure with proper heading hierarchy
|
||||
- All logos include descriptive alt text
|
||||
- Clickable logos have proper link semantics
|
||||
- Keyboard navigation support with visible focus states
|
||||
- ARIA labels provided through Button component
|
||||
- Color contrast meets WCAG AA standards in all variants
|
||||
|
||||
## Best Practices
|
||||
|
||||
### When to Use Each Variant
|
||||
|
||||
- **Gray**: General-purpose logo grids, subtle integration
|
||||
- **Green**: Featured partnerships, brand-focused sections
|
||||
|
||||
### Content Guidelines
|
||||
|
||||
- **Heading**: Keep concise (1 line preferred), use sentence case
|
||||
- **Description**: Provide context (2-3 lines max), complete sentences
|
||||
- **Logo Count**: Aim for multiples of 4 for visual balance on desktop
|
||||
- **Alt Text**: Use company/product names, not generic "logo"
|
||||
|
||||
### Logo Preparation
|
||||
|
||||
1. **Consistent Sizing**: Ensure all logos have similar visual weight
|
||||
2. **Format**: Use SVG for scalability and crisp rendering
|
||||
3. **Background**: Transparent backgrounds work best
|
||||
4. **Color**: Consider providing light/dark variants if needed
|
||||
5. **Padding**: Include minimal internal padding in the SVG itself
|
||||
|
||||
### Performance
|
||||
|
||||
- Use optimized SVG files (run through SVGO or similar)
|
||||
- Consider lazy loading for grids with many logos
|
||||
- Provide appropriate alt text for all images
|
||||
- Use `width` and `height` attributes on img tags when possible
|
||||
|
||||
### Technical Implementation
|
||||
|
||||
- **Grid System**: Uses PageGridCol with `span={{ base: 2, lg: 3 }}` for responsive layout (2 cols mobile, 4 cols desktop)
|
||||
- **Tile Rendering**: Leverages TileLogo component for all logo tiles
|
||||
- **Variant Mapping**: LogoSquareGrid 'gray' → TileLogo 'neutral', LogoSquareGrid 'green' → TileLogo 'green'
|
||||
- **Interactive States**: TileLogo handles href (links), onClick (buttons), and disabled states
|
||||
- **Aspect Ratio**: Square tiles maintained by TileLogo with CSS `aspect-ratio: 1`
|
||||
- **Animations**: Window shade hover effect managed by TileLogo component
|
||||
- **Button Layout**: Uses shared ButtonGroup component with `gap="small"` (4px on tablet+)
|
||||
|
||||
## Files
|
||||
|
||||
- `LogoSquareGrid.tsx` - Component implementation
|
||||
- `LogoSquareGrid.scss` - Styles with color variants and responsive breakpoints
|
||||
- `index.ts` - Barrel exports
|
||||
- `README.md` - This documentation
|
||||
|
||||
## Related Components
|
||||
|
||||
- **TileLogo**: Core component used to render individual logo tiles with interactive states
|
||||
- **ButtonGroup**: Shared pattern used for responsive button layout in the header
|
||||
- **PageGrid**: Used internally for responsive grid structure and standard container support
|
||||
|
||||
## Design References
|
||||
|
||||
- **Figma Design**: [Pattern Logo - Square Grid](https://www.figma.com/design/ThBcoYLNKsBGw3r9g1L6Z8/Pattern-Logo---Square-Grid?node-id=1-2)
|
||||
- **Showcase Page**: `/about/logo-square-grid-showcase.page.tsx`
|
||||
- **Component Location**: `shared/patterns/LogoSquareGrid/`
|
||||
|
||||
## Version History
|
||||
|
||||
- **January 2026**: Initial implementation
|
||||
- Figma design alignment with 2 color variants
|
||||
- Responsive grid with 2/4 column layout
|
||||
- Optional header section with buttons
|
||||
- Clickable logo support
|
||||
- Refactored to use shared ButtonGroup component for button layout
|
||||
2
shared/patterns/LogoSquareGrid/index.ts
Normal file
2
shared/patterns/LogoSquareGrid/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { LogoSquareGrid } from './LogoSquareGrid';
|
||||
export type { LogoSquareGridProps, LogoItem } from './LogoSquareGrid';
|
||||
@@ -5332,7 +5332,7 @@ textarea.form-control-lg {
|
||||
display: table-cell !important;
|
||||
}
|
||||
|
||||
.d-flex, .bds-callout-media-banner__actions, .bds-callout-media-banner__text, .bds-callout-media-banner__content, .bds-callout-media-banner > [class*=bds-grid__col], .bds-callout-media-banner {
|
||||
.d-flex, .bds-logo-square-grid__text, .bds-logo-square-grid__header, .bds-logo-square-grid, .bds-callout-media-banner__text, .bds-callout-media-banner__content, .bds-callout-media-banner > [class*=bds-grid__col], .bds-callout-media-banner, .bds-button-group {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
@@ -5646,7 +5646,7 @@ textarea.form-control-lg {
|
||||
width: 75% !important;
|
||||
}
|
||||
|
||||
.w-100, .bds-callout-media-banner__content, .bds-callout-media-banner {
|
||||
.w-100, .bds-logo-square-grid, .bds-callout-media-banner__content, .bds-callout-media-banner {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
@@ -5706,7 +5706,7 @@ textarea.form-control-lg {
|
||||
flex-direction: row !important;
|
||||
}
|
||||
|
||||
.flex-column, .bds-callout-media-banner__actions, .bds-callout-media-banner__text, .bds-callout-media-banner__content, .bds-callout-media-banner > [class*=bds-grid__col] {
|
||||
.flex-column, .bds-logo-square-grid__text, .bds-logo-square-grid__header, .bds-logo-square-grid, .bds-callout-media-banner__text, .bds-callout-media-banner__content, .bds-callout-media-banner > [class*=bds-grid__col], .bds-button-group {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
@@ -5734,7 +5734,7 @@ textarea.form-control-lg {
|
||||
flex-shrink: 1 !important;
|
||||
}
|
||||
|
||||
.flex-wrap, .bds-callout-media-banner__actions {
|
||||
.flex-wrap, .bds-button-group {
|
||||
flex-wrap: wrap !important;
|
||||
}
|
||||
|
||||
@@ -5770,7 +5770,7 @@ textarea.form-control-lg {
|
||||
justify-content: space-evenly !important;
|
||||
}
|
||||
|
||||
.align-items-start, .bds-callout-media-banner__actions {
|
||||
.align-items-start, .bds-button-group {
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
|
||||
@@ -11295,7 +11295,7 @@ aside .active-parent > a {
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.w-100, .bds-callout-media-banner__content, .bds-callout-media-banner {
|
||||
.w-100, .bds-logo-square-grid, .bds-callout-media-banner__content, .bds-callout-media-banner {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -19588,6 +19588,28 @@ html.light .bds-hero-split-media--accent .bds-hero-split-media__subtitle {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.bds-button-group {
|
||||
gap: 8px;
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.bds-button-group {
|
||||
flex-direction: row !important;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.bds-button-group--gap-none {
|
||||
gap: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.bds-button-group--gap-small {
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.bds-callout-media-banner {
|
||||
box-sizing: border-box;
|
||||
min-height: 280px;
|
||||
@@ -19702,17 +19724,6 @@ html.light .bds-hero-split-media--accent .bds-hero-split-media__subtitle {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.bds-callout-media-banner__actions {
|
||||
gap: 8px;
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.bds-callout-media-banner__actions {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.bds-callout-media-banner--default {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
@@ -19829,6 +19840,49 @@ html.light .bds-callout-media-banner--image-text-black .bds-callout-media-banner
|
||||
color: #141414 !important;
|
||||
}
|
||||
|
||||
.bds-logo-square-grid {
|
||||
gap: 24px;
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.bds-logo-square-grid {
|
||||
gap: 32px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.bds-logo-square-grid {
|
||||
gap: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.bds-logo-square-grid__header {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
gap: 24px;
|
||||
}
|
||||
@media (min-width: 576px) {
|
||||
.bds-logo-square-grid__header {
|
||||
gap: 32px;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.bds-logo-square-grid__header {
|
||||
gap: 40px;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.bds-logo-square-grid__text {
|
||||
gap: 8px;
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.bds-logo-square-grid__text {
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
color: #FFFFFF;
|
||||
background-color: #232325;
|
||||
|
||||
@@ -99,7 +99,9 @@ $line-height-base: 1.5;
|
||||
@import "../shared/components/CardIcon/CardIcon.scss";
|
||||
@import "../shared/components/SmallTilesSection/_small-tiles-section.scss";
|
||||
@import "../shared/patterns/HeaderHeroSplitMedia/HeaderHeroSplitMedia.scss";
|
||||
@import "../shared/patterns/ButtonGroup/ButtonGroup.scss";
|
||||
@import "../shared/patterns/CalloutMediaBanner/CalloutMediaBanner.scss";
|
||||
@import "../shared/patterns/LogoSquareGrid/LogoSquareGrid.scss";
|
||||
@import "_code-tabs.scss";
|
||||
@import "_code-walkthrough.scss";
|
||||
@import "_diagrams.scss";
|
||||
|
||||
Reference in New Issue
Block a user