Saltar al contenido
ssntl/ui

Stat

Un número grande con una etiqueta corta y un cambio con signo, donde la importancia se ve en tamaño, peso y color.

Monthly visitors

24,608

+2,140 since September

Refunds

38

+6 since September

Instalación

$ pnpm dlx shadcn@latest add @ssntl/stat

Uso

tsx
import { Stat, StatDelta, StatLabel, StatValue } from "@/components/ui/stat"

<Stat>
  <StatLabel>Visitas mensuales</StatLabel>
  <StatValue>24,608</StatValue>
  <StatDelta trend="up">+2,140 desde septiembre</StatDelta>
</Stat>
  • Importancia: el valor manda en tamaño (48px, lg 64px), peso (bold) y color (foreground); la etiqueta y la nota pasan a muted.
  • Más allá del tamaño: la jerarquía también viene del peso y el color, así el número sigue mandando en una tarjeta pequeña.
  • Escribe el signo y el periodo en el cambio; el verde y el rojo solo acompañan las palabras.
  • Las estadísticas pares comparten tamaño, peso y posición de la etiqueta. Los dígitos tabulares las mantienen alineadas.

Código

components/ui/stat.tsx
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

// Essential Design `es-stat`: when the number is the point, it gets the space.
// Importance shows in more than size: the value is large, heavy and full
// foreground; the label and the note step back in caption size and muted
// color. Tabular figures so peer stats line up.
const statValueVariants = cva("font-bold tabular-nums", {
  variants: {
    size: {
      md: "text-headline",
      lg: "text-stat",
    },
  },
  defaultVariants: { size: "md" },
})

function Stat({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="stat"
      className={cn("grid content-start gap-1", className)}
      {...props}
    />
  )
}

function StatLabel({ className, ...props }: React.ComponentProps<"p">) {
  return (
    <p
      data-slot="stat-label"
      className={cn("text-label font-normal text-muted-foreground", className)}
      {...props}
    />
  )
}

function StatValue({
  size,
  className,
  ...props
}: React.ComponentProps<"p"> & VariantProps<typeof statValueVariants>) {
  return (
    <p
      data-slot="stat-value"
      className={cn(statValueVariants({ size, className }))}
      {...props}
    />
  )
}

// Write the sign and the period ("+2,140 this month"): the color only
// supports a change the words already state.
function StatDelta({
  trend = "neutral",
  className,
  ...props
}: React.ComponentProps<"p"> & { trend?: "up" | "down" | "neutral" }) {
  return (
    <p
      data-slot="stat-delta"
      data-trend={trend}
      className={cn(
        "text-caption",
        trend === "up" && "text-green-600 dark:text-green-400",
        trend === "down" && "text-destructive",
        trend === "neutral" && "text-muted-foreground",
        className
      )}
      {...props}
    />
  )
}

export { Stat, StatDelta, StatLabel, StatValue, statValueVariants }