{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "upstash-counter",
  "title": "Upstash Counter",
  "description": "Animated counter display for Redis INCR patterns with optional increment/decrement controls.",
  "dependencies": [
    "@upstash/redis"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/blocks/upstash/upstash-counter/components/elements/upstash-counter.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface UpstashCounterProps {\n  value: number;\n  label?: string;\n  format?: \"full\" | \"compact\";\n  showControls?: boolean;\n  onIncrement?: () => void;\n  onDecrement?: () => void;\n  animated?: boolean;\n  size?: \"sm\" | \"md\" | \"lg\";\n  className?: string;\n}\n\nfunction PlusIcon({ className }: { className?: string }) {\n  return (\n    <svg\n      className={className}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={2}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    >\n      <path d=\"M5 12h14\" />\n      <path d=\"M12 5v14\" />\n    </svg>\n  );\n}\n\nfunction MinusIcon({ className }: { className?: string }) {\n  return (\n    <svg\n      className={className}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth={2}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    >\n      <path d=\"M5 12h14\" />\n    </svg>\n  );\n}\n\nfunction formatNumber(value: number, format: \"full\" | \"compact\"): string {\n  if (format === \"compact\") {\n    if (value >= 1_000_000_000) return `${(value / 1_000_000_000).toFixed(1)}B`;\n    if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;\n    if (value >= 1_000) return `${(value / 1_000).toFixed(1)}K`;\n  }\n  return value.toLocaleString();\n}\n\nconst SIZE_CLASSES = {\n  sm: { value: \"text-2xl\", label: \"text-xs\", button: \"p-1\", icon: \"w-3 h-3\" },\n  md: { value: \"text-4xl\", label: \"text-sm\", button: \"p-1.5\", icon: \"w-4 h-4\" },\n  lg: { value: \"text-6xl\", label: \"text-base\", button: \"p-2\", icon: \"w-5 h-5\" },\n};\n\nexport function UpstashCounter({\n  value,\n  label,\n  format = \"full\",\n  showControls = false,\n  onIncrement,\n  onDecrement,\n  animated = true,\n  size = \"md\",\n  className,\n}: UpstashCounterProps) {\n  const [displayValue, setDisplayValue] = useState(value);\n  const [isPulsing, setIsPulsing] = useState(false);\n  const prevValue = useRef(value);\n  const sizes = SIZE_CLASSES[size];\n\n  useEffect(() => {\n    if (!animated) {\n      setDisplayValue(value);\n      return;\n    }\n\n    if (prevValue.current !== value) {\n      setIsPulsing(true);\n      setTimeout(() => setIsPulsing(false), 300);\n\n      const diff = value - prevValue.current;\n      const steps = Math.min(Math.abs(diff), 20);\n      const stepValue = diff / steps;\n      let current = prevValue.current;\n      let step = 0;\n\n      const interval = setInterval(() => {\n        step++;\n        if (step >= steps) {\n          setDisplayValue(value);\n          clearInterval(interval);\n        } else {\n          current += stepValue;\n          setDisplayValue(Math.round(current));\n        }\n      }, 30);\n\n      prevValue.current = value;\n      return () => clearInterval(interval);\n    }\n  }, [value, animated]);\n\n  return (\n    <div\n      data-slot=\"upstash-counter\"\n      className={cn(\n        \"flex flex-col items-center gap-2\",\n        className\n      )}\n    >\n      <div className=\"flex items-center gap-4\">\n        {showControls && (\n          <button\n            type=\"button\"\n            onClick={onDecrement}\n            disabled={!onDecrement}\n            className={cn(\n              \"rounded-full border border-border bg-muted/50\",\n              \"hover:bg-muted transition-colors\",\n              \"disabled:opacity-50 disabled:cursor-not-allowed\",\n              sizes.button\n            )}\n          >\n            <MinusIcon className={sizes.icon} />\n          </button>\n        )}\n\n        <span\n          className={cn(\n            \"font-mono font-bold tabular-nums transition-transform\",\n            sizes.value,\n            isPulsing && \"scale-110 text-primary\"\n          )}\n        >\n          {formatNumber(displayValue, format)}\n        </span>\n\n        {showControls && (\n          <button\n            type=\"button\"\n            onClick={onIncrement}\n            disabled={!onIncrement}\n            className={cn(\n              \"rounded-full border border-border bg-muted/50\",\n              \"hover:bg-muted transition-colors\",\n              \"disabled:opacity-50 disabled:cursor-not-allowed\",\n              sizes.button\n            )}\n          >\n            <PlusIcon className={sizes.icon} />\n          </button>\n        )}\n      </div>\n\n      {label && (\n        <span className={cn(\"text-muted-foreground\", sizes.label)}>\n          {label}\n        </span>\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "docs": "Animated counter with smooth number transitions, compact formatting (1.2K), and optional +/- controls. Great for page views, likes, or any Redis counter.",
  "categories": [
    "upstash"
  ],
  "type": "registry:block"
}