{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "threshold-ring",
  "title": "Threshold Ring",
  "description": "Compact ring gauge expressing how close a value is to a threshold.",
  "dependencies": [
    "@vllnt/ui@^0.3.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/threshold-ring/threshold-ring.tsx",
      "content": "\"use client\";\n\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\n\nimport { cn } from \"@vllnt/ui\";\n\n/**\n * Tone of the active arc — drives its stroke color.\n *\n * @public\n */\nexport type ThresholdRingTone = \"danger\" | \"neutral\" | \"success\" | \"warn\";\n\nconst TONE_STROKE: Record<ThresholdRingTone, string> = {\n  danger: \"stroke-red-500\",\n  neutral: \"stroke-foreground\",\n  success: \"stroke-emerald-500\",\n  warn: \"stroke-amber-500\",\n};\n\n/**\n * Localizable strings.\n *\n * @public\n */\nexport type ThresholdRingLabels = {\n  /** Aria-label override. Defaults to `\"Threshold ring\"`. */\n  region?: string;\n};\n\nconst DEFAULT_LABELS = {\n  region: \"Threshold ring\",\n} as const satisfies Required<ThresholdRingLabels>;\n\n/**\n * Props for {@link ThresholdRing}.\n *\n * @public\n */\nexport type ThresholdRingProps = {\n  /** Optional center label (formatted by host — e.g. `\"82%\"`). */\n  centerLabel?: ReactNode;\n  /** Localizable strings. */\n  labels?: ThresholdRingLabels;\n  /** Upper bound of the ring's value range. Defaults to `1`. */\n  max?: number;\n  /** Outer diameter in pixels. Defaults to `64`. */\n  size?: number;\n  /** Stroke width in pixels. Defaults to `6`. */\n  stroke?: number;\n  /** Optional threshold marker `0..max` rendered as a small notch. */\n  threshold?: number;\n  /** Tone of the active arc. Defaults to `\"neutral\"`. */\n  tone?: ThresholdRingTone;\n  /** Current value `0..max`. */\n  value: number;\n} & ComponentPropsWithoutRef<\"svg\">;\n\nconst clamp = (v: number, min: number, max: number): number => {\n  if (v < min) {\n    return min;\n  }\n  if (v > max) {\n    return max;\n  }\n  return v;\n};\n\nconst polar = (input: {\n  angle: number;\n  cx: number;\n  cy: number;\n  r: number;\n}): { x: number; y: number } => {\n  const rad = (input.angle - 90) * (Math.PI / 180);\n  return {\n    x: input.cx + input.r * Math.cos(rad),\n    y: input.cy + input.r * Math.sin(rad),\n  };\n};\n\ntype Geometry = {\n  circumference: number;\n  cx: number;\n  cy: number;\n  r: number;\n  ratio: number;\n  size: number;\n  stroke: number;\n  tickAngle: null | number;\n};\n\nconst computeGeometry = (input: {\n  max: number;\n  size: number;\n  stroke: number;\n  threshold?: number;\n  value: number;\n}): Geometry => {\n  const safeMax = input.max <= 0 ? 1 : input.max;\n  const ratio = clamp(input.value / safeMax, 0, 1);\n  const cx = input.size / 2;\n  const cy = input.size / 2;\n  const r = (input.size - input.stroke) / 2;\n  const circumference = 2 * Math.PI * r;\n  const tickAngle =\n    input.threshold === undefined\n      ? null\n      : clamp(input.threshold / safeMax, 0, 1) * 360;\n  return {\n    circumference,\n    cx,\n    cy,\n    r,\n    ratio,\n    size: input.size,\n    stroke: input.stroke,\n    tickAngle,\n  };\n};\n\nconst Tick = (props: { geom: Geometry }): null | React.ReactElement => {\n  const { geom } = props;\n  if (geom.tickAngle === null) {\n    return null;\n  }\n  const inner = polar({\n    angle: geom.tickAngle,\n    cx: geom.cx,\n    cy: geom.cy,\n    r: geom.r - geom.stroke / 2,\n  });\n  const outer = polar({\n    angle: geom.tickAngle,\n    cx: geom.cx,\n    cy: geom.cy,\n    r: geom.r + geom.stroke / 2,\n  });\n  return (\n    <line\n      className=\"stroke-foreground/80\"\n      data-threshold-ring-tick\n      strokeLinecap=\"round\"\n      strokeWidth={2}\n      x1={inner.x}\n      x2={outer.x}\n      y1={inner.y}\n      y2={outer.y}\n    />\n  );\n};\n\n/**\n * Compact ring gauge expressing how close a value is to a threshold.\n * Pure presentation; the host supplies the value, threshold, and tone.\n * Use to overlay budget / quota / SLA indicators on canvas objects\n * without consuming card real-estate.\n *\n * Distinct from `MetricGauge`: this primitive is small, headless, and\n * meant to attach to a runtime object — not a dashboard tile.\n *\n * @example\n * ```tsx\n * <ThresholdRing value={0.82} threshold={0.7} tone=\"warn\" centerLabel=\"82%\" />\n * ```\n *\n * @public\n */\nexport const ThresholdRing = ({\n  ref,\n  ...props\n}: ThresholdRingProps & { ref?: React.Ref<SVGSVGElement> }) => {\n  const {\n    centerLabel,\n    className,\n    labels,\n    max = 1,\n    size = 64,\n    stroke = 6,\n    threshold,\n    tone = \"neutral\",\n    value,\n    ...rest\n  } = props;\n  const resolvedLabels = { ...DEFAULT_LABELS, ...labels };\n  const geom = computeGeometry({ max, size, stroke, threshold, value });\n  const dash = `${geom.ratio * geom.circumference} ${geom.circumference}`;\n  return (\n    <svg\n      aria-label={resolvedLabels.region}\n      className={cn(\"inline-block\", className)}\n      data-threshold-ring\n      data-threshold-tone={tone}\n      height={geom.size}\n      ref={ref}\n      role=\"img\"\n      viewBox={`0 0 ${geom.size} ${geom.size}`}\n      width={geom.size}\n      {...rest}\n    >\n      <circle\n        className=\"stroke-muted\"\n        cx={geom.cx}\n        cy={geom.cy}\n        fill=\"none\"\n        r={geom.r}\n        strokeWidth={geom.stroke}\n      />\n      <circle\n        className={cn(\"transition-all duration-300\", TONE_STROKE[tone])}\n        cx={geom.cx}\n        cy={geom.cy}\n        data-threshold-ring-arc\n        fill=\"none\"\n        r={geom.r}\n        strokeDasharray={dash}\n        strokeLinecap=\"round\"\n        strokeWidth={geom.stroke}\n        transform={`rotate(-90 ${geom.cx} ${geom.cy})`}\n      />\n      <Tick geom={geom} />\n      {centerLabel ? (\n        <text\n          className=\"fill-foreground text-[10px] font-semibold\"\n          data-threshold-ring-label\n          dominantBaseline=\"central\"\n          textAnchor=\"middle\"\n          x={geom.cx}\n          y={geom.cy}\n        >\n          {centerLabel}\n        </text>\n      ) : null}\n    </svg>\n  );\n};\nThresholdRing.displayName = \"ThresholdRing\";\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component",
  "version": "0.3.0",
  "stability": "stable"
}
