115 lines
3.6 KiB
TypeScript
115 lines
3.6 KiB
TypeScript
/* ============================================================================
|
|
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-control-lg bg-overlay-scrim px-xs py-2xs label-md text-text-on-accent backdrop-blur-[4px]">
|
|
{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>
|
|
);
|
|
}
|