40 lines
1.5 KiB
TypeScript
40 lines
1.5 KiB
TypeScript
import type { ReactNode } from "react";
|
|
import ArrowDownIcon from "@/assets/icons/arrow-down.svg?react";
|
|
import ArrowUpIcon from "@/assets/icons/arrow-up.svg?react";
|
|
import MinusIcon from "@/assets/icons/minus.svg?react";
|
|
|
|
export type MetricCardProps = {
|
|
label: string;
|
|
value: string | number;
|
|
subtext?: string;
|
|
icon?: ReactNode;
|
|
trend?: "up" | "down" | "neutral";
|
|
};
|
|
|
|
const trendConfig = {
|
|
up: { Icon: ArrowUpIcon, className: "text-violet-600" },
|
|
down: { Icon: ArrowDownIcon, className: "text-[var(--color-status-critical-text)]" },
|
|
neutral: { Icon: MinusIcon, className: "text-neutral-60" },
|
|
} as const;
|
|
|
|
export function MetricCard({ label, value, subtext, icon, trend }: MetricCardProps) {
|
|
const TrendGlyph = trend ? trendConfig[trend].Icon : null;
|
|
const trendColor = trend ? trendConfig[trend].className : "";
|
|
|
|
return (
|
|
<div className="rounded-2xl border border-neutral-20 shadow-sm bg-white p-5 relative animate-fade-in-up">
|
|
{icon ? <div className="absolute top-4 right-4 text-neutral-40 [&_svg]:block">{icon}</div> : null}
|
|
<p className="body-14 text-neutral-60 mb-1 break-keep">{label}</p>
|
|
<div className="flex items-end gap-2 min-w-0">
|
|
<span className="text-3xl font-bold text-navy-900 break-keep">{value}</span>
|
|
{trend && TrendGlyph ? (
|
|
<span className={`mb-1 ${trendColor}`}>
|
|
<TrendGlyph width={18} height={18} aria-hidden />
|
|
</span>
|
|
) : null}
|
|
</div>
|
|
{subtext ? <p className="label-12 text-neutral-60 mt-1 break-keep">{subtext}</p> : null}
|
|
</div>
|
|
);
|
|
}
|