{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "objective-progress",
  "title": "Objective progress",
  "description": "One objective's progress — the polymorphic readout behind triumphs, quests, bounties, metrics, crafting levels, kill trackers, and deepsight bars.",
  "dependencies": [
    "@engram/core"
  ],
  "registryDependencies": [
    "@engram/cn",
    "@engram/segmented-bar",
    "@engram/tokens"
  ],
  "files": [
    {
      "path": "src/components/objective-progress.tsx",
      "content": "import type { ObjectiveProps } from \"@engram/core\";\nimport { cn } from \"../lib/cn.js\";\nimport { SegmentedBar } from \"./segmented-bar.js\";\n\nexport interface ObjectiveProgressProps extends ObjectiveProps {\n  className?: string;\n  /** Fill color for the bar/pips. Defaults to the neutral bar color (in-progress)\n   *  / gold (done). Pass `var(--engram-progress)` for the magenta seal progress. */\n  color?: string;\n  /** Format the numeric value/target (e.g. dates for countdown, thousands). */\n  formatValue?: (n: number) => string;\n}\n\nfunction Check() {\n  return (\n    <svg viewBox=\"0 0 12 12\" className=\"size-2.5 text-white\" aria-hidden=\"true\">\n      <title>complete</title>\n      <path\n        d=\"M2.5 6.3 5 8.8l4.5-5.2\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2\"\n        strokeLinecap=\"square\"\n      />\n    </svg>\n  );\n}\n\n/**\n * One objective's progress — the polymorphic readout behind triumphs, quests,\n * bounties, metrics, crafting levels, kill trackers, and deepsight bars. The\n * `style` (mapped from DestinyUnlockValueUIStyle) picks the form; everything\n * else is generic. Presentational: pass `formatValue` to render dates/units.\n */\nexport function ObjectiveProgress({\n  value,\n  completionValue,\n  complete,\n  style = \"bar\",\n  label,\n  className,\n  color,\n  formatValue,\n}: ObjectiveProgressProps) {\n  const target = completionValue ?? 0;\n  const done = complete ?? (target > 0 ? value >= target : value > 0);\n  const fmt = formatValue ?? ((n: number) => n.toLocaleString());\n  // Individual objective/record bars use the neutral (white-ish) fill in\n  // progress and gold when done. The magenta seal/title progress is opt-in via\n  // `color=\"var(--engram-progress)\"`.\n  const fill = color ?? (done ? \"var(--engram-gold)\" : \"var(--engram-bar)\");\n\n  const labelEl = label ? (\n    <span className=\"min-w-0 flex-1 truncate text-engram-muted text-xs leading-tight\">\n      {label}\n    </span>\n  ) : null;\n  const readoutCls = cn(\n    \"shrink-0 font-engram-display font-semibold text-[11px] tabular-nums\",\n    done ? \"text-engram-fg\" : \"text-engram-muted\",\n  );\n\n  if (style === \"checkbox\") {\n    return (\n      <div className={cn(\"flex items-center gap-2\", className)}>\n        <span\n          aria-hidden=\"true\"\n          className={cn(\n            \"grid size-3.5 shrink-0 place-items-center\",\n            done\n              ? \"bg-[var(--engram-ok)]\"\n              : \"border border-engram-border-strong\",\n          )}\n        >\n          {done ? <Check /> : null}\n        </span>\n        {label ? (\n          <span\n            className={cn(\n              \"text-xs leading-tight\",\n              done ? \"text-engram-fg\" : \"text-engram-muted\",\n            )}\n          >\n            {label}\n          </span>\n        ) : null}\n      </div>\n    );\n  }\n\n  if (style === \"pips\") {\n    const count = Math.max(0, Math.round(target));\n    const filled = Math.max(0, Math.min(count, Math.round(value)));\n    return (\n      <div className={cn(\"flex items-center gap-2\", className)}>\n        {labelEl}\n        <div className=\"flex shrink-0 items-center gap-1\">\n          {Array.from({ length: count }, (_, i) => (\n            <span\n              // biome-ignore lint/suspicious/noArrayIndexKey: fixed-length static pips\n              key={i}\n              aria-hidden=\"true\"\n              className=\"size-2 rotate-45\"\n              style={{\n                background: i < filled ? fill : \"var(--engram-raised-2)\",\n              }}\n            />\n          ))}\n        </div>\n      </div>\n    );\n  }\n\n  const readout =\n    style === \"percent\"\n      ? `${Math.round(target > 0 ? (value / target) * 100 : done ? 100 : 0)}%`\n      : style === \"numeric\" || style === \"countdown\"\n        ? fmt(value)\n        : target > 0\n          ? `${fmt(value)} / ${fmt(target)}`\n          : fmt(value);\n\n  // Text-only forms.\n  if (style === \"fraction\" || style === \"numeric\" || style === \"countdown\") {\n    return (\n      <div className={cn(\"flex items-center gap-2\", className)}>\n        {labelEl}\n        <span className={readoutCls}>{readout}</span>\n      </div>\n    );\n  }\n\n  // Bar forms (\"bar\" | \"percent\").\n  return (\n    <div className={cn(\"flex flex-col gap-1\", className)}>\n      {label || readout ? (\n        <div className=\"flex items-baseline justify-between gap-2\">\n          {labelEl}\n          <span className={readoutCls}>{readout}</span>\n        </div>\n      ) : null}\n      <SegmentedBar\n        value={value}\n        max={target > 0 ? target : 1}\n        color={fill}\n        glow={false}\n        className=\"h-1.5\"\n        role=\"progressbar\"\n        aria-valuenow={value}\n        aria-valuemin={0}\n        aria-valuemax={target > 0 ? target : undefined}\n      />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/engram/objective-progress.tsx"
    }
  ],
  "meta": {
    "level": "component"
  },
  "docs": "Extend without forking: edit the copied source, use `asChild` (Radix Slot) to change the rendered element, pass the typed `annotations` prop for curated data (verdict/tags/per-plug), or use slot / render-prop props for arbitrary content. Requires the @engram/tokens theme (--engram-* CSS variables).",
  "categories": [
    "triumph"
  ],
  "type": "registry:component"
}