{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "spinner",
  "type": "registry:ui",
  "title": "Spinner",
  "description": "Fluent 2-styled loading indicator: a rotating brand-colored arc (not a full ring) over an optional faint track, four sizes, and an optional visible label that becomes the accessible name.",
  "author": "graundtech <https://github.com/graundtech/fluent2-react-kit>",
  "dependencies": [
    "class-variance-authority"
  ],
  "registryDependencies": [
    "https://fluent2-react-kit.graund.io/r/utils.json"
  ],
  "files": [
    {
      "path": "components/ui/spinner.tsx",
      "type": "registry:ui",
      "target": "components/ui/spinner.tsx",
      "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Spinner — Fluent 2-styled, shadcn-API loading indicator.\n *\n * Fluent 2's spinner is a rotating brand-colored ARC (not a full ring). It is\n * drawn as an inline SVG: a faint full-circle \"track\" plus a shorter,\n * round-capped arc in `text-primary` (brand). The whole SVG spins via\n * `animate-spin`; because only the arc has visible stroke, rotating the\n * (rotationally symmetric) track alongside it is visually inert — only the arc\n * appears to move.\n *\n * Track color: Fluent's Primary spinner track is `BrandStroke2Contrast.Rest`\n * = `#b4d6fa`, which is this kit's `--brand-140` exactly — a pale *blue* tint,\n * not the neutral grey an earlier version used. So the track is\n * `text-brand-140`. Dark: the Figma file had only a light frame, so the dark\n * pairing is derived — `dark:text-brand-40` (a low, subtle brand stop) keeps\n * the track a faint blue that reads clearly *behind* the arc without colliding\n * with the arc's own dark color (`--primary` = `brand-70` in dark).\n *\n * Sizes map 1:1 onto Tailwind's `size-*` scale so the numbers in the spec\n * line up exactly: sm=16px (`size-4`), default=24px (`size-6`), lg=32px\n * (`size-8`), xl=40px (`size-10`).\n *\n * Labeled spinner: when `label` is set, Fluent renders visible text next to\n * the glyph (default `labelPosition=\"after\"`) — reproduced here as\n * `text-sm text-foreground` to the right of the arc via `gap-2`. The label\n * text also becomes the accessible name (see a11y notes on `Spinner` below).\n *\n * Reduced motion: `animate-spin` is a CSS animation, not a token-driven\n * transition, so it does not get the automatic duration collapse tokens.css\n * applies to `duration-*` utilities. Per spec we deliberately slow rather than\n * stop the spin under `prefers-reduced-motion` — `motion-reduce:[animation-duration:3s]`\n * — because hiding the animation entirely would remove the only visual signal\n * that progress is still happening (the wrapper's `role=\"status\"` covers the\n * non-visual signal either way).\n *\n * Server-safe: no `\"use client\"`, no hooks/handlers — the React import is\n * type-only and the animation is pure CSS, so this drops straight into an RSC\n * tree.\n */\nconst spinnerVariants = cva(\n  [\"shrink-0\", \"animate-spin\", \"motion-reduce:[animation-duration:3s]\"],\n  {\n    variants: {\n      size: {\n        sm: \"size-4\",\n        default: \"size-6\",\n        lg: \"size-8\",\n        xl: \"size-10\",\n      },\n    },\n    defaultVariants: {\n      size: \"default\",\n    },\n  }\n);\n\nfunction Spinner({\n  className,\n  size = \"default\",\n  label,\n  \"aria-label\": ariaLabel,\n  ...props\n}: ComponentProps<\"span\"> &\n  VariantProps<typeof spinnerVariants> & {\n    /**\n     * Visible text rendered beside the arc (Fluent's \"labeled spinner\").\n     * When set, this text is also the element's accessible name.\n     */\n    label?: string;\n  }) {\n  // Accessible name precedence: visible label > explicit aria-label override\n  // > default \"Loading\". `aria-label` is pulled out of the rest spread so it\n  // can't be set twice on the root element. Use `||` (not `??`) so an *empty*\n  // string falls through to the next option: an empty `aria-label` is ignored\n  // by the accessible-name computation, which would leave the role=status\n  // region unnamed — `label=\"\"` must still resolve to \"Loading\".\n  const accessibleName = label || ariaLabel || \"Loading\";\n\n  return (\n    <span\n      role=\"status\"\n      aria-label={accessibleName}\n      data-slot=\"spinner\"\n      data-size={size}\n      className={cn(\"inline-flex items-center gap-2\", className)}\n      {...props}\n    >\n      <svg\n        viewBox=\"0 0 16 16\"\n        fill=\"none\"\n        aria-hidden=\"true\"\n        className={spinnerVariants({ size })}\n      >\n        {/* faint full-circle track — Fluent BrandStroke2Contrast (#b4d6fa =\n            brand-140); dark derived to a low brand stop (see doc comment) */}\n        <circle\n          cx=\"8\"\n          cy=\"8\"\n          r=\"7\"\n          stroke=\"currentColor\"\n          strokeWidth=\"2\"\n          className=\"text-brand-140 dark:text-brand-40\"\n        />\n        {/* rotating brand arc — ~100° of the circle, round-capped */}\n        <circle\n          cx=\"8\"\n          cy=\"8\"\n          r=\"7\"\n          stroke=\"currentColor\"\n          strokeWidth=\"2\"\n          strokeLinecap=\"round\"\n          pathLength=\"100\"\n          strokeDasharray=\"28 72\"\n          className=\"text-primary\"\n        />\n      </svg>\n      {label ? <span className=\"text-sm text-foreground\">{label}</span> : null}\n    </span>\n  );\n}\n\nexport { Spinner, spinnerVariants };\n"
    }
  ]
}
