oxios 1.5.2

Oxios Agent OS — Agent Operating System powered by oxi-sdk
import { Minus, TrendingDown, TrendingUp } from 'lucide-react'
import { useMemo } from 'react'
import { Area, AreaChart, ResponsiveContainer } from 'recharts'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { cn, cssVarToRgb } from '@/lib/utils'

export type SparkColor = 'info' | 'success' | 'warning' | 'primary' | 'error' | 'accent' | 'muted'

/**
 * CSS custom property names for each spark color.
 * Resolved at runtime via cssVarToRgb() because Recharts SVG
 * attributes don't support CSS variables directly.
 */
const COLOR_VARS: Record<SparkColor, { stroke: string; fill: string }> = {
  info: { stroke: '--color-info', fill: '--color-info-muted' },
  success: { stroke: '--color-success', fill: '--color-success-muted' },
  warning: { stroke: '--color-warning', fill: '--color-warning-muted' },
  primary: { stroke: '--color-primary', fill: '--color-primary' },
  error: { stroke: '--color-error', fill: '--color-error-muted' },
  accent: { stroke: '--color-info', fill: '--color-info-muted' },
  muted: { stroke: '--color-muted-foreground', fill: '--color-muted-foreground' },
}

/**
 * Resolve spark color tokens to computed RGB strings.
 * Memoized per sparkColor so we don't thrash getComputedStyle.
 */
function useSparkColors(sparkColor: SparkColor) {
  return useMemo(() => {
    const vars = COLOR_VARS[sparkColor]
    const stroke = cssVarToRgb(vars.stroke)
    const fillBase = cssVarToRgb(vars.fill)
    // Convert fill to a semi-transparent version for the area fill
    // e.g. 'rgb(59 130 246)' → 'rgb(59 130 246 / 0.18)'
    const fill = fillBase.replace('rgb(', 'rgba(').replace(')', ' / 0.18)')
    return { stroke, fill }
  }, [sparkColor])
}

export interface StatCardProps {
  /** Visible card label (i18n key or raw string). */
  label: string
  /** Big number / text to display. */
  value: string | number
  /** Optional icon (e.g. lucide-react component) shown in the header. */
  icon?: React.ReactNode
  /** Tailwind color class for the icon, e.g. "text-emerald-500". */
  iconClassName?: string
  /**
   * Percent change vs. start of the sparkline window. Positive = up arrow,
   * negative = down arrow, near-zero = minus.
   */
  delta?: number
  /** Time series for the mini sparkline. Empty/undefined hides the chart. */
  sparkline?: number[]
  /** Sparkline accent color. */
  sparkColor?: SparkColor
  /** Optional extra info, e.g. a unit or hint, shown next to the value. */
  hint?: string
  /** Optional URL — clicking the card navigates here. */
  href?: string
  /** Click handler. Either `href` or `onClick` may be set. */
  onClick?: () => void
  /** Optional native `title` attribute — rendered on the outer card. */
  title?: string
}

/**
 * Compact KPI card with a mini sparkline + delta indicator.
 *
 * Designed for the dashboard stat row. Renders as a Card; when `href`
 * is provided, wraps in an anchor with hover affordance.
 */
export function StatCard({
  label,
  value,
  icon,
  iconClassName,
  delta,
  sparkline,
  sparkColor = 'info',
  hint,
  href,
  onClick,
  title,
}: StatCardProps) {
  const colors = useSparkColors(sparkColor)
  const hasSparkline = Array.isArray(sparkline) && sparkline.length > 1

  const series = hasSparkline ? sparkline.map((v, i) => ({ i, v })) : []

  const deltaDir: 'up' | 'down' | 'flat' =
    typeof delta !== 'number' ? 'flat' : Math.abs(delta) < 0.5 ? 'flat' : delta > 0 ? 'up' : 'down'

  const cardInner = (
    <Card
      className={cn(
        'relative h-full overflow-hidden',
        (href || onClick) &&
          'cursor-pointer select-none transition-all hover:bg-accent/60 hover:shadow-sm hover:-translate-y-px focus-visible:ring-2 focus-visible:ring-ring',
      )}
      onClick={onClick}
      title={title}
    >
      <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
        <CardTitle className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
          {label}
        </CardTitle>
        {icon && (
          <div className={cn('shrink-0', iconClassName ?? 'text-muted-foreground')}>{icon}</div>
        )}
      </CardHeader>
      <CardContent>
        <div className="flex items-end justify-between gap-2">
          <div className="min-w-0">
            <div className="text-2xl font-bold leading-none truncate">{value}</div>
            {(hint || typeof delta === 'number') && (
              <div className="mt-1.5 flex items-center gap-1.5 text-xs text-muted-foreground">
                {typeof delta === 'number' && (
                  <span
                    className={cn(
                      'inline-flex items-center gap-0.5 font-medium',
                      deltaDir === 'up' && 'text-success',
                      deltaDir === 'down' && 'text-error',
                      deltaDir === 'flat' && 'text-muted-foreground',
                    )}
                    aria-label={
                      deltaDir === 'up'
                        ? `up ${delta.toFixed(1)}%`
                        : deltaDir === 'down'
                          ? `down ${delta.toFixed(1)}%`
                          : 'no change'
                    }
                  >
                    {deltaDir === 'up' && <TrendingUp className="h-3 w-3" />}
                    {deltaDir === 'down' && <TrendingDown className="h-3 w-3" />}
                    {deltaDir === 'flat' && <Minus className="h-3 w-3" />}
                    {deltaDir !== 'flat' && `${delta > 0 ? '+' : ''}${delta.toFixed(0)}%`}
                  </span>
                )}
                {hint && <span className="truncate">{hint}</span>}
              </div>
            )}
          </div>
          {hasSparkline && (
            <div className="h-10 w-20 shrink-0" aria-hidden="true">
              <ResponsiveContainer width={80} height={40}>
                <AreaChart data={series} margin={{ top: 2, right: 0, bottom: 2, left: 0 }}>
                  <defs>
                    <linearGradient id={`spark-${sparkColor}`} x1="0" y1="0" x2="0" y2="1">
                      <stop offset="0%" stopColor={colors.stroke} stopOpacity={0.5} />
                      <stop offset="100%" stopColor={colors.stroke} stopOpacity={0} />
                    </linearGradient>
                  </defs>
                  <Area
                    type="monotone"
                    dataKey="v"
                    stroke={colors.stroke}
                    strokeWidth={1.5}
                    fill={`url(#spark-${sparkColor})`}
                    isAnimationActive={false}
                    dot={false}
                  />
                </AreaChart>
              </ResponsiveContainer>
            </div>
          )}
        </div>
      </CardContent>
    </Card>
  )

  if (href) {
    return (
      <a href={href} className="block h-full focus:outline-none">
        {cardInner}
      </a>
    )
  }
  return cardInner
}