{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "quota-ring",
  "type": "registry:component",
  "title": "Quota Ring",
  "description": "A small usage ring that turns amber, then red, as it crosses its thresholds, with a tooltip that breaks the number down and counts down to the reset.",
  "categories": [
    "component",
    "Status"
  ],
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://coss.com/ui/r/tooltip.json"
  ],
  "files": [
    {
      "path": "registry/devl/quota-ring.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport { AlertTriangleIcon, OctagonAlertIcon, RotateCcwIcon } from \"lucide-react\";\nimport { motion, useReducedMotion, useSpring, useTransform } from \"motion/react\";\nimport { type ReactNode, useEffect, useMemo, useRef, useState } from \"react\";\nimport { Tooltip, TooltipPopup, TooltipProvider, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface QuotaPart {\n  label: string;\n  value: number;\n}\n\nexport interface QuotaRingProps {\n  label: string;\n  used: number;\n  limit: number;\n  unit?: string;\n  breakdown?: QuotaPart[];\n  resetsAt?: Date | string | number;\n  warnAt?: number;\n  criticalAt?: number;\n  locale?: string;\n  className?: string;\n}\n\ntype Level = \"ok\" | \"warn\" | \"critical\";\n\nconst LEVEL: Record<Level, { stroke: string; text: string }> = {\n  ok: { stroke: \"stroke-foreground/80\", text: \"text-foreground\" },\n  warn: { stroke: \"stroke-warning\", text: \"text-warning-foreground\" },\n  critical: { stroke: \"stroke-destructive\", text: \"text-destructive-foreground\" },\n};\nconst SIZE = 40;\nconst STROKE = 4;\nconst R = (SIZE - STROKE) / 2;\nconst CIRC = 2 * Math.PI * R;\n\nfunction useNow(active: boolean, every = 1000) {\n  const [now, setNow] = useState<number | null>(null);\n  useEffect(() => {\n    setNow(Date.now());\n    if (!active) return;\n    const id = setInterval(() => setNow(Date.now()), every);\n    return () => clearInterval(id);\n  }, [active, every]);\n  return now;\n}\n\nfunction until(target: number, now: number, withSeconds: boolean) {\n  const s = Math.max(0, Math.floor((target - now) / 1000));\n  const d = Math.floor(s / 86400);\n  const h = Math.floor((s % 86400) / 3600);\n  const m = Math.floor((s % 3600) / 60);\n  const sec = s % 60;\n  const pad = (n: number) => String(n).padStart(2, \"0\");\n  if (!withSeconds) return d ? `${d}d ${h}h` : h ? `${h}h ${m}m` : `${m}m`;\n  return d ? `${d}d ${pad(h)}h ${pad(m)}m ${pad(sec)}s` : `${pad(h)}h ${pad(m)}m ${pad(sec)}s`;\n}\n\nfunction Countdown({ target }: { target: number }) {\n  const now = useNow(true);\n  return <span className=\"font-medium tabular-nums\">{now === null ? \"–\" : until(target, now, true)}</span>;\n}\n\nexport function QuotaRing({\n  label,\n  used,\n  limit,\n  unit = \"\",\n  breakdown = [],\n  resetsAt,\n  warnAt = 0.75,\n  criticalAt = 0.9,\n  locale = \"en-US\",\n  className,\n}: QuotaRingProps) {\n  const reduce = useReducedMotion();\n  const ratio = limit > 0 ? used / limit : 0;\n  const level: Level = ratio >= criticalAt ? \"critical\" : ratio >= warnAt ? \"warn\" : \"ok\";\n  const tone = LEVEL[level];\n  const num = useMemo(() => new Intl.NumberFormat(locale), [locale]);\n  const compact = useMemo(() => new Intl.NumberFormat(locale, { notation: \"compact\", maximumFractionDigits: 1 }), [locale]);\n  const target = resetsAt === undefined ? null : new Date(resetsAt).getTime();\n  const [pulse, setPulse] = useState(0);\n  const lastLevel = useRef(level);\n  useEffect(() => {\n    if (lastLevel.current !== level && level !== \"ok\") setPulse((p) => p + 1);\n    lastLevel.current = level;\n  }, [level]);\n\n  const progress = useSpring(0, { stiffness: 120, damping: 20 });\n  useEffect(() => {\n    const v = Math.min(1, ratio);\n    if (reduce) progress.jump(v);\n    else progress.set(v);\n  }, [ratio, reduce, progress]);\n  const dash = useTransform(progress, (p) => `${(p * CIRC).toFixed(2)} ${CIRC.toFixed(2)}`);\n  const pct = Math.round(ratio * 100);\n  const u = unit ? ` ${unit}` : \"\";\n\n  return (\n    <Tooltip>\n      <TooltipTrigger\n        render={\n          <button\n            aria-label={`${label}: ${num.format(used)} of ${num.format(limit)}${u}, ${pct}% used${level !== \"ok\" ? `, ${level === \"warn\" ? \"nearing\" : \"at\"} limit` : \"\"}`}\n            className={cn(\n              \"group/quota flex min-w-0 cursor-default items-center gap-2.5 rounded-xl p-2 pe-2.5 text-left outline-none transition-[background-color,scale] duration-150 ease-out focus-visible:ring-2 focus-visible:ring-ring active:scale-[0.97] [@media(hover:hover)]:hover:bg-accent\",\n              className,\n            )}\n            type=\"button\"\n          />\n        }\n      >\n        <motion.span\n          animate={pulse === 0 || reduce ? { scale: 1 } : { scale: [1, 1.1, 1] }}\n          className=\"relative flex shrink-0 items-center justify-center\"\n          key={pulse}\n          style={{ width: SIZE, height: SIZE }}\n          transition={{ duration: 0.45, ease: [0.23, 1, 0.32, 1] }}\n        >\n          <svg aria-hidden className=\"-rotate-90\" height={SIZE} width={SIZE}>\n            <circle className=\"fill-none stroke-foreground/10\" cx={SIZE / 2} cy={SIZE / 2} r={R} strokeWidth={STROKE} />\n            {[warnAt, criticalAt].map((t) => (\n              <line\n                className=\"stroke-card\"\n                key={t}\n                strokeWidth={1.5}\n                transform={`rotate(${t * 360} ${SIZE / 2} ${SIZE / 2})`}\n                x1={SIZE - STROKE - 0.5}\n                x2={SIZE + 0.5}\n                y1={SIZE / 2}\n                y2={SIZE / 2}\n              />\n            ))}\n            <motion.circle\n              className={cn(\"fill-none transition-[stroke] duration-300\", tone.stroke)}\n              cx={SIZE / 2}\n              cy={SIZE / 2}\n              r={R}\n              strokeDasharray={dash}\n              strokeLinecap=\"round\"\n              strokeWidth={STROKE}\n            />\n          </svg>\n          <span className={cn(\"absolute font-medium text-[10px] tabular-nums transition-colors duration-300\", tone.text)}>\n            {pct > 999 ? \"999+\" : pct}\n          </span>\n        </motion.span>\n        <span className=\"flex min-w-0 flex-col\">\n          <span className=\"flex items-center gap-1 text-muted-foreground text-xs\">\n            <span className=\"truncate\">{label}</span>\n            {level === \"warn\" ? <AlertTriangleIcon className=\"size-3 shrink-0 text-warning\" /> : null}\n            {level === \"critical\" ? <OctagonAlertIcon className=\"size-3 shrink-0 text-destructive\" /> : null}\n          </span>\n          <span className=\"truncate font-medium text-sm tabular-nums\">\n            {compact.format(used)}\n            <span className=\"font-normal text-muted-foreground\">\n              {\" \"}\n              / {compact.format(limit)}\n              {unit ? <span className=\"text-xs\"> {unit}</span> : null}\n            </span>\n          </span>\n        </span>\n      </TooltipTrigger>\n      <TooltipPopup className=\"w-60\" side=\"top\" sideOffset={6}>\n        <div className=\"flex flex-col gap-2 py-1.5\">\n          <div className=\"flex items-baseline justify-between gap-2\">\n            <span className=\"font-medium\">{label}</span>\n            <span className={cn(\"font-medium tabular-nums\", tone.text)}>{pct}%</span>\n          </div>\n          <div className=\"text-muted-foreground tabular-nums\">\n            {num.format(used)} of {num.format(limit)}\n            {u} used\n            {used > limit ? ` · ${num.format(used - limit)}${u} over` : ` · ${num.format(limit - used)}${u} left`}\n          </div>\n          {breakdown.length ? (\n            <div className=\"flex flex-col gap-1.5 border-t pt-2\">\n              {breakdown.map((p) => (\n                <div className=\"flex flex-col gap-1\" key={p.label}>\n                  <div className=\"flex items-baseline justify-between gap-2\">\n                    <span className=\"truncate text-muted-foreground\">{p.label}</span>\n                    <span className=\"tabular-nums\">\n                      {num.format(p.value)}\n                      {u}\n                    </span>\n                  </div>\n                  <span className=\"relative h-1 overflow-hidden rounded-full bg-foreground/8\">\n                    <span\n                      className=\"absolute inset-y-0 left-0 rounded-full bg-foreground/50\"\n                      style={{ width: `${Math.min(100, (p.value / (limit || 1)) * 100)}%` }}\n                    />\n                  </span>\n                </div>\n              ))}\n            </div>\n          ) : null}\n          {target !== null ? (\n            <div className=\"flex items-center gap-1.5 border-t pt-2 text-muted-foreground\">\n              <RotateCcwIcon className=\"size-3\" />\n              <span>\n                Resets in <Countdown target={target} />\n              </span>\n            </div>\n          ) : null}\n        </div>\n      </TooltipPopup>\n    </Tooltip>\n  );\n}\n\nexport function QuotaRingGroup({ children, className }: { children: ReactNode; className?: string }) {\n  return (\n    <TooltipProvider closeDelay={80} delay={350}>\n      <div className={cn(\"flex flex-wrap gap-1\", className)}>{children}</div>\n    </TooltipProvider>\n  );\n}\n"
    }
  ]
}