A player stat card with a headshot placeholder, team accent band, and a season stat grid in tabular figures.
npx shadcn@latest add @paragon/player-card"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
export interface PlayerStat {
label: string;
value: string;
}
export interface PlayerCardProps extends React.ComponentProps<"div"> {
name?: string;
/** e.g. "Forward · #0". */
role?: string;
team?: string;
teamColor?: string;
/** Two initials for the headshot placeholder. */
initials?: string;
/** Season line, e.g. "2025–26 · 64 GP". */
season?: string;
stats?: PlayerStat[];
}
const DEFAULT_STATS: PlayerStat[] = [
{ label: "PPG", value: "27.4" },
{ label: "RPG", value: "8.1" },
{ label: "APG", value: "4.9" },
{ label: "FG%", value: "47.2" },
];
/**
* A player stat card: a gradient headshot placeholder with initials, name and
* role, a team accent bar, and a season stat grid in tabular figures. The
* accent color threads through the header band and stat separators. Static —
* no motion; reads cleanly in both themes.
*/
export function PlayerCard({
name = "Jayson Tatum",
role = "Forward · #0",
team = "Boston Celtics",
teamColor = "#008348",
initials = "JT",
season = "2025–26 · 64 GP",
stats = DEFAULT_STATS,
className,
...props
}: PlayerCardProps) {
return (
<div
data-slot="player-card"
className={cn(
"w-full max-w-xs overflow-hidden rounded-xl bg-card text-card-foreground shadow-border",
className,
)}
{...props}
>
<div
className="relative h-20"
style={{
background: `linear-gradient(120deg, ${teamColor}, color-mix(in oklch, ${teamColor} 45%, black))`,
}}
>
<span
aria-hidden
className="absolute -bottom-8 left-4 grid size-20 place-items-center rounded-full border-4 border-card text-xl font-bold text-white/90"
style={{
background: `linear-gradient(135deg, color-mix(in oklch, ${teamColor} 70%, white), ${teamColor})`,
}}
>
{initials}
</span>
</div>
<div className="px-4 pt-10 pb-4">
<h3 className="text-base font-bold leading-tight">{name}</h3>
<p className="text-xs text-muted-foreground">{role}</p>
<div className="mt-1.5 flex items-center gap-1.5">
<span
aria-hidden
className="size-2 rounded-full"
style={{ background: teamColor }}
/>
<span className="text-xs font-medium text-muted-foreground">
{team}
</span>
</div>
<div className="mt-4 grid grid-cols-4 divide-x divide-border rounded-lg bg-muted/50 py-2.5">
{stats.map((s) => (
<div key={s.label} className="px-1 text-center">
<div className="text-lg font-bold tabular-nums leading-none">
{s.value}
</div>
<div className="mt-1 text-[10px] font-medium tracking-wide text-muted-foreground uppercase">
{s.label}
</div>
</div>
))}
</div>
<p className="mt-3 text-center text-[11px] tabular-nums text-muted-foreground">
{season}
</p>
</div>
</div>
);
}