{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "progress-ring",
  "type": "registry:component",
  "title": "Progress ring",
  "description": "How much of a quota is used. Sample data: 72% of the token quota used, 7.2M used, 2.8M remaining, resets in 9 days.",
  "registryDependencies": [
    "@chartory/core",
    "@chartory/plot-pie"
  ],
  "files": [
    {
      "path": "registry/chartory/components/progress-ring.tsx",
      "type": "registry:component",
      "target": "components/chartory/progress-ring.tsx",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// Progress ring (Chartory chart 020): sizes xs | s | m | l, laid out again from the data at each\n// size; collection \"hearth\" (default) or \"signal\" (with @chartory/signal, Code and Complete). Data in the type's shape (default: the sample). Docs:\n// https://usechartory.com/docs/charts/progress-ring\nimport { ChartView, type ChartViewProps } from \"@/lib/chartory/charts/chart-view\"\nimport { registerChart } from \"@/lib/chartory/charts/defs\"\nimport { data as sample } from \"@/lib/chartory/charts/types/020-progress-ring/data\"\nimport { def } from \"@/lib/chartory/charts/types/020-progress-ring/layout\"\n\nregisterChart(def, sample)\n\nexport type ProgressRingData = typeof sample\nexport type ProgressRingProps = Omit<ChartViewProps, \"def\" | \"data\" | \"states\"> & {\n  /** the data to draw (default: the sample) */\n  data?: ProgressRingData\n  /** further data states: the chart morphs to them (update) or ticks through them (live, with interval) */\n  states?: ProgressRingData[]\n}\n\nexport function ProgressRing(props: ProgressRingProps) {\n  return <ChartView def={def} {...props} />\n}\n"
    },
    {
      "path": "registry/chartory/charts/types/020-progress-ring/data.ts",
      "type": "registry:lib",
      "target": "lib/chartory/charts/types/020-progress-ring/data.ts",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// 020 Progress ring · sample data (Figma chart/020, AI: token budget, 7.2M of 10M used on day 21 of 30).\nimport type { PieData } from \"../../plots/pie\";\n\nexport const data: PieData = {\n  inner: 0.84,\n  track: true,\n  of: 100,\n  slices: [{ label: \"used\", v: 72, tok: \"data/primary\" }],\n  center: { value: \"72%\", caps: \"USED\" },\n  rows: [\n    {  label: \"Used\", text: \"7.2M\"  },\n    {  label: \"Remaining\", text: \"2.8M\"  },\n    {  label: \"Forecast at reset\", text: \"10.4M\", tone: \"status/warning\"  },\n    {  label: \"Resets in\", text: \"9 days\"  },\n  ],\n};\n"
    },
    {
      "path": "registry/chartory/charts/types/020-progress-ring/layout.ts",
      "type": "registry:lib",
      "target": "lib/chartory/charts/types/020-progress-ring/layout.ts",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// 020 Progress ring (Figma chart/020, \"Token budget\"): C.pie as a thin ring on a track, the share in the centre.\nimport type { ChartDef, TooltipSpec } from \"../../core/shell\";\nimport { pie } from \"../../plots/pie\";\nimport { data } from \"./data\";\nimport { frame, type Frame, type Text } from \"../../core/node\";\n\n/** A text is named by its characters, so an update state takes the sample's text names (same order): its value\n *  labels then stay the same marks and morph in place instead of reading as new ones. */\nfunction keepNames(pl: Frame, ref: Frame): Frame {\n  const texts = (f: Frame): Text[] => f.children.flatMap((c) => (c.removed ? [] : c.kind === \"text\" ? [c] : c.kind === \"frame\" ? texts(c) : []));\n  const a = texts(pl), b = texts(ref);\n  if (a.length === b.length) a.forEach((t, i) => { if (t.name === t.text.slice(0, 40)) t.name = b[i].name; });\n  return pl;\n}\n\n/** Hover tooltip: the used arc (tokens used and the share of the budget). */\nconst tip = (d: unknown, g: number): TooltipSpec => {\n  const o = d as typeof data, s = o.slices[g];\n  return { kind: \"value\", text: `${o.rows[0].label} · ${o.rows[0].text} · ${Math.round((s.v / (o.of ?? 100)) * 100)}%` };\n};\n\nexport const def: ChartDef = {\n  no: 20,\n  slug: \"progress-ring\",\n  legend: null,\n  plot: (K, p, w, d) => {\n    const o = (d as typeof data | undefined) ?? data;\n    const pl = pie(K, p, w, o);\n    return o === data ? pl : keepNames(pl, pie(K, frame(null, \"sample\"), w, data));\n  },\n  tip,\n  update: (d, step) => {\n    const o = d as typeof data;\n    // two days later per step (day 21 → 23, 25, 27 of 30): usage, remaining, the forecast at reset and the days left\n    const day = 21 + 2 * step, used = [7.2, 7.9, 8.5, 9.2][Math.min(step, 3)] ?? 9.2, pct = Math.round(used * 10);\n    const rows = [used.toFixed(1) + \"M\", (10 - used).toFixed(1) + \"M\", ((used / day) * 30).toFixed(1) + \"M\", 30 - day + \" days\"];\n    return {\n      ...o,\n      slices: o.slices.map((s) => ({ ...s, v: pct })),\n      center: o.center && { ...o.center, value: pct + \"%\" },\n      rows: o.rows.map((r, i) => ({ ...r, text: rows[i] })),\n    };\n  },\n  describe: {\n    title: \"Token budget\",\n    summary: \"7.2M of a 10M token budget used (72%), 2.8M remaining; the forecast at reset is 10.4M, and the budget resets in 9 days.\",\n    table: { head: [\"Item\", \"Value\"], rows: data.rows.map((r) => [r.label, r.text]) },\n  },\n};\n"
    }
  ],
  "categories": [
    "charts",
    "part-of-a-whole"
  ],
  "meta": {
    "access": "free",
    "no": 20,
    "family": "Part of a whole"
  }
}
