/* ============================================================================ CARD - shared building blocks ---------------------------------------------------------------------------- Every card in this directory is assembled from the pieces below, so the surface, radius, border, elevation and link behaviour are decided once. STYLING RULE for this whole directory: * colour and radius come from the tier-2 Tailwind bridge (bg-surface-raised, text-text-accent, border-border-subtle, rounded-card, ...) - never a hex, never a tier-1 primitive; * tokens without a bridge utility are spent as arbitrary values, e.g. shadow-[var(--elevation-card)]; * spacing uses the named scale (p-lg = 24, gap-md = 16) so the Figma numbers stay readable; * everything directional uses logical utilities (ps-/pe-/start-/end-) so the Arabic build mirrors without a second stylesheet. Nothing here is surface-aware by hand: the same markup is correct on [data-surface="dark"] and [data-surface="light"] because every colour is a role the surface re-points. ========================================================================= */ import type { CSSProperties, ReactElement, ReactNode } from "react"; import { Link } from "react-router"; import "./card.css"; import type { CardElement, CardIconName, CardImage, CardLinkTarget, } from "./types"; /* --------------------------------------------------------------------------- LINK ------------------------------------------------------------------------ */ /** Anything with a scheme, a protocol-relative host or a fragment is not a route. */ const NON_ROUTE_HREF = /^(?:[a-z][a-z0-9+.-]*:|\/\/|#)/i; /** * React Router sits in the hoisted root of this workspace, where it resolves * its React types from the @types/react 18 that Strapi pulls in, so its props * are declared against a ReactNode that is not ours (no bigint, and a * ReactPortal that still requires children). Re-declaring the component once * here keeps every call site below honest instead of casting at each of them. * Delete this alias when the workspace resolves to a single @types/react. */ const RouterLink = Link as unknown as (props: { to: string; className?: string; target?: string; rel?: string; children: ReactNode; }) => ReactElement; export interface CardLinkProps extends CardLinkTarget { className?: string; children: ReactNode; } /** * Routes internal hrefs through React Router so navigation stays client-side, * and falls back to a plain anchor for external links, mailto: and fragments. */ export function CardLink({ href, className, children, ...rest }: CardLinkProps) { if (NON_ROUTE_HREF.test(href)) { return ( {children} ); } return ( {children} ); } /* --------------------------------------------------------------------------- ICON ------------------------------------------------------------------------ */ /** The assets exported from the Figma icon frame, served from public/. */ const CARD_ICON_SRC: Record = { "arrow-right": "url('/assets/icons/arrow-right.svg')", "arrow-up": "url('/assets/icons/arrow-up.svg')", linkedin: "url('/assets/icons/linkedin.svg')", mail: "url('/assets/icons/mail.svg')", }; export interface CardIconProps { name: CardIconName; /** Designed box in px - 14 for the 14x14 icons, 10 for the trend arrow. */ size: number; /** Mirror in RTL. True for the horizontal arrow, false for the vertical one. */ directional?: boolean; className?: string; } /** * Draws an exported SVG as a mask over a currentColor fill, so the icon keeps * the geometry Figma exported while taking its colour from the token in force. * Always decorative - the adjacent text carries the meaning. */ export function CardIcon({ name, size, directional, className }: CardIconProps) { return (