{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "badge",
  "type": "registry:ui",
  "title": "Badge",
  "description": "An accessible React badge for Next.js and Tailwind CSS: small status labels in five variants, including one with a light travelling round its edge.",
  "author": "FewTone <https://fewtone.com>",
  "dependencies": [
    "class-variance-authority@^0.7.1",
    "motion@^14.0.0",
    "radix-ui@^1.7.0"
  ],
  "registryDependencies": [
    "https://ui.fewtone.com/r/tokens.json",
    "https://ui.fewtone.com/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/fewtone/ui/badge.tsx",
      "type": "registry:ui",
      "content": "import { BadgeShine } from \"@/components/ui/badge-shine\";\nimport { cn } from \"@/lib/utils\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Slot } from \"radix-ui\";\nimport type { ComponentProps } from \"react\";\n\n/*\n * A small status label (owner's roadmap, 2026-10-16: \"Small status labels, including one with a shining edge\").\n * - shadcn's API: `variant` (default, secondary, destructive, outline) and `asChild`, plus `shine`.\n * - Shine is the outline badge with two lights travelling round its edge, the neutral twin of the site's Beta\n *   badge (which is Navy, and site-only). The lights are the label's own colour, a 1px line with no blur, so it\n *   never reads as a glow; the inside stays clear, so it sits on any surface. They live in BadgeShine, a small\n *   client part, so Badge itself stays server-safe. With reduced motion they rest on the left and right.\n * - Flat: no shadows. Rendered as a link (asChild), the background shifts on hover.\n */\n\nconst badgeVariants = cva(\n  [\n    \"inline-flex w-fit shrink-0 items-center justify-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium whitespace-nowrap\",\n    \"transition-colors duration-(--duration-fast) ease-out\",\n    \"outline-ring focus-visible:outline-(length:--focus-ring-width) focus-visible:outline-offset-(--focus-ring-offset)\",\n    \"[&>svg]:pointer-events-none [&>svg]:size-3 [&>svg]:shrink-0\",\n  ],\n  {\n    variants: {\n      variant: {\n        default: \"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90\",\n        secondary:\n          \"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground\",\n        destructive:\n          \"border-transparent bg-destructive text-destructive-foreground outline-destructive [a&]:hover:bg-destructive/90\",\n        outline: \"border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground\",\n        // The lights are drawn by BadgeShine, positioned over this edge\n        shine:\n          \"relative border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground\",\n      },\n    },\n    defaultVariants: { variant: \"default\" },\n  },\n);\n\nexport type BadgeProps = ComponentProps<\"span\"> &\n  VariantProps<typeof badgeVariants> & {\n    /** Render the single child instead of a span, for example a link, keeping the badge styles */\n    asChild?: boolean;\n  };\n\n/**\n * A small label for a status, a count or a category. It renders a `span`, or with `asChild` its single child\n * (a link, for example). `variant=\"shine\"` adds two lights travelling round the edge.\n */\nfunction Badge({ className, variant, asChild = false, children, ...props }: BadgeProps) {\n  const Comp = asChild ? Slot.Root : \"span\";\n  return (\n    <Comp\n      data-slot=\"badge\"\n      data-variant={variant ?? \"default\"}\n      className={cn(badgeVariants({ variant }), className)}\n      {...props}\n    >\n      {/* With asChild, Slottable marks the child that takes the styles, and the shine goes inside it */}\n      <Slot.Slottable>{children}</Slot.Slottable>\n      {variant === \"shine\" ? <BadgeShine /> : null}\n    </Comp>\n  );\n}\n\nexport { Badge, badgeVariants };\n"
    },
    {
      "path": "registry/fewtone/ui/badge-shine.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { duration } from \"@/lib/motion\";\nimport { motion, useReducedMotion } from \"motion/react\";\n\n/*\n * The lights travelling round a shine Badge's edge. The only client part of Badge, so Badge stays server-safe.\n * - How: a ring the size of the badge masks away everything but its 1px edge (a static mask, painted once).\n *   Inside it, a square holding a conic gradient (two lights on opposite sides, the site Beta badge's 15%\n *   spread) turns. Only a transform moves, so the browser composites it without repainting, and motion hands it\n *   to the Web Animations API, off the main thread. One lap takes 3s, the site badge's pace.\n * - The lights are `currentColor`, the label's colour, over the badge's own `border` edge: light on dark in dark\n *   mode, ink on light in light mode. A line only, no blur or spread, so it never reads as a glow.\n * - With reduced motion nothing turns, and the lights rest on the left and right.\n */\n\n// Everything but a 1px ring is masked away, so the badge stays clear inside. Only the mask's alpha counts. The\n// prefixed pair is for browsers without the standard property; the standard one comes last, so it wins.\nconst ring = {\n  borderRadius: \"inherit\",\n  WebkitMask: \"linear-gradient(black 0 0) content-box, linear-gradient(black 0 0)\",\n  WebkitMaskComposite: \"xor\",\n  mask: \"linear-gradient(black 0 0) content-box exclude, linear-gradient(black 0 0)\",\n} as const;\n\n// Two lights, each brightest at a quarter of the way round and gone 15% either side of it\nconst lights = {\n  background:\n    \"conic-gradient(transparent 10%, currentColor 25%, transparent 40% 60%, currentColor 75%, transparent 90%)\",\n} as const;\n\nfunction BadgeShine() {\n  const reduce = useReducedMotion() ?? false;\n  return (\n    <span\n      aria-hidden\n      data-slot=\"badge-shine\"\n      className=\"pointer-events-none absolute -inset-px overflow-clip p-px\"\n      style={ring}\n    >\n      {/* A square half as wide again as the badge, centred, so it covers every corner as it turns */}\n      <motion.span\n        key={reduce ? \"rest\" : \"turn\"}\n        className=\"absolute top-1/2 left-1/2 aspect-square w-3/2 -translate-x-1/2 -translate-y-1/2\"\n        style={lights}\n        animate={reduce ? undefined : { transform: [\"rotate(0turn)\", \"rotate(1turn)\"] }}\n        transition={{ duration: duration.slower * 6, ease: \"linear\", repeat: Number.POSITIVE_INFINITY }}\n      />\n    </span>\n  );\n}\n\nexport { BadgeShine };\n"
    }
  ],
  "categories": [
    "feedback"
  ],
  "docs": "Usage and examples: https://ui.fewtone.com/components/badge"
}
