Files
xrpl-dev-portal/shared/components/CardTextIcon/CardTextIconCard.tsx
Calvin b0db5b3a5c Perf pass + migrate home/payments page sections to shared (#3771)
* Perf pass + migrate home/payments page sections to shared

Performance work across every page plus a refactor moving home/payments
page sections into reusable shared components.

JavaScript
- Defer xrpl.js, jQuery, and Bootstrap in the global <head> (redocly.yaml)
  and on the 11 interactive tool pages' inline <script> tags — removes
  ~1MB of render-blocking JS from first paint on every page.
- Drop moment/moment-timezone from the client: remove the dead APEX-2025
  countdown in the navbar AlertBanner; format/parse dates with native
  Date/Intl in the blog and events pages.

Fonts & icons
- Remove render-blocking font-awesome.min.css + webfont. CSS glyphs
  (callouts, breadcrumbs, status labels, tutorials) now render as
  self-contained data-URI SVG masks (styles/_fa-icons.scss); markup icons
  use a shared inline-SVG <Icon> component (shared/components/Icon).
- Preconnect to Google Fonts / osano / GTM origins; preload the primary
  Booton UI font so text no longer waits on the 775KB stylesheet to parse.

Images & rendering
- Re-encode/resize oversized assets: new-york.jpeg 3.86MB→60KB,
  resources-icon.svg 4.2MB→150KB, event date/location SVGs ~1MB→0.5MB.
- Add loading=lazy/decoding=async to shared card components and the
  Video embed iframe.
- Add content-visibility to the events page's ~126-card grid to skip
  off-screen rendering.
- Earlier QA fixes: light-mode link color (#141414), events card grid
  layout, close nav menu on client-side navigation.

Home / payments refactor
- Move home page sections from page-local pages/home/sections/* to reusable
  shared/sections/* components (HomeHero relocated to shared/sections/HomeHero;
  home page now composes CalloutMediaBanner, CardStatsList, LinkTextDirectory,
  LogoSquareGrid, StandardCardGroupSection, FeatureSingleTopic, CarouselFeatured).
- Remove the now-unused pages/payments/sections/* in the same migration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Revert FontAwesome removal (keep it for open-source flexibility)

Restore the FontAwesome webfont + font-awesome.min.css and all FA icon
usages (markup fa-* classes and CSS ::before glyphs), reverting the
consolidation in d4cf4e8581. Other performance changes from that commit
(deferred head scripts, moment removal, image optimization, lazy-loading,
content-visibility, preconnect/preload) are retained.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* cleaning up breadcrumb styling

* sticky side nav

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 14:03:02 -07:00

132 lines
3.6 KiB
TypeScript

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;
/** Semantic heading element. Defaults to `'h3'` so cards enter the document outline.
* Use a heading level that fits the page hierarchy (typically one below the section heading). */
headingAs?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
/** 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,
headingAs: NonNullable<CardTextIconCardProps['headingAs']>,
description: React.ReactNode | string,
icon?: string,
iconAlt?: string,
iconHeight?: number,
iconWidth?: number
) => {
const HeadingElement = headingAs;
return (
<>
<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"
loading="lazy"
decoding="async"
/>
)}
<HeadingElement className="bds-card-text-icon-card__heading sh-md-r">{heading}</HeadingElement>
</div>
<p className="bds-card-text-icon-card__description body-l">
{description}
</p>
</>
);
};
export const CardTextIconCard: React.FC<CardTextIconCardProps> = ({
icon,
iconAlt = '',
heading,
headingAs = 'h3',
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, headingAs, 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;