{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "spinner",
  "type": "registry:ui",
  "dependencies": [
    "class-variance-authority",
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "spinner.tsx",
      "target": "components/ui/spinner.tsx",
      "type": "registry:ui",
      "content": "import { cva } from \"class-variance-authority\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type SpinnerVariant = \"ring\" | \"dots\" | \"bars\" | \"orbit\";\nexport type SpinnerSize = \"sm\" | \"md\" | \"lg\";\nexport type SpinnerTone = \"accent\" | \"current\";\n\nexport const spinnerVariants = cva(\n  cn(\n    \"inline-block shrink-0 rounded-full\",\n    \"animate-spin motion-reduce:animate-none\",\n  ),\n  {\n    variants: {\n      size: {\n        sm: \"h-4 w-4 border-[1.5px]\",\n        md: \"h-6 w-6 border-[1.5px]\",\n        lg: \"h-8 w-8 border-2\",\n      },\n      tone: {\n        accent:\n          \"border-border border-t-accent [filter:drop-shadow(var(--usva-glow-accent))]\",\n        current: \"border-current/25 border-t-current\",\n      },\n    },\n    defaultVariants: { size: \"md\", tone: \"accent\" },\n  },\n);\n\nconst box: Record<SpinnerSize, string> = {\n  sm: \"h-4 w-4\",\n  md: \"h-6 w-6\",\n  lg: \"h-8 w-8\",\n};\n\nconst dot: Record<SpinnerSize, string> = {\n  sm: \"size-1\",\n  md: \"size-1.5\",\n  lg: \"size-2\",\n};\n\n/** `current` inherits the parent's text colour, so it stays legible on a filled surface. */\nconst fill: Record<SpinnerTone, string> = {\n  accent: \"bg-accent\",\n  current: \"bg-current\",\n};\n\nconst glow: Record<SpinnerTone, string> = {\n  accent: \"[filter:drop-shadow(var(--usva-glow-accent))]\",\n  current: \"\",\n};\n\nconst strongGlow: Record<SpinnerTone, string> = {\n  accent: \"[filter:drop-shadow(var(--usva-glow-accent-strong))]\",\n  current: \"\",\n};\n\nconst track: Record<SpinnerTone, string> = {\n  accent: \"border-border/60\",\n  current: \"border-current/20\",\n};\n\ninterface RendererProps {\n  size: SpinnerSize;\n  tone: SpinnerTone;\n}\n\nfunction Ring({ size, tone }: RendererProps) {\n  return <span className={spinnerVariants({ size, tone })} />;\n}\n\nfunction Dots({ size, tone }: RendererProps) {\n  return (\n    <span\n      className={cn(\n        \"inline-flex items-center\",\n        size === \"lg\" ? \"gap-2\" : \"gap-1\",\n      )}\n    >\n      {[0, 1, 2].map((i) => (\n        <span\n          key={i}\n          style={{ animationDelay: `${i * 160}ms` }}\n          className={cn(\n            \"rounded-full animate-loader-pulse motion-reduce:animate-none\",\n            fill[tone],\n            dot[size],\n            glow[tone],\n          )}\n        />\n      ))}\n    </span>\n  );\n}\n\nfunction Bars({ size, tone }: RendererProps) {\n  const width = size === \"sm\" ? \"w-0.5\" : size === \"lg\" ? \"w-1\" : \"w-[3px]\";\n  return (\n    <span\n      className={cn(\n        \"inline-flex items-center\",\n        box[size],\n        size === \"sm\" ? \"gap-0.5\" : \"gap-1\",\n      )}\n    >\n      {[0, 1, 2, 3].map((i) => (\n        <span\n          key={i}\n          style={{ animationDelay: `${i * 130}ms` }}\n          className={cn(\n            \"h-full origin-center rounded-full animate-loader-bar motion-reduce:animate-none\",\n            fill[tone],\n            width,\n          )}\n        />\n      ))}\n    </span>\n  );\n}\n\nfunction Orbit({ size, tone }: RendererProps) {\n  return (\n    <span\n      className={cn(\n        \"relative inline-block shrink-0 animate-spin motion-reduce:animate-none\",\n        box[size],\n      )}\n    >\n      <span\n        className={cn(\"absolute inset-0 rounded-full border\", track[tone])}\n      />\n      <span\n        className={cn(\n          \"absolute left-1/2 top-0 -translate-x-1/2 -translate-y-1/2 rounded-full\",\n          fill[tone],\n          dot[size],\n          strongGlow[tone],\n        )}\n      />\n    </span>\n  );\n}\n\nconst RENDERERS: Record<\n  SpinnerVariant,\n  (p: RendererProps) => React.ReactElement\n> = {\n  ring: Ring,\n  dots: Dots,\n  bars: Bars,\n  orbit: Orbit,\n};\n\nexport interface SpinnerProps\n  extends Omit<React.HTMLAttributes<HTMLSpanElement>, \"children\"> {\n  size?: SpinnerSize;\n  variant?: SpinnerVariant;\n  tone?: SpinnerTone;\n  label?: string;\n}\n\nexport const Spinner = React.forwardRef<HTMLSpanElement, SpinnerProps>(\n  (\n    {\n      className,\n      size = \"md\",\n      variant = \"ring\",\n      tone = \"accent\",\n      label = \"Loading\",\n      ...props\n    },\n    ref,\n  ) => {\n    const Impl = RENDERERS[variant];\n    return (\n      <span\n        ref={ref}\n        role=\"status\"\n        className={cn(\"inline-flex\", className)}\n        {...props}\n      >\n        <Impl size={size} tone={tone} />\n        <span className=\"sr-only\">{label}</span>\n      </span>\n    );\n  },\n);\nSpinner.displayName = \"Spinner\";\n\nexport interface PageLoaderProps\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n  label?: string;\n  size?: SpinnerSize;\n  variant?: SpinnerVariant;\n  tone?: SpinnerTone;\n}\n\nexport const PageLoader = React.forwardRef<HTMLDivElement, PageLoaderProps>(\n  (\n    {\n      className,\n      label,\n      size = \"lg\",\n      variant = \"ring\",\n      tone = \"accent\",\n      ...props\n    },\n    ref,\n  ) => (\n    <div\n      ref={ref}\n      className={cn(\n        \"flex min-h-[12rem] w-full flex-col items-center justify-center gap-3\",\n        className,\n      )}\n      {...props}\n    >\n      <Spinner\n        size={size}\n        variant={variant}\n        tone={tone}\n        label={label ?? \"Loading\"}\n      />\n      {label ? (\n        <p\n          aria-hidden=\"true\"\n          className=\"animate-reveal font-mono text-xs lowercase tracking-wide text-muted\"\n        >\n          {label}\n        </p>\n      ) : null}\n    </div>\n  ),\n);\nPageLoader.displayName = \"PageLoader\";\n"
    }
  ]
}