Add CardOffgrid component with showcase and documentation

- Introduced the CardOffgrid component, designed for displaying feature highlights with customizable icons, titles, and descriptions.
- Implemented two color variants: neutral and green, with interactive states and a unique hover animation.
- Created a comprehensive showcase page demonstrating all variants and states of the CardOffgrid component.
- Added detailed documentation covering usage guidelines, best practices, and API reference.
- Included SCSS styles for the CardOffgrid component, ensuring compatibility with both light and dark themes.
This commit is contained in:
akcodez
2025-12-05 11:41:18 -08:00
parent 2dbb111943
commit 57898ab010
8 changed files with 2042 additions and 218 deletions

View File

@@ -0,0 +1,436 @@
# CardOffgrid Component - Usage Guide
## Overview
`CardOffgrid` is a feature highlight card component designed to showcase key capabilities, features, or resources. It combines an icon, title, and description in a visually engaging, interactive card format with smooth hover animations.
**Use CardOffgrid when:**
- Highlighting key features or capabilities
- Creating feature grids or showcases
- Linking to important documentation or resources
- Presenting product/service highlights
**Don't use CardOffgrid for:**
- Simple content cards (use standard Bootstrap cards)
- Navigation items (use navigation components)
- Data display (use tables or data components)
- Long-form content (use article/page layouts)
---
## When to Use Each Variant
### Neutral Variant (`variant="neutral"`)
**Use for:**
- General feature highlights
- Standard content cards
- Secondary or supporting features
- When you want subtle, professional presentation
**Example use cases:**
- Documentation sections
- Feature lists
- Service offerings
- Standard informational cards
### Green Variant (`variant="green"`)
**Use for:**
- Primary or featured highlights
- Call-to-action cards
- Important announcements
- Brand-emphasized content
**Example use cases:**
- Hero feature cards
- Primary CTAs
- Featured resources
- Branded highlights
---
## Content Best Practices
### Title Guidelines
**Do:**
- Keep titles concise (1-3 words ideal)
- Use line breaks (`\n`) for multi-word titles when needed
- Make titles action-oriented or descriptive
- Examples: "Onchain Metadata", "Token\nManagement", "Cross-Chain\nBridges"
**Don't:**
- Write long sentences as titles
- Use more than 2 lines
- Include punctuation (periods, commas)
- Make titles too generic ("Feature", "Service")
### Description Guidelines
**Do:**
- Write 1-2 sentences (15-25 words ideal)
- Focus on benefits or key information
- Use clear, simple language
- Keep descriptions scannable
**Don't:**
- Write paragraphs (save for full pages)
- Use jargon without context
- Include multiple ideas in one description
- Make descriptions too short (< 10 words) or too long (> 40 words)
### Icon Guidelines
**Do:**
- Use SVG icons for crisp rendering
- Choose icons that represent the feature clearly
- Ensure icons are recognizable at 68×68px
- Use consistent icon style across cards
**Don't:**
- Use low-resolution raster images
- Choose overly complex icons
- Mix icon styles within a single grid
- Use icons that don't relate to the content
---
## Interaction Patterns
### Using `onClick` vs `href`
**Use `onClick` when:**
- Triggering JavaScript actions (modals, analytics, state changes)
- Opening external links in new tabs
- Performing client-side navigation
- Handling complex interactions
```tsx
<CardOffgrid
variant="neutral"
icon={<AnalyticsIcon />}
title="View Analytics"
description="See detailed usage statistics and insights."
onClick={() => {
trackEvent('analytics_viewed');
openModal('analytics');
}}
/>
```
**Use `href` when:**
- Navigating to internal pages
- Linking to documentation
- Simple page navigation
- SEO-friendly links
```tsx
<CardOffgrid
variant="green"
icon="/icons/docs.svg"
title="API\nReference"
description="Complete API documentation and examples."
href="/docs/api"
/>
```
### Disabled State
Use `disabled` when:
- Feature is coming soon
- Feature requires authentication
- Feature is temporarily unavailable
- You want to show but not allow interaction
```tsx
<CardOffgrid
variant="neutral"
icon={<BetaIcon />}
title="Coming\nSoon"
description="This feature will be available in the next release."
disabled
/>
```
---
## Layout Best Practices
### Grid Layouts
**Recommended grid patterns:**
```tsx
// 2-column grid (desktop)
<div className="row">
<div className="col-md-6 mb-4">
<CardOffgrid {...props1} />
</div>
<div className="col-md-6 mb-4">
<CardOffgrid {...props2} />
</div>
</div>
// 3-column grid (desktop)
<div className="row">
{cards.map(card => (
<div key={card.id} className="col-md-4 mb-4">
<CardOffgrid {...card} />
</div>
))}
</div>
```
**Spacing:**
- Use Bootstrap spacing utilities (`mb-4`, `mb-5`) between cards
- Maintain consistent spacing in grids
- Cards are responsive and stack on mobile automatically
### Single Card Usage
For hero sections or featured highlights:
```tsx
<div className="d-flex justify-content-center">
<CardOffgrid
variant="green"
icon={<FeaturedIcon />}
title="New Feature"
description="Introducing our latest capability..."
href="/features/new"
/>
</div>
```
---
## Accessibility Best Practices
### Semantic HTML
The component automatically renders as:
- `<button>` when using `onClick`
- `<a>` when using `href`
This ensures proper semantic meaning for screen readers.
### Keyboard Navigation
**Always test:**
- Tab navigation moves focus to cards
- Enter/Space activates cards
- Focus ring is clearly visible
- Focus order follows logical reading order
### Screen Reader Content
**Ensure:**
- Titles are descriptive and unique
- Descriptions provide context
- Icons have appropriate `aria-hidden="true"` (handled automatically)
- Disabled cards communicate their state
### Color Contrast
All variants meet WCAG AA standards:
- Dark mode: White text on colored backgrounds
- Light mode: Dark text on light backgrounds
- Focus rings provide sufficient contrast
---
## Common Patterns
### Feature Showcase Grid
```tsx
const features = [
{
variant: 'green',
icon: <TokenIcon />,
title: 'Token\nManagement',
description: 'Create and manage fungible and non-fungible tokens.',
href: '/docs/tokens'
},
{
variant: 'neutral',
icon: <MetadataIcon />,
title: 'Onchain\nMetadata',
description: 'Store key asset information using simple APIs.',
href: '/docs/metadata'
},
// ... more features
];
<div className="row">
{features.map((feature, index) => (
<div key={index} className="col-md-4 mb-4">
<CardOffgrid {...feature} />
</div>
))}
</div>
```
### Mixed Variants for Hierarchy
Use green variant for primary features, neutral for supporting:
```tsx
<div className="row">
<div className="col-md-6 mb-4">
<CardOffgrid
variant="green" // Primary feature
icon={<PrimaryIcon />}
title="Main Feature"
description="Our flagship capability..."
href="/feature/main"
/>
</div>
<div className="col-md-6 mb-4">
<CardOffgrid
variant="neutral" // Supporting feature
icon={<SupportIcon />}
title="Supporting Feature"
description="Complementary capability..."
href="/feature/support"
/>
</div>
</div>
```
### Coming Soon Pattern
```tsx
<CardOffgrid
variant="neutral"
icon={<ComingSoonIcon />}
title="Coming\nSoon"
description="This feature is currently in development and will be available soon."
disabled
/>
```
---
## Performance Considerations
### Icon Optimization
**Best practices:**
- Use SVG React components (inlined) for small icons
- Use optimized SVG files for image icons
- Avoid large raster images
- Consider lazy loading for below-the-fold cards
### Rendering Performance
- Cards are lightweight components
- Hover animations use CSS transforms (GPU-accelerated)
- No heavy JavaScript calculations
- Suitable for grids with 10+ cards
---
## Troubleshooting
### Common Issues
**Card not clickable:**
- Ensure `onClick` or `href` is provided
- Check that `disabled` is not set to `true`
- Verify no parent element is blocking pointer events
**Icon not displaying:**
- Verify icon path is correct (if using string)
- Check icon component is properly imported
- Ensure icon fits within 68×68px bounds
**Hover animation not working:**
- Check browser supports CSS `clip-path`
- Verify no conflicting CSS is overriding transitions
- Test in different browsers
**Focus ring not visible:**
- Ensure keyboard navigation (Tab key)
- Check focus ring color contrasts with background
- Verify `outline-offset: 2px` is applied
---
## Design System Integration
### Color Tokens
All colors reference `styles/_colors.scss`:
- Dark mode (default): Uses `$gray-500`, `$gray-400`, `$green-300`, `$green-200`
- Light mode (`html.light`): Uses `$gray-200`, `$gray-300`, `$green-200`, `$green-300`
### Typography
- Title: Booton Light, 32px, -1px letter-spacing
- Description: Booton Light, 18px, -0.5px letter-spacing
### Spacing
- Card padding: 24px
- Content gap: 40px (between title and description)
- Icon container: 84×84px
---
## Figma References
- **Light Mode Colors**: [Figma Design - Light Mode](https://www.figma.com/design/vwDwMJ3mFrAklj5zvZwX5M/Card---OffGrid?node-id=8001-1963&m=dev)
- **Dark Mode Colors**: [Figma Design - Dark Mode](https://www.figma.com/design/vwDwMJ3mFrAklj5zvZwX5M/Card---OffGrid?node-id=8001-2321&m=dev)
- **Animation Specs**: [Figma Design - Storyboard](https://www.figma.com/design/vwDwMJ3mFrAklj5zvZwX5M/Card---OffGrid?node-id=8007-1096&m=dev)
---
## Component API
```typescript
interface CardOffgridProps {
/** Color variant: 'neutral' (default) or 'green' */
variant?: 'neutral' | 'green';
/** Icon element (ReactNode) or image path (string) */
icon: React.ReactNode | string;
/** Card title - use \n for line breaks */
title: string;
/** Card description text (1-2 sentences) */
description: string;
/** Click handler - renders as <button> */
onClick?: () => void;
/** Link destination - renders as <a> */
href?: string;
/** Disabled state - prevents interaction */
disabled?: boolean;
/** Additional CSS classes */
className?: string;
}
```
---
## Quick Reference
| Use Case | Variant | Interaction |
|----------|---------|-------------|
| Standard feature | `neutral` | `href` or `onClick` |
| Primary feature | `green` | `href` or `onClick` |
| Coming soon | `neutral` | `disabled` |
| Feature grid | Mix both | `href` preferred |
| Hero section | `green` | `href` |
---
## Examples
See the [CardOffgrid Showcase](/about/card-offgrid-showcase) for live examples and interactive demos.

View File

@@ -0,0 +1,376 @@
// BDS CardOffgrid Component Styles
// Brand Design System - Feature card with icon, title, and description
//
// Naming Convention: BEM with 'bds' namespace
// .bds-card-offgrid - Base card (resets button/anchor styles)
// .bds-card-offgrid--neutral - Neutral gray color variant (default)
// .bds-card-offgrid--green - Green color variant
// .bds-card-offgrid--disabled - Disabled state
// .bds-card-offgrid__overlay - Hover gradient overlay
// .bds-card-offgrid__icon-container - Icon wrapper (84x84px)
// .bds-card-offgrid__icon-image - Image icon styling
// .bds-card-offgrid__content - Title and description wrapper
// .bds-card-offgrid__title - Card title (32px)
// .bds-card-offgrid__description - Card description (18px)
//
// Note: This file is imported within xrpl.scss after Bootstrap and project
// variables are loaded, so $grid-breakpoints, colors, and mixins are available.
//
// Theme: Site defaults to DARK mode. Light mode uses html.light selector.
// =============================================================================
// Design Tokens
// =============================================================================
// Dimensions (from Figma design spec)
$bds-card-offgrid-width: 400px;
$bds-card-offgrid-height: 480px;
$bds-card-offgrid-padding: 24px;
$bds-card-offgrid-icon-container: 84px;
$bds-card-offgrid-icon-size: 68px;
$bds-card-offgrid-content-gap: 40px;
// Animation (from Figma design spec)
$bds-card-offgrid-transition-duration: 200ms;
$bds-card-offgrid-transition-timing: cubic-bezier(0.98, 0.12, 0.12, 0.98);
// Typography - Title
$bds-card-offgrid-title-size: 32px;
$bds-card-offgrid-title-line-height: 40px;
$bds-card-offgrid-title-letter-spacing: -1px;
// Typography - Description
$bds-card-offgrid-desc-size: 18px;
$bds-card-offgrid-desc-line-height: 26.1px;
$bds-card-offgrid-desc-letter-spacing: -0.5px;
// -----------------------------------------------------------------------------
// Dark Mode Colors (Default)
// -----------------------------------------------------------------------------
// Neutral variant - Dark Mode
$bds-card-offgrid-neutral-default-dark: $gray-500; // #72777E
$bds-card-offgrid-neutral-hover-dark: $gray-400; // #8A919A
$bds-card-offgrid-neutral-pressed-dark: rgba($gray-500, 0.7); // 70% opacity
$bds-card-offgrid-neutral-text-dark: $white; // #FFFFFF
// Green variant - Dark Mode
$bds-card-offgrid-green-default-dark: $green-300; // #21E46B
$bds-card-offgrid-green-hover-dark: $green-200; // #70EE97
$bds-card-offgrid-green-pressed-dark: $green-400; // #0DAA3E
$bds-card-offgrid-green-text-dark: $black; // #000000
// Disabled - Dark Mode (30% opacity on gray-500)
$bds-card-offgrid-disabled-opacity-dark: 0.3;
$bds-card-offgrid-disabled-text-dark: $white; // #FFFFFF
// Focus ring - Dark Mode
$bds-card-offgrid-focus-color-dark: $white; // #FFFFFF
// -----------------------------------------------------------------------------
// Light Mode Colors
// -----------------------------------------------------------------------------
// Neutral variant - Light Mode
$bds-card-offgrid-neutral-default-light: $gray-200; // #E6EAF0
$bds-card-offgrid-neutral-hover-light: $gray-300; // #CAD4DF
$bds-card-offgrid-neutral-pressed-light: $gray-400; // #8A919A
$bds-card-offgrid-neutral-text-light: $gray-900; // #111112
$bds-card-offgrid-neutral-text-hover-light: $black; // #000000
// Green variant - Light Mode
$bds-card-offgrid-green-default-light: $green-200; // #70EE97
$bds-card-offgrid-green-hover-light: $green-300; // #21E46B
$bds-card-offgrid-green-pressed-light: $green-400; // #0DAA3E
$bds-card-offgrid-green-text-light: $black; // #000000
// Disabled - Light Mode
$bds-card-offgrid-disabled-bg-light: $gray-100; // #F0F3F7
$bds-card-offgrid-disabled-text-light: $gray-500; // #72777E
// Focus ring - Light Mode
$bds-card-offgrid-focus-color-light: $gray-900; // #111112
// =============================================================================
// Base Card Styles
// =============================================================================
.bds-card-offgrid {
// Reset button/anchor styles
appearance: none;
border: none;
background: none;
padding: 0;
margin: 0;
font: inherit;
color: inherit;
text-decoration: none;
cursor: pointer;
text-align: left;
// Card layout
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
width: $bds-card-offgrid-width;
height: $bds-card-offgrid-height;
padding: $bds-card-offgrid-padding;
overflow: hidden;
// Animation
transition: background-color $bds-card-offgrid-transition-duration $bds-card-offgrid-transition-timing,
opacity $bds-card-offgrid-transition-duration $bds-card-offgrid-transition-timing;
// Focus styles - Dark Mode (default)
// 1px gap between card and focus ring
&:focus {
outline: 2px solid $bds-card-offgrid-focus-color-dark;
outline-offset: 1px;
}
&:focus:not(:focus-visible) {
outline: none;
}
&:focus-visible {
outline: 2px solid $bds-card-offgrid-focus-color-dark;
outline-offset: 2px;
}
}
// =============================================================================
// Overlay (Color wipe animation - "Window Shade" effect)
// =============================================================================
// Hover in: shade rises from bottom to top (reveals)
// Hover out: shade falls from top to bottom (hides)
.bds-card-offgrid__overlay {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 0;
// Default: hidden (shade is "rolled up" at bottom, top is 100% clipped)
// When transitioning TO this state, the top inset increases = shade falls down
clip-path: inset(100% 0 0 0);
transition: clip-path $bds-card-offgrid-transition-duration $bds-card-offgrid-transition-timing;
}
// Hovered state: shade fully raised (visible)
// When transitioning TO this state, the top inset decreases = shade rises up
.bds-card-offgrid--hovered .bds-card-offgrid__overlay {
clip-path: inset(0 0 0 0);
}
// =============================================================================
// Icon Container
// =============================================================================
.bds-card-offgrid__icon-container {
position: relative;
z-index: 1; // Above overlay
display: flex;
align-items: center;
justify-content: center;
width: $bds-card-offgrid-icon-container;
height: $bds-card-offgrid-icon-container;
flex-shrink: 0;
// Icon sizing
> * {
max-width: $bds-card-offgrid-icon-size;
max-height: $bds-card-offgrid-icon-size;
}
}
.bds-card-offgrid__icon-image {
width: auto;
height: auto;
max-width: $bds-card-offgrid-icon-size;
max-height: $bds-card-offgrid-icon-size;
object-fit: contain;
}
// =============================================================================
// Content (Title + Description)
// =============================================================================
.bds-card-offgrid__content {
position: relative;
z-index: 1; // Above overlay
display: flex;
flex-direction: column;
gap: $bds-card-offgrid-content-gap;
}
.bds-card-offgrid__title {
font-size: $bds-card-offgrid-title-size;
font-weight: 300; // Light
line-height: $bds-card-offgrid-title-line-height;
letter-spacing: $bds-card-offgrid-title-letter-spacing;
white-space: pre-wrap;
}
.bds-card-offgrid__description {
font-size: $bds-card-offgrid-desc-size;
font-weight: 300; // Light
line-height: $bds-card-offgrid-desc-line-height;
letter-spacing: $bds-card-offgrid-desc-letter-spacing;
}
// =============================================================================
// DARK MODE (Default)
// =============================================================================
// -----------------------------------------------------------------------------
// Neutral Variant - Dark Mode
// -----------------------------------------------------------------------------
.bds-card-offgrid--neutral {
background-color: $bds-card-offgrid-neutral-default-dark;
color: $bds-card-offgrid-neutral-text-dark;
// Overlay color for hover wipe
.bds-card-offgrid__overlay {
background-color: $bds-card-offgrid-neutral-hover-dark;
}
// Pressed state
&:active:not(.bds-card-offgrid--disabled) {
.bds-card-offgrid__overlay {
background-color: $bds-card-offgrid-neutral-pressed-dark;
clip-path: inset(0 0 0 0);
}
}
}
// -----------------------------------------------------------------------------
// Green Variant - Dark Mode
// -----------------------------------------------------------------------------
.bds-card-offgrid--green {
background-color: $bds-card-offgrid-green-default-dark;
color: $bds-card-offgrid-green-text-dark;
// Overlay color for hover wipe
.bds-card-offgrid__overlay {
background-color: $bds-card-offgrid-green-hover-dark;
}
// Pressed state
&:active:not(.bds-card-offgrid--disabled) {
.bds-card-offgrid__overlay {
background-color: $bds-card-offgrid-green-pressed-dark;
clip-path: inset(0 0 0 0);
}
}
}
// -----------------------------------------------------------------------------
// Disabled State - Dark Mode
// -----------------------------------------------------------------------------
.bds-card-offgrid--disabled {
background-color: $bds-card-offgrid-neutral-default-dark;
color: $bds-card-offgrid-disabled-text-dark;
opacity: $bds-card-offgrid-disabled-opacity-dark;
cursor: not-allowed;
&:focus,
&:focus-visible {
outline: none;
}
}
// =============================================================================
// LIGHT MODE (html.light)
// =============================================================================
html.light {
// Focus styles - Light Mode
.bds-card-offgrid {
&:focus {
outline-color: $bds-card-offgrid-focus-color-light;
}
&:focus-visible {
outline-color: $bds-card-offgrid-focus-color-light;
}
}
// ---------------------------------------------------------------------------
// Neutral Variant - Light Mode
// ---------------------------------------------------------------------------
.bds-card-offgrid--neutral {
background-color: $bds-card-offgrid-neutral-default-light;
color: $bds-card-offgrid-neutral-text-light;
// Overlay color for hover wipe
.bds-card-offgrid__overlay {
background-color: $bds-card-offgrid-neutral-hover-light;
}
// Text color on hover
&.bds-card-offgrid--hovered {
color: $bds-card-offgrid-neutral-text-hover-light;
}
// Pressed state
&:active:not(.bds-card-offgrid--disabled) {
.bds-card-offgrid__overlay {
background-color: $bds-card-offgrid-neutral-pressed-light;
clip-path: inset(0 0 0 0);
}
}
}
// ---------------------------------------------------------------------------
// Green Variant - Light Mode
// ---------------------------------------------------------------------------
.bds-card-offgrid--green {
background-color: $bds-card-offgrid-green-default-light;
color: $bds-card-offgrid-green-text-light;
// Overlay color for hover wipe
.bds-card-offgrid__overlay {
background-color: $bds-card-offgrid-green-hover-light;
}
// Pressed state
&:active:not(.bds-card-offgrid--disabled) {
.bds-card-offgrid__overlay {
background-color: $bds-card-offgrid-green-pressed-light;
clip-path: inset(0 0 0 0);
}
}
}
// ---------------------------------------------------------------------------
// Disabled State - Light Mode
// ---------------------------------------------------------------------------
.bds-card-offgrid--disabled {
background-color: $bds-card-offgrid-disabled-bg-light;
color: $bds-card-offgrid-disabled-text-light;
opacity: 1; // Reset opacity, use background color instead
.bds-card-offgrid__icon-container {
opacity: 0.5;
}
}
}
// =============================================================================
// Responsive Styles
// =============================================================================
@media (max-width: 767px) {
.bds-card-offgrid {
width: 100%;
min-height: $bds-card-offgrid-height;
height: auto;
}
}

View File

@@ -0,0 +1,167 @@
import React, { useState, useCallback } from 'react';
export interface CardOffgridProps {
/** Color variant of the card */
variant?: 'neutral' | 'green';
/** Icon element or image source */
icon: React.ReactNode | string;
/** Card title (supports multi-line via \n) */
title: string;
/** Card description text */
description: string;
/** Click handler */
onClick?: () => void;
/** Link destination (renders as anchor if provided) */
href?: string;
/** Disabled state */
disabled?: boolean;
/** Optional className for custom styling */
className?: string;
}
/**
* BDS CardOffgrid Component
*
* A versatile card component for displaying feature highlights with an icon,
* title, and description. Supports neutral and green color variants with
* interactive states (hover, focus, pressed, disabled).
*
* Features a "window shade" color wipe animation:
* - Hover in: shade rises from bottom to top (reveals hover color)
* - Hover out: shade falls from top to bottom (hides hover color)
*
* @example
* // Basic neutral card
* <CardOffgrid
* variant="neutral"
* icon={<MetadataIcon />}
* title="Onchain Metadata"
* description="Easily store key asset information."
* onClick={() => console.log('clicked')}
* />
*
* @example
* // Green card with link
* <CardOffgrid
* variant="green"
* icon="/icons/metadata.svg"
* title="Onchain Metadata"
* description="Easily store key asset information."
* href="/docs/metadata"
* />
*/
export const CardOffgrid: React.FC<CardOffgridProps> = ({
variant = 'neutral',
icon,
title,
description,
onClick,
href,
disabled = false,
className = '',
}) => {
// Track hover state for animation
const [isHovered, setIsHovered] = useState(false);
const handleMouseEnter = useCallback(() => {
if (!disabled) {
setIsHovered(true);
}
}, [disabled]);
const handleMouseLeave = useCallback(() => {
if (!disabled) {
setIsHovered(false);
}
}, [disabled]);
// Build class names using BEM with bds namespace
const classNames = [
'bds-card-offgrid',
`bds-card-offgrid--${variant}`,
disabled && 'bds-card-offgrid--disabled',
isHovered && 'bds-card-offgrid--hovered',
className,
]
.filter(Boolean)
.join(' ');
// Render icon - supports both React nodes and image URLs
const renderIcon = () => {
if (typeof icon === 'string') {
return (
<img
src={icon}
alt=""
className="bds-card-offgrid__icon-image"
aria-hidden="true"
/>
);
}
return icon;
};
// Split title by newline for multi-line support
const renderTitle = () => {
const lines = title.split('\n');
return lines.map((line, index) => (
<React.Fragment key={index}>
{line}
{index < lines.length - 1 && <br />}
</React.Fragment>
));
};
// Common content for both button and anchor
const content = (
<>
{/* Hover color wipe overlay */}
<span className="bds-card-offgrid__overlay" aria-hidden="true" />
<span className="bds-card-offgrid__icon-container">
{renderIcon()}
</span>
<span className="bds-card-offgrid__content">
<span className="bds-card-offgrid__title">
{renderTitle()}
</span>
<span className="bds-card-offgrid__description">
{description}
</span>
</span>
</>
);
// Render as anchor if href is provided
if (href && !disabled) {
return (
<a
href={href}
className={classNames}
aria-disabled={disabled}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{content}
</a>
);
}
// Render as button for onClick or disabled state
return (
<button
type="button"
className={classNames}
onClick={disabled ? undefined : onClick}
disabled={disabled}
aria-disabled={disabled}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{content}
</button>
);
};
export default CardOffgrid;

View File

@@ -0,0 +1,2 @@
export { CardOffgrid, type CardOffgridProps } from './CardOffgrid';
export { default } from './CardOffgrid';