{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "progress",
  "type": "registry:ui",
  "title": "Progress",
  "description": "Fluent 2-styled progress bar with a shadcn API. Wraps Base UI's Progress primitive (Root/Track/Indicator) into a single `Progress` component: a thin 2px determinate bar (`value` 0-100) plus a pulsing indeterminate stand-in when `value` is omitted or `null`.",
  "author": "graundtech <https://github.com/graundtech/fluent2-react-kit>",
  "dependencies": [
    "@base-ui/react",
    "class-variance-authority"
  ],
  "registryDependencies": [
    "https://fluent2-react-kit.graund.io/r/utils.json"
  ],
  "files": [
    {
      "path": "components/ui/progress.tsx",
      "type": "registry:ui",
      "target": "components/ui/progress.tsx",
      "content": "import { Progress as ProgressPrimitive } from \"@base-ui/react/progress\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Progress — Fluent 2-styled, shadcn-API progress bar.\n *\n * Base UI ships a Progress primitive (`@base-ui/react/progress`, imported as\n * a namespace exposing `Root` / `Track` / `Indicator` / `Label` / `Value`,\n * matching the actual export shape in node_modules — same pattern as\n * `avatar.tsx`). This is the batch's Base UI case (conventions §9): value→\n * percentage math, `role=\"progressbar\"`, `aria-valuemin`/`aria-valuemax`/\n * `aria-valuenow`/`aria-valuetext` wiring, and indeterminate detection\n * (`aria-valuenow` correctly omitted when `value` is `null`/`undefined`) are\n * exactly the kind of genuine behavior a primitive should own rather than\n * hand-rolling ARIA math. `Root`, `Track`, and `Indicator` are composed\n * internally into a single `Progress` export to match shadcn's flat API\n * (`<Progress value={33} />`) — the sub-parts are not re-exported, since the\n * spec calls for one component, not shadcn's newer multi-part variant.\n *\n * `\"use client\"` is intentionally omitted, mirroring `avatar.tsx`'s\n * reasoning: Base UI's Root/Track/Indicator modules each carry their own\n * `'use client'` directive at the source level, so they are already client\n * boundaries on their own. This wrapper file has no hooks/handlers of its\n * own — it only forwards props and branches on a plain value comparison — so\n * it stays a plain (server-renderable) module per conventions §2. Confirmed\n * via `pnpm typecheck` + the test suite.\n *\n * Aesthetics follow Fluent 2's ProgressBar, which is thin — 2px (`h-0.5`),\n * not shadcn's 8px `h-2` default. This is a deliberate, documented deviation\n * from the shadcn reference; consumers who want the shadcn-thick bar (or any\n * other height) can override via `className` (`cn` + tailwind-merge resolve\n * the conflict, last utility wins). Track is `bg-secondary` (flat neutral\n * fill) with `rounded-full overflow-hidden`; the indicator is `bg-primary`,\n * also `rounded-full`.\n *\n * Intent variants: Fluent's ProgressBar ships a `State` axis (Default/Success/\n * Error/Warning), so `Progress` exposes a matching `variant` prop\n * (`default`/`success`/`warning`/`destructive`) via `progressVariants` — a\n * `cva` on the indicator sub-part, the same precedent `spinner.tsx` sets by\n * putting its `cva` on the inner glyph. `success` (`bg-success` `#107c10`) and\n * `warning` (`bg-warning` `#da3b01`) are exact hex matches to Fluent's\n * Success/Warning `State`. `destructive` reuses `bg-destructive` (`#d13438`)\n * rather than Fluent's filled-status `#c50f1f`: this kit standardizes on one\n * destructive red across Badge/Progress/Label per the single-red policy\n * (docs/design/tokens-research.md §12.9), keeping consistency over a one-off\n * fifth red token. The indicator width is driven by Base UI's own computed\n * `style.width` percentage (`ProgressIndicator` reads `value`/`min`/`max`\n * from context and sets `width: ${percent}%` — this is \"the cleaner\" of the\n * two width strategies the spec allows: it needs no manual percent math on\n * our side, unlike shadcn's classic `translateX(-${100 - value}%)` recipe).\n * `transition-[width]` (not `transition-all`/`transform`, since Base UI\n * animates `width` directly) plus the token duration/easing pair animates\n * value changes; reduced motion is handled automatically by the token layer\n * (conventions §3.5) since `duration-normal`/`ease-ease` are token-driven.\n *\n * Indeterminate (`value` is `null`/undefined): Fluent's sliding segment. The\n * indicator renders a `w-1/3` segment driven by the token-layer\n * `--animate-progress-indeterminate` keyframes (tokens.css): it travels from\n * fully off-track left (`translate: -100%` of its own width) to fully\n * off-track right (`300%`), clipped by the root's `overflow-hidden`.\n *\n * Under `prefers-reduced-motion` the slide is fully stopped via\n * `motion-reduce:animate-none` (same call as `skeleton.tsx`, not `spinner.tsx`'s\n * slow-down): the pulse carries no information a reduced-motion user would\n * lose by holding it still — the indeterminate/busy state is conveyed by the\n * `role=\"progressbar\"` + omitted `aria-valuenow` to assistive tech and by the\n * bar's presence to sighted users — and a persistently animating region is\n * exactly what `prefers-reduced-motion` asks apps to avoid. `motion-reduce:\n * animate-none` is a plain Tailwind utility (not a `duration-*` token), so it\n * isn't covered by the automatic duration collapse `tokens.css` applies and\n * has to be declared here explicitly.\n *\n * `role=\"progressbar\"` requires an accessible name; Base UI does not default\n * one. Pass `aria-label` (forwarded straight through to the root via\n * `...props`) or compose Base UI's own `Progress.Label` alongside this\n * component — consumers must supply one or the other.\n */\nconst progressVariants = cva(\n  \"h-full rounded-full transition-[width] duration-normal ease-ease\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary\",\n        success: \"bg-success\",\n        warning: \"bg-warning\",\n        destructive: \"bg-destructive\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n);\n\nfunction Progress({\n  className,\n  value,\n  variant = \"default\",\n  size = \"default\",\n  ...props\n}: Omit<ComponentProps<typeof ProgressPrimitive.Root>, \"value\"> &\n  VariantProps<typeof progressVariants> & {\n    /**\n     * 0–100 (relative to `min`/`max`, both default 0/100). Omit or pass `null`\n     * for the indeterminate state — Base UI's Root then correctly omits\n     * `aria-valuenow` and this wrapper renders the pulsing stand-in indicator.\n     */\n    value?: number | null;\n    /**\n     * Fluent ProgressBar `Size` axis: `default` = Medium (2px track),\n     * `lg` = Large (4px) — surfaced by the pass-1 Figma validation.\n     */\n    size?: \"default\" | \"lg\";\n  }) {\n  return (\n    <ProgressPrimitive.Root\n      data-slot=\"progress\"\n      data-variant={variant}\n      data-size={size}\n      value={value ?? null}\n      className={cn(\n        \"relative w-full overflow-hidden rounded-full bg-secondary\",\n        size === \"lg\" ? \"h-1\" : \"h-0.5\",\n        className\n      )}\n      {...props}\n    >\n      <ProgressPrimitive.Track\n        data-slot=\"progress-track\"\n        className=\"relative h-full w-full\"\n      >\n        <ProgressPrimitive.Indicator\n          data-slot=\"progress-indicator\"\n          className={cn(\n            progressVariants({ variant }),\n            // Fluent indeterminate: the token-layer keyframes slide the 1/3\n            // segment from off-track left to off-track right (root clips).\n            value == null &&\n              \"w-1/3 animate-progress-indeterminate motion-reduce:animate-none\"\n          )}\n        />\n      </ProgressPrimitive.Track>\n    </ProgressPrimitive.Root>\n  );\n}\n\nexport { Progress, progressVariants };\n"
    }
  ]
}
