"use client" import type { KeyboardEvent, ReactNode } from "react" import Link from "next/link" import { Frame, FramePanel } from "@/components/reui/frame" import { Badge } from "@/components/reui/badge" import { IconTile } from "@/components/reui/icon-tile" import { Skeleton } from "@/components/ui/skeleton" import { cn } from "@/lib/utils" import { kpiCols } from "./kpi-cols" export type KpiStatVariant = "default" | "warning" | "destructive" /** * KPI tile — horizontal compact hybrid (icon left + label/Badge + value). * @see https://reui.io/preview/base/stats-12 */ export type KpiStatItem = { id?: string label: ReactNode value: ReactNode hint?: ReactNode href?: string onSelect?: () => void onClick?: () => void selected?: boolean active?: boolean icon?: ReactNode iconClassName?: string variant?: KpiStatVariant footer?: ReactNode } export type KpiStatCardData = KpiStatItem & { id: string } const DEFAULT_ICON_CLASS = "text-muted-foreground [&_svg]:text-current" const VALUE_VARIANT_CLASS: Record = { default: "text-foreground", warning: "text-warning", destructive: "text-destructive", } function handleCardKeyDown(onActivate: () => void, event: KeyboardEvent) { if (event.key === "Enter" || event.key === " ") { event.preventDefault() onActivate() } } function resolveActivate(item: KpiStatItem): (() => void) | undefined { return item.onClick ?? item.onSelect } function isSelected(item: KpiStatItem): boolean { return Boolean(item.selected ?? item.active) } function resolveFooter(item: KpiStatItem): ReactNode { if (item.footer) return item.footer if (typeof item.hint === "string") { return ( {item.hint} ) } if (item.hint) return item.hint return null } function KpiStatCardBody({ item }: { item: KpiStatItem }) { const footer = resolveFooter(item) const valueVariant = item.variant ?? "default" return (
{item.icon ? ( ) : null}
{item.label}
{footer ? (
{footer}
) : null}
{item.value}
{footer ? (
{footer}
) : null}
) } function panelClassName(item: KpiStatItem, className?: string) { const onActivate = resolveActivate(item) const clickable = Boolean(item.href || onActivate) const selected = isSelected(item) return cn( "relative isolate flex h-full min-w-0 flex-col", clickable && "hover:bg-muted/40 focus-within:ring-ring cursor-pointer transition-colors focus-within:ring-2", selected && "ring-primary/30 bg-muted/30 ring-1", className, ) } export function KpiStatCardTile({ item, embedded = false, className, }: { item: KpiStatItem embedded?: boolean className?: string }) { const onActivate = resolveActivate(item) const panelClass = panelClassName(item, className) let panel: ReactNode if (item.href) { panel = ( ) } else if (onActivate) { panel = ( handleCardKeyDown(onActivate, e)} > ) } else { panel = ( ) } if (embedded) { return {panel} } return {panel} } function KpiStatGridSkeleton({ count }: { count: number }) { return (
{Array.from({ length: count }).map((_, index) => (
))}
) } interface KpiStatGridProps { items?: KpiStatItem[] cards?: KpiStatCardData[] isLoading?: boolean emptyMessage?: ReactNode emptyIcon?: ReactNode className?: string skeletonCount?: number embedded?: boolean "aria-label"?: string } function KpiStatCardItem({ item }: { item: KpiStatItem }) { const onActivate = resolveActivate(item) const panelClass = panelClassName(item) if (item.href) { return ( ) } if (onActivate) { return ( handleCardKeyDown(onActivate, e)} > ) } return ( ) } /** * Hybrid KPI — horizontal compact layout (icon left). * Preview: https://reui.io/preview/base/stats-12 */ export function KpiStatGrid({ items, cards, isLoading = false, emptyMessage, emptyIcon, className, skeletonCount = 4, embedded = false, "aria-label": ariaLabel, }: KpiStatGridProps) { if (isLoading) { return } const list = items ?? cards ?? [] if (list.length === 0 && (emptyMessage || emptyIcon)) { return ( {emptyIcon} {emptyMessage ? (

{emptyMessage}

) : null}
) } if (embedded) { return (
{list.map((item, index) => ( ))}
) } return (
{list.map((item, index) => ( ))}
) } export function kpiStatItemKey(item: KpiStatItem, index: number): string { if (item.id) return item.id if (typeof item.label === "string") return item.label return `kpi-${index}` }