{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "like-button",
  "title": "Like Button",
  "description": "Heart toggle with a spring pop, ring burst, particles and a rolling counter.",
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/new-york/like-button/like-button.tsx",
      "content": "\"use client\"\n\nimport { useState } from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\nimport { Heart } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\n\nexport interface LikeButtonProps\n  extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"onChange\" | \"children\"> {\n  /** Initial state when uncontrolled. Default: false */\n  defaultLiked?: boolean\n  /** Controlled liked state. */\n  liked?: boolean\n  /** Called with the next state when toggled. */\n  onChange?: (liked: boolean) => void\n  /** Like count, excluding the viewer's own like. Default: 128 */\n  count?: number\n  /** Heart, ring and particle color. Default: \"#f43f5e\" */\n  color?: string\n  /** Heart size in px. Default: 22 */\n  size?: number\n  /** Number of particles in the burst. Default: 8 */\n  particles?: number\n}\n\nexport function LikeButton({\n  defaultLiked = false,\n  liked: likedProp,\n  onChange,\n  count = 128,\n  color = \"#f43f5e\",\n  size = 22,\n  particles = 8,\n  className,\n  type = \"button\",\n  onClick,\n  ...props\n}: LikeButtonProps) {\n  const reduced = useReducedMotion()\n  const [internal, setInternal] = useState(defaultLiked)\n  const [burst, setBurst] = useState(0)\n  const liked = likedProp ?? internal\n  const total = count + (liked ? 1 : 0)\n\n  const toggle = (e: React.MouseEvent<HTMLButtonElement>) => {\n    onClick?.(e)\n    const next = !liked\n    if (likedProp === undefined) setInternal(next)\n    if (next) setBurst((b) => b + 1)\n    onChange?.(next)\n  }\n\n  const box = size * 2\n  const n = Math.max(0, Math.round(particles))\n  const dots = Array.from({ length: n }, (_, i) => {\n    const angle = (i / n) * Math.PI * 2 - Math.PI / 2\n    return { x: Math.cos(angle) * size * 1.15, y: Math.sin(angle) * size * 1.15, big: i % 2 === 0 }\n  })\n\n  return (\n    <button\n      type={type}\n      {...props}\n      aria-pressed={liked}\n      onClick={toggle}\n      className={cn(\n        \"group inline-flex items-center gap-1.5 rounded-full py-1.5 pl-1.5 pr-3.5 text-sm font-medium tabular-nums text-muted-foreground outline-none transition-colors hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50\",\n        className\n      )}\n    >\n      <span className=\"relative grid place-items-center\" style={{ width: box * 0.7, height: box * 0.7 }}>\n        {burst > 0 && liked && !reduced && (\n          <span key={burst} aria-hidden=\"true\" className=\"pointer-events-none absolute inset-0 grid place-items-center\">\n            <motion.span\n              className=\"absolute rounded-full\"\n              style={{ width: box, height: box, border: `2px solid ${color}` }}\n              initial={{ scale: 0.2, opacity: 0.9 }}\n              animate={{ scale: 1, opacity: 0, borderWidth: 0 }}\n              transition={{ duration: 0.5, ease: [0.2, 0.7, 0.3, 1] }}\n            />\n            {dots.map((d, i) => (\n              <motion.span\n                key={i}\n                className=\"absolute rounded-full\"\n                style={{ width: d.big ? 5 : 3.5, height: d.big ? 5 : 3.5, background: color }}\n                initial={{ x: 0, y: 0, scale: 0, opacity: 1 }}\n                animate={{ x: d.x, y: d.y, scale: [0, 1.2, 0], opacity: [1, 1, 0] }}\n                transition={{ duration: 0.6, delay: 0.08, ease: [0.2, 0.8, 0.3, 1] }}\n              />\n            ))}\n          </span>\n        )}\n        <motion.span\n          key={liked && !reduced ? `on-${burst}` : \"off\"}\n          className=\"relative grid place-items-center\"\n          initial={burst > 0 && liked && !reduced ? { scale: 0.3 } : false}\n          animate={{ scale: 1 }}\n          whileTap={reduced ? undefined : { scale: 0.85 }}\n          transition={{ type: \"spring\", stiffness: 520, damping: 14 }}\n        >\n          <Heart\n            aria-hidden=\"true\"\n            className={cn(\"transition-colors duration-200\", !liked && \"group-hover:text-foreground\")}\n            style={{ width: size, height: size, color: liked ? color : undefined }}\n            fill={liked ? color : \"transparent\"}\n            strokeWidth={liked ? 0 : 2}\n          />\n        </motion.span>\n      </span>\n      <span className=\"sr-only\">Like</span>\n      <RollingNumber value={total} up={liked} reduced={!!reduced} className={liked ? \"text-foreground\" : undefined} />\n    </button>\n  )\n}\n\nfunction RollingNumber({\n  value,\n  up,\n  reduced,\n  className,\n}: {\n  value: number\n  up: boolean\n  reduced: boolean\n  className?: string\n}) {\n  const digits = value.toLocaleString(\"en-US\").split(\"\")\n  const offset = reduced ? 0 : 1\n\n  return (\n    <span className={cn(\"inline-flex overflow-hidden transition-colors\", className)} aria-live=\"polite\">\n      {digits.map((d, i) => {\n        const position = digits.length - i\n        return (\n          <span key={position} className=\"relative inline-block\">\n            <AnimatePresence mode=\"popLayout\" initial={false} custom={up}>\n              <motion.span\n                key={d}\n                custom={up}\n                className=\"inline-block\"\n                variants={{\n                  enter: (goingUp: boolean) => ({ y: `${(goingUp ? 1 : -1) * offset * 100}%`, opacity: 0 }),\n                  center: { y: \"0%\", opacity: 1 },\n                  exit: (goingUp: boolean) => ({ y: `${(goingUp ? -1 : 1) * offset * 100}%`, opacity: 0 }),\n                }}\n                initial=\"enter\"\n                animate=\"center\"\n                exit=\"exit\"\n                transition={{ type: \"spring\", stiffness: 420, damping: 32 }}\n              >\n                {d}\n              </motion.span>\n            </AnimatePresence>\n          </span>\n        )\n      })}\n    </span>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "docs": "Docs, live playground and every prop: https://trytweenly.vercel.app/docs/components/like-button",
  "type": "registry:component"
}