{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"gauge","type":"registry:ui","title":"Gauge","description":"One value within a range as a half ring — a score, a quota or a target. A meter for assistive technology.","dependencies":[],"registryDependencies":["https://dashral.com/r/utils.json","https://dashral.com/r/lib-count-to.json","https://dashral.com/r/lib-locale.json"],"files":[{"path":"registry/dashral/gauge.tsx","type":"registry:file","target":"components/dashral/gauge.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\n\nimport { CountTo } from \"./lib/count-to\"\nimport { useLocale } from \"./lib/locale\"\nimport { cn } from \"./lib/utils\"\n\ntype GaugeProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  value: number\n  min?: number\n  max?: number\n  /** Under the number, e.g. \"Uptime\". Also the meter's accessible name unless aria-label is set. */\n  label?: string\n  /** How the number is written, e.g. `{ style: \"percent\" }` (then pass 0.82 for 82%) or `{ maximumFractionDigits: 0 }`. */\n  formatOptions?: Intl.NumberFormatOptions\n  locale?: string\n  /** Width in pixels; the height is about half of it. */\n  size?: number\n  thickness?: number\n  /**\n   * Colors by level, lowest first: the last one the value reaches wins, e.g.\n   * `[{ from: 0, className: \"text-destructive\" }, { from: 60, className: \"text-warning\" }, { from: 85, className: \"text-success\" }]`.\n   * Without it, the arc takes the text color (text-primary by default).\n   */\n  levels?: { from: number; className: string }[]\n}\n\n/**\n * A single value within a known range, as a half ring — a score, a quota, a target. It's a `meter` for assistive\n * technology, with the formatted value as its text. The arc fills from empty when it first shows.\n */\nfunction Gauge({\n  value,\n  min = 0,\n  max = 100,\n  label,\n  formatOptions,\n  locale: localeProp,\n  size = 180,\n  thickness = 14,\n  levels,\n  className,\n  \"aria-label\": ariaLabel,\n  ...props\n}: GaugeProps) {\n  const { locale: contextLocale } = useLocale()\n  const locale = localeProp ?? contextLocale\n  const [drawn, setDrawn] = React.useState(false)\n  React.useEffect(() => setDrawn(true), [])\n\n  const share = Math.min(1, Math.max(0, (value - min) / (max - min || 1)))\n  const text = new Intl.NumberFormat(locale, formatOptions).format(value)\n  const level = levels?.filter((l) => value >= l.from).at(-1)\n  const r = (size - thickness) / 2\n  const cy = size / 2\n  const arc = `M ${thickness / 2} ${cy} A ${r} ${r} 0 0 1 ${size - thickness / 2} ${cy}`\n\n  return (\n    <div\n      data-slot=\"gauge\"\n      role=\"meter\"\n      aria-valuenow={value}\n      aria-valuemin={min}\n      aria-valuemax={max}\n      aria-valuetext={text}\n      aria-label={ariaLabel ?? label}\n      className={cn(\"inline-flex flex-col items-center text-primary\", level?.className, className)}\n      style={{ width: size }}\n      {...props}\n    >\n      <div className=\"relative\">\n        <svg width={size} height={cy + thickness / 2} viewBox={`0 0 ${size} ${cy + thickness / 2}`} aria-hidden>\n          <path d={arc} fill=\"none\" stroke=\"var(--muted)\" strokeWidth={thickness} strokeLinecap=\"round\" />\n          <path\n            d={arc}\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth={thickness}\n            strokeLinecap=\"round\"\n            pathLength={100}\n            strokeDasharray={`${drawn ? share * 100 : 0} 100`}\n            className=\"transition-[stroke-dasharray] duration-(--duration-considered) ease-enter motion-reduce:transition-none\"\n            // A zero-length round cap still draws a dot; hide it at 0.\n            style={{ opacity: share === 0 ? 0 : 1 }}\n          />\n        </svg>\n        <div className=\"absolute inset-x-0 bottom-0 flex flex-col items-center leading-tight\" aria-hidden>\n          <span className=\"text-2xl font-semibold tracking-tight text-foreground tabular-nums\">\n            <CountTo value={value} locale={locale} formatOptions={formatOptions} />\n          </span>\n          {label && <span className=\"text-xs text-muted-foreground\">{label}</span>}\n        </div>\n      </div>\n      <div className=\"mt-1 flex w-full justify-between px-1 text-[11px] text-muted-foreground tabular-nums\" aria-hidden>\n        <span>{new Intl.NumberFormat(locale, formatOptions).format(min)}</span>\n        <span>{new Intl.NumberFormat(locale, formatOptions).format(max)}</span>\n      </div>\n    </div>\n  )\n}\n\nexport { Gauge }\n"}],"cssVars":{"theme":{"color-success":"var(--success)","color-success-foreground":"var(--success-foreground)","color-success-text":"var(--success-text)","color-warning":"var(--warning)","color-warning-foreground":"var(--warning-foreground)","color-warning-text":"var(--warning-text)","ease-standard":"var(--ease-standard)","ease-enter":"var(--ease-enter)","ease-exit":"var(--ease-exit)","ease-spring":"var(--ease-spring)"},"light":{"success":"oklch(0.508 0.118 165.612)","success-foreground":"oklch(0.985 0 0)","success-text":"oklch(0.432 0.095 166.913)","warning":"#f59200","warning-foreground":"oklch(0.3 0.07 60)","warning-text":"#964e00","duration-fast":"160ms","duration-standard":"240ms","duration-considered":"480ms","ease-standard":"cubic-bezier(0.2, 0, 0, 1)","ease-enter":"cubic-bezier(0, 0, 0, 1)","ease-exit":"cubic-bezier(0.3, 0, 1, 1)","ease-spring":"linear(0, 0.071 4%, 0.234 8%, 0.433 12%, 0.626 17%, 0.791 21%, 0.917 25%, 1.004 29%, 1.055 33%, 1.079 38%, 1.083 42%, 1.075 46%, 1.06 50%, 1.043 54%, 1.027 58%, 1.015 62%, 1.005 67%, 0.998 71%, 0.995 75%, 0.993 79%, 0.993 83%, 0.994 88%, 0.995 92%, 0.997 96%, 1)"},"dark":{"success":"oklch(0.696 0.17 162.48)","success-foreground":"oklch(0.2 0.04 160)","success-text":"oklch(0.765 0.177 163.223)","warning":"#f59200","warning-foreground":"oklch(0.25 0.06 60)","warning-text":"#ffba42"}}}