{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "progress",
  "type": "registry:ui",
  "title": "Progress",
  "description": "A thin rail for work that is happening now, such as an upload or an import. It fills on the instant tier, ends done or failed in words, and with no value yet stays empty: nothing sweeps, pulses or shimmers.",
  "registryDependencies": [
    "https://littmd.vercel.app/r/litt.json"
  ],
  "files": [
    {
      "path": "registry/litt/ui/progress.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n// @litt/progress 2026.11.06. What changed: https://littmd.vercel.app/changelog.md\n\n/**\n * Progress: how far along something is that is happening now, such as an upload, an import or a build\n * (components.md, \"Progress\"). A Meter shows a quantity against a limit and stands still; Progress moves while the\n * work goes on, then ends done or failed.\n *\n * - Anatomy: the Meter's lockup. The label at the small size in --litt-fg, the reading at the end in tabular figures at the caption\n *   size in --litt-fg-subtle (\"3.2 of 8 MB\", or the percent), and under them a slot cut into the page (R3, material.md 6: a\n *   slot is what fills), 8px tall at the control radius: the floor --litt-slot, the done part in it in --litt-fg. The walls\n *   (--litt-slot-shade: the top one's shade, the lit lower one) are drawn on a layer above the fill, so the fill sits down in\n *   the channel instead of covering its walls. Never the accent and never a data hue: it is one quantity.\n * - Ending: done fills the slot and the reading says \"Done\"; failed keeps the fill where it stopped, in --litt-danger (a status\n *   mark, which R7 allows), and the reading says \"Failed\". A message under the slot says the rest (what failed and what\n *   to do, or the time left).\n * - Not known yet: with no value the slot stays empty and the reading says so in words (\"Waiting\"). Nothing moves on its\n *   own: no stripes, no shimmer, no sweep. A wait that has no end in sight is a spinner in a button, not a slot.\n * - Motion: the done part is a full-width band scaled from the left, so only a transform changes, on the instant tier,\n *   which keeps up with a value that updates often. Its end is square: a radius would squash with the scale. Under\n *   reduced motion it jumps.\n * - Forced colors drops the walls and the floor, so the slot gets a real border and the fill takes CanvasText.\n * - Accessibility: role=\"progressbar\", named by the label, with aria-valuenow while the value is known and the reading\n *   as aria-valuetext. Ending done or failed is said once in a polite status, so a long wait need not be watched.\n */\nimport { useId, type ReactNode } from \"react\";\n\nexport type ProgressStatus = \"active\" | \"done\" | \"failed\";\n\nexport type ProgressProps = {\n  /** What is in progress: \"Uploading brief.pdf\", \"Importing 1,204 pages\". */\n  label: ReactNode;\n  /** How far along it is, from 0 to max. Leave it out while that is not known yet: the slot stays empty and nothing moves. */\n  value?: number;\n  /** The value that means finished. */\n  max?: number;\n  /** The reading at the end of the label row, and what a screen reader hears: \"3.2 of 8 MB\". Default: the percent. */\n  valueText?: string;\n  /** active while it runs; done fills the slot and says Done; failed keeps the fill where it stopped and says Failed. */\n  status?: ProgressStatus;\n  /** A line under the slot: the time left, or what failed and what to do about it. */\n  message?: ReactNode;\n  /** Hides the label row but keeps it as the name, for a row that already shows the name, such as a file in a list. */\n  hideLabel?: boolean;\n  className?: string;\n};\n\n/** A slot for work that is happening now: it fills as the value grows, then ends done or failed, said in words. */\nexport function Progress({ label, value, max = 100, valueText, status = \"active\", message, hideLabel = false, className = \"\" }: ProgressProps) {\n  const labelId = useId();\n  const messageId = useId();\n  const known = value !== undefined || status === \"done\";\n  const share = status === \"done\" ? 1 : max > 0 && value !== undefined ? Math.max(0, Math.min(1, value / max)) : 0;\n  const reading =\n    status === \"done\" ? \"Done\" : status === \"failed\" ? \"Failed\" : value === undefined ? (valueText ?? \"Waiting\") : (valueText ?? `${Math.round(share * 100)}%`);\n  const tone = status === \"failed\" ? \"text-litt-danger\" : \"text-litt-fg-subtle\";\n\n  return (\n    <div className={\"grid min-w-0 gap-2 \" + className}>\n      <div className={hideLabel ? \"sr-only\" : \"flex items-baseline justify-between gap-4\"}>\n        <span id={labelId} className=\"min-w-0 truncate text-litt-small text-litt-fg\">\n          {label}\n        </span>\n        <span className={\"shrink-0 text-litt-caption font-normal tabular-nums \" + tone}>{reading}</span>\n      </div>\n      <div\n        role=\"progressbar\"\n        aria-labelledby={labelId}\n        aria-describedby={message ? messageId : undefined}\n        aria-valuemin={0}\n        aria-valuemax={max}\n        aria-valuenow={known ? Math.round(share * max) : undefined}\n        aria-valuetext={reading}\n        className=\"relative h-2 overflow-hidden rounded-litt-control bg-litt-slot in-[[data-material=flat]]:bg-litt-surface-3 forced-colors:border\"\n      >\n        {/* A full-width band scaled from the left: only a transform changes as the value grows. */}\n        <div\n          className={\n            \"h-full w-full origin-left transition-transform duration-(--litt-dur-instant) ease-litt forced-colors:bg-[CanvasText] \" +\n            (status === \"failed\" ? \"bg-litt-danger\" : \"bg-litt-fg\")\n          }\n          style={{ transform: `scaleX(${share})` }}\n        />\n        <span aria-hidden=\"true\" className=\"pointer-events-none absolute inset-0 rounded-litt-control shadow-(--litt-slot-shade) in-[[data-material=flat]]:shadow-none\" />\n      </div>\n      {message && (\n        <p id={messageId} className={\"text-litt-small \" + (status === \"failed\" ? \"text-litt-danger\" : \"text-litt-fg-subtle\")}>\n          {message}\n        </p>\n      )}\n      {/* The end is said once, so nobody has to watch the slot. Kept mounted, so the change is announced. */}\n      <span role=\"status\" className=\"sr-only\">\n        {status === \"active\" ? \"\" : `${typeof label === \"string\" ? `${label}: ` : \"\"}${status === \"done\" ? \"done\" : \"failed\"}.`}\n      </span>\n    </div>\n  );\n}\n",
      "target": "components/litt/progress.tsx"
    }
  ],
  "docs": "Import Progress from components/litt/progress. Pass a label, a value and a max, and status=\"done\" or \"failed\" when it ends. A quantity that does not move while you watch is a Meter.",
  "meta": {
    "version": "2026.11.06",
    "released": "2026-11-06",
    "changelog": "https://littmd.vercel.app/changelog.md"
  }
}
