Files

115 lines
3.6 KiB
TypeScript
Raw Permalink Normal View History

2026-09-11 02:46:56 +02:00
/* ============================================================================
Card.Content - Figma 25:752 (360 x 372)
----------------------------------------------------------------------------
The news / blog / programme card: image, category badge, eyebrow, title,
excerpt and a "Read more" affordance. The whole card is one link.
USAGE
-----
<ul className="grid gap-lg sm:grid-cols-2 lg:grid-cols-3">
<Card.Content
as="li"
href="/en/news/civic-dialogue"
eyebrow="Programme"
badge="Rule of Law"
title="Strengthening Civic Dialogue"
excerpt="Supporting coordinated efforts among institutions, civil society,
and local actors to foster community resilience."
actionLabel="Read more"
image={{ src: "/media/dialogue.jpg", alt: "" }}
/>
</ul>
// Arabic - the copy is the only thing that changes, RTL mirrors itself.
<Card.Content
href="/ar/news/civic-dialogue"
title="تعزيز الحوار المدني"
actionLabel="اقرأ المزيد"
image={{ src: "/media/dialogue.jpg", alt: "" }}
/>
// Above the fold - skip lazy loading for the lead card only.
<Card.Content … image={{ src: "/media/lead.jpg", alt: "", priority: true }} />
========================================================================= */
import {
CardActionHint,
CardEyebrow,
CardLink,
CardMedia,
CardShell,
cx,
} from "./CardShell";
import type { CardElement, CardImage, CardTitleElement } from "./types";
export interface CardContentProps {
/** Where the whole card goes. */
href: string;
title: string;
/**
* Label of the "Read more" affordance. Required rather than defaulted so an
* Arabic page cannot silently ship the English string.
*/
actionLabel: string;
/** Accent kicker above the title, rendered uppercase. */
eyebrow?: string;
excerpt?: string;
/** Chip over the top-inline-start corner of the image ("Rule of Law"). */
badge?: string;
image?: CardImage;
as?: CardElement;
/** Heading level - match the surrounding page outline. */
titleAs?: CardTitleElement;
className?: string;
}
export function CardContent({
href,
title,
actionLabel,
eyebrow,
excerpt,
badge,
image,
as = "article",
titleAs = "h3",
className,
}: CardContentProps) {
const Title = titleAs;
return (
<CardShell as={as} linked className={cx("flex-col overflow-hidden", className)}>
{image ? (
<CardMedia image={image} className="aspect-[9/5] w-full">
{/* The chip sits on a scrim over the photo, so its label has to stay
light on both surfaces - text-on-accent is the only always-white
role, text-primary would go dark on the light surface. */}
{badge ? (
<span className="absolute start-md top-md rounded-chip bg-overlay-scrim px-xs py-2xs label-md text-text-on-accent backdrop-blur-[4px]">
2026-09-11 02:46:56 +02:00
{badge}
</span>
) : null}
</CardMedia>
) : null}
<div className="flex flex-col gap-md p-lg">
<div className="flex flex-col gap-xs">
{eyebrow ? <CardEyebrow>{eyebrow}</CardEyebrow> : null}
<Title className="sh-card-title body-lg-bold text-text-primary">
<CardLink href={href} className="sh-card-link">
{title}
</CardLink>
</Title>
{excerpt ? (
<p className="body-sm text-text-secondary line-clamp-3">{excerpt}</p>
) : null}
</div>
<CardActionHint label={actionLabel} />
</div>
</CardShell>
);
}