{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "segmented-meter",
  "title": "Segmented Meter",
  "description": "A zoned bar meter with a marker at the current value, for heart-rate zones, allocations, or thresholds.",
  "files": [
    {
      "path": "registry/dashboardcn/ui/segmented-meter.tsx",
      "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { formatNumber } from \"@/registry/dashboardcn/lib/format\"\n\nexport interface MeterZone {\n  label?: string\n  from: number\n  to: number\n  /** Any CSS color. Defaults to chart-1. */\n  color?: string\n}\n\nexport interface SegmentedMeterProps extends React.ComponentProps<\"div\"> {\n  value: number\n  zones: MeterZone[]\n  /** Only color the zone that contains the value; others stay muted. */\n  highlightActive?: boolean\n  /** Show zone boundary values under the bar. */\n  showTicks?: boolean\n  /** Show zone labels under the bar. */\n  showLabels?: boolean\n  /** Show a marker at the current value. */\n  showMarker?: boolean\n  tickFormatter?: (value: number) => string\n}\n\nfunction SegmentedMeter({\n  value,\n  zones,\n  highlightActive = true,\n  showTicks = true,\n  showLabels = false,\n  showMarker = true,\n  tickFormatter = (v) => formatNumber(v),\n  className,\n  ...props\n}: SegmentedMeterProps) {\n  const min = Math.min(...zones.map((z) => z.from))\n  const max = Math.max(...zones.map((z) => z.to))\n  const span = max - min || 1\n  const position = Math.min(1, Math.max(0, (value - min) / span))\n  const activeIndex = zones.findIndex(\n    (z, i) => value >= z.from && (value < z.to || (i === zones.length - 1 && value <= z.to))\n  )\n\n  return (\n    <div\n      data-slot=\"segmented-meter\"\n      role=\"meter\"\n      aria-valuemin={min}\n      aria-valuemax={max}\n      aria-valuenow={value}\n      className={cn(\"flex flex-col gap-1.5\", className)}\n      {...props}\n    >\n      <div className=\"relative\">\n        <div className=\"flex h-2 w-full gap-0.5\">\n          {zones.map((zone, index) => {\n            const active = index === activeIndex\n            const color = zone.color ?? \"var(--chart-1)\"\n            return (\n              <div\n                key={index}\n                data-active={active}\n                className=\"bg-muted h-full rounded-sm transition-colors first:rounded-l-full last:rounded-r-full\"\n                style={{\n                  width: `${((zone.to - zone.from) / span) * 100}%`,\n                  backgroundColor: !highlightActive || active ? color : undefined,\n                }}\n              />\n            )\n          })}\n        </div>\n        {showMarker ? (\n          <div\n            aria-hidden=\"true\"\n            className=\"bg-foreground ring-background absolute top-1/2 size-3 -translate-x-1/2 -translate-y-1/2 rounded-full ring-2\"\n            style={{ left: `${position * 100}%` }}\n          />\n        ) : null}\n      </div>\n      {showTicks || showLabels ? (\n        <div className=\"text-muted-foreground relative h-4 text-[10px] tabular-nums\">\n          {showTicks\n            ? [min, ...zones.map((z) => z.to)].map((tick, index, all) => (\n                <span\n                  key={index}\n                  className={cn(\n                    \"absolute top-0\",\n                    index === 0 && \"left-0\",\n                    index === all.length - 1 && \"right-0\",\n                    index > 0 && index < all.length - 1 && \"-translate-x-1/2\"\n                  )}\n                  style={\n                    index > 0 && index < all.length - 1\n                      ? { left: `${((tick - min) / span) * 100}%` }\n                      : undefined\n                  }\n                >\n                  {tickFormatter(tick)}\n                </span>\n              ))\n            : zones.map((zone, index) => (\n                <span\n                  key={index}\n                  className=\"absolute top-0 -translate-x-1/2 truncate\"\n                  style={{\n                    left: `${(((zone.from + zone.to) / 2 - min) / span) * 100}%`,\n                  }}\n                >\n                  {zone.label}\n                </span>\n              ))}\n        </div>\n      ) : null}\n    </div>\n  )\n}\n\nexport { SegmentedMeter }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/dashboardcn/lib/format.ts",
      "content": "export type NumberFormat = \"number\" | \"compact\" | \"currency\" | \"percent\"\n\nexport interface FormatNumberOptions {\n  format?: NumberFormat\n  /** ISO 4217 code, used when format is \"currency\". Defaults to USD. */\n  currency?: string\n  locale?: string\n  maximumFractionDigits?: number\n  /** Abbreviate large values, e.g. \"$158K\" instead of \"$158,143\". */\n  compact?: boolean\n}\n\nconst formatterCache = new Map<string, Intl.NumberFormat>()\n\nfunction getFormatter(locale: string, options: Intl.NumberFormatOptions) {\n  const key = locale + JSON.stringify(options)\n  let formatter = formatterCache.get(key)\n  if (!formatter) {\n    formatter = new Intl.NumberFormat(locale, options)\n    formatterCache.set(key, formatter)\n  }\n  return formatter\n}\n\n/**\n * Format a metric value for display.\n *\n * formatNumber(1234567)                          -> \"1,234,567\"\n * formatNumber(1234567, { format: \"compact\" })   -> \"1.2M\"\n * formatNumber(48.2, { format: \"currency\" })     -> \"$48.20\"\n * formatNumber(158143, { format: \"currency\", compact: true }) -> \"$158K\"\n * formatNumber(0.124, { format: \"percent\" })     -> \"12.4%\"\n */\nexport function formatNumber(\n  value: number,\n  {\n    format = \"number\",\n    currency = \"USD\",\n    locale = \"en-US\",\n    maximumFractionDigits,\n    compact = false,\n  }: FormatNumberOptions = {}\n): string {\n  if (!Number.isFinite(value)) return \"—\"\n\n  // Compact values keep three significant digits: $158K, $1.23M, 41.2K.\n  const compactOptions: Intl.NumberFormatOptions = compact\n    ? maximumFractionDigits === undefined\n      ? { notation: \"compact\", maximumSignificantDigits: 3 }\n      : { notation: \"compact\", maximumFractionDigits }\n    : {}\n  switch (format) {\n    case \"compact\":\n      return getFormatter(locale, {\n        notation: \"compact\",\n        maximumFractionDigits: maximumFractionDigits ?? 1,\n      }).format(value)\n    case \"currency\":\n      return getFormatter(locale, {\n        style: \"currency\",\n        currency,\n        maximumFractionDigits: maximumFractionDigits ?? 2,\n        ...compactOptions,\n      }).format(value)\n    case \"percent\":\n      return getFormatter(locale, {\n        style: \"percent\",\n        maximumFractionDigits: maximumFractionDigits ?? 1,\n      }).format(value)\n    default:\n      return getFormatter(locale, {\n        maximumFractionDigits: maximumFractionDigits ?? 0,\n        ...compactOptions,\n      }).format(value)\n  }\n}\n\n/**\n * Format a fractional change as a signed percentage.\n *\n * formatDelta(0.124)  -> \"+12.4%\"\n * formatDelta(-0.03)  -> \"-3.0%\"\n * formatDelta(0)      -> \"0.0%\"\n */\nexport function formatDelta(delta: number, locale = \"en-US\"): string {\n  if (!Number.isFinite(delta)) return \"—\"\n  return getFormatter(locale, {\n    style: \"percent\",\n    signDisplay: \"exceptZero\",\n    maximumFractionDigits: 1,\n    minimumFractionDigits: 1,\n  }).format(delta)\n}\n\n/**\n * Fractional change between two values. Returns 0 when there is no baseline.\n */\nexport function computeDelta(current: number, previous: number): number {\n  if (!previous) return 0\n  return (current - previous) / Math.abs(previous)\n}\n",
      "type": "registry:lib"
    }
  ],
  "type": "registry:ui"
}