{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "badge",
  "title": "Badge",
  "description": "Badges pop in, and a count rolls its digits up or down when the number changes.",
  "dependencies": [],
  "registryDependencies": [
    "@keyframery/motion"
  ],
  "files": [
    {
      "path": "registry/components/keyframery/primitives/badge.tsx",
      "content": "\"use client\"\n\n/* Animated badge. UI SDK has no badge, so this is a plain span with shadcn's part name: swap the\n   import and keep your markup. This file adds motion only. Colours, radii and spacing stay yours. */\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst keyframes =\n  \"@keyframes kf-badge-in{from{opacity:0;scale:.6}}\" +\n  \"@keyframes kf-badge-ping{from{opacity:.55;scale:1}to{opacity:0;scale:3.4}}\"\n\n/* Roll: each digit is a column of 0 to 9 that slides to its value on a spring, so a count rolls up\n   when it grows and down when it shrinks. Columns are keyed from the right, so a new leading digit\n   rises in from below instead of every digit shifting. Exported for other numbers. */\nfunction Roll({ text, className }: { text: string; className?: string }) {\n  const chars = text.split(\"\")\n  return (\n    <span\n      aria-hidden\n      data-slot=\"roll\"\n      className={cn(\n        \"inline-flex h-[1lh] overflow-hidden whitespace-pre tabular-nums [mask-image:linear-gradient(transparent,black_18%,black_82%,transparent)]\",\n        className,\n      )}\n    >\n      {chars.map((c, i) => {\n        const key = chars.length - i\n        return /\\d/.test(c) ? (\n          <span\n            key={key}\n            style={{ \"--d\": Number(c) } as React.CSSProperties}\n            className=\"flex flex-col transition-[translate] duration-[calc(520ms*var(--kf-speed,1))] ease-spring [translate:0_calc(var(--d)*-1lh)] starting:[translate:0_1lh] motion-reduce:transition-none!\"\n          >\n            {Array.from({ length: 10 }, (_, d) => (\n              <span key={d} className=\"h-[1lh]\">\n                {d}\n              </span>\n            ))}\n          </span>\n        ) : (\n          <span key={key}>{c}</span>\n        )\n      })}\n    </span>\n  )\n}\n\nfunction textOf(node: React.ReactNode): string {\n  return typeof node === \"string\" || typeof node === \"number\" ? String(node) : \"\"\n}\n\nfunction Badge({\n  className,\n  count,\n  max,\n  dot,\n  children,\n  ...props\n}: React.ComponentProps<\"span\"> & {\n  /** Renders the number as rolling digits that roll up or down when it changes. */\n  count?: number\n  /** Above this, shows `max+`. */\n  max?: number\n  /** A leading status dot that pulses once whenever the badge's value (count or text) changes. */\n  dot?: boolean\n}) {\n  const text = count === undefined ? \"\" : max !== undefined && count > max ? `${max}+` : String(count)\n\n  /* Count changes of the value (React's \"adjust state while rendering\" pattern, no effect).\n     Each change remounts the pulse, so it plays exactly once; nothing pulses on first render. */\n  const value = count === undefined ? textOf(children) : text\n  const [seen, setSeen] = React.useState(value)\n  const [pulses, setPulses] = React.useState(0)\n  if (seen !== value) {\n    setSeen(value)\n    setPulses((n) => n + 1)\n  }\n\n  return (\n    <>\n      <style href=\"kf-badge\" precedence=\"default\">\n        {keyframes}\n      </style>\n      <span\n        data-slot=\"badge\"\n        data-dot={dot || undefined}\n        className={cn(\n          \"animate-[kf-badge-in_calc(380ms*var(--kf-speed,1))_var(--ease-pop)_backwards] motion-reduce:animate-none!\",\n          className,\n        )}\n        {...props}\n      >\n        {dot && (\n          <span data-slot=\"badge-dot\" aria-hidden className=\"relative inline-grid size-1.5 shrink-0\">\n            <span\n              key={pulses}\n              className={cn(\n                \"[grid-area:1/1] rounded-full bg-current\",\n                pulses > 0 && \"animate-[kf-badge-in_calc(420ms*var(--kf-speed,1))_var(--ease-pop)_backwards] motion-reduce:animate-none!\",\n              )}\n            />\n            {pulses > 0 && (\n              <span\n                key={`ping-${pulses}`}\n                className=\"[grid-area:1/1] rounded-full bg-current opacity-0 animate-[kf-badge-ping_calc(800ms*var(--kf-speed,1))_var(--ease-house)] motion-reduce:animate-none!\"\n              />\n            )}\n          </span>\n        )}\n        {count !== undefined && (\n          <>\n            <Roll text={text} />\n            <span className=\"sr-only\">{text}</span>\n          </>\n        )}\n        {children}\n      </span>\n    </>\n  )\n}\n\nexport { Badge, Roll }\n",
      "type": "registry:component",
      "target": "components/keyframery/primitives/badge.tsx"
    },
    {
      "path": "registry/components/keyframery/badge.tsx",
      "content": "\"use client\"\n\n/* Styled badge: the animated primitive plus the Keyframery look. Same variants as shadcn/ui's badge,\n   plus `success`. */\n\nimport * as React from \"react\"\n\nimport { Badge as BadgePrimitive } from \"@/components/keyframery/primitives/badge\"\nimport { cn } from \"@/lib/utils\"\n\nconst variants = {\n  default: \"bg-primary text-primary-foreground shadow-[inset_0_1px_0_rgb(255_255_255/0.14),0_1px_2px_rgb(0_0_0/0.18)]\",\n  secondary:\n    \"bg-secondary text-secondary-foreground shadow-[inset_0_0_0_1px_rgb(0_0_0/0.04)] dark:shadow-[inset_0_0_0_1px_rgb(255_255_255/0.05)]\",\n  outline: \"text-foreground shadow-[inset_0_0_0_1px_var(--input)]\",\n  destructive: \"bg-destructive/12 text-destructive dark:bg-destructive/18\",\n  success:\n    \"bg-[color-mix(in_oklch,oklch(0.62_0.16_150)_14%,transparent)] text-[color-mix(in_oklch,oklch(0.62_0.16_150)_82%,var(--foreground))] dark:text-[oklch(0.62_0.16_150)] dark:bg-[color-mix(in_oklch,oklch(0.62_0.16_150)_18%,transparent)]\",\n} as const\n\nfunction Badge({\n  className,\n  variant = \"default\",\n  ...props\n}: React.ComponentProps<typeof BadgePrimitive> & { variant?: keyof typeof variants }) {\n  return (\n    <BadgePrimitive\n      data-variant={variant}\n      className={cn(\n        \"inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1.5 rounded-full px-2 text-[11px] leading-none font-medium whitespace-nowrap tabular-nums\",\n        \"transition-[background-color,color,box-shadow] duration-[calc(200ms*var(--kf-speed,1))] ease-house motion-reduce:transition-none!\",\n        \"[&>svg]:size-3 [&>svg]:shrink-0 [&_[data-slot=roll]]:leading-[15px] has-data-[slot=roll]:min-w-5 has-data-[slot=roll]:px-1.5\",\n        variants[variant],\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Badge }\n",
      "type": "registry:component",
      "target": "components/keyframery/badge.tsx"
    }
  ],
  "type": "registry:component"
}