{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "add-to-cart-button", "type": "registry:ui", "title": "Add To Cart Button", "description": "A morphing add-to-cart button with spring physics, slot-machine quantity flip, and live price updates.", "dependencies": [ "motion" ], "files": [ { "path": "registry/ruixenui/add-to-cart-button.tsx", "content": "\"use client\";\n\nimport { motion, AnimatePresence, type Transition } from \"motion/react\";\nimport { useState } from \"react\";\n\n/**\n * Add-to-cart morphing button — Rauno Freiberg style.\n *\n * Design rules applied:\n * 1. Monochrome — neutral-900 / white / one muted accent\n * 2. Spring physics everywhere — no cubic-bezier, real springs\n * 3. One morphing container — layout animates via spring\n * 4. Content crossfades — whole phrase, not per-character\n * 5. Only transform + opacity — zero filter:blur\n * 6. Fewer elements — no pills-in-pills, no checkmarks, no icons\n * 7. Slot-machine flip — the only \"flashy\" detail, earned\n * 8. Hover & press feel physical — scale springs with overshoot\n */\n\n/* ── spring presets ── */\n\nconst SPRING_LAYOUT: Transition = {\n type: \"spring\",\n stiffness: 400,\n damping: 30,\n};\n\nconst SPRING_CONTENT: Transition = {\n type: \"spring\",\n stiffness: 300,\n damping: 25,\n};\n\nconst SPRING_TAP: Transition = {\n type: \"spring\",\n stiffness: 500,\n damping: 30,\n};\n\n/* ── component ── */\n\ninterface AddToCartButtonProps {\n price?: number;\n currency?: string;\n maxQuantity?: number;\n onQuantityChange?: (quantity: number) => void;\n}\n\nexport function AddToCartButton({\n price = 24.99,\n currency = \"$\",\n maxQuantity = 99,\n onQuantityChange,\n}: AddToCartButtonProps) {\n const [added, setAdded] = useState(false);\n const [qty, setQty] = useState(1);\n const [locked, setLocked] = useState(false);\n\n const total = `${currency}${(price * qty).toFixed(2)}`;\n\n const lock = () => {\n setLocked(true);\n setTimeout(() => setLocked(false), 350);\n };\n\n const add = () => {\n if (locked) return;\n lock();\n setAdded(true);\n setQty(1);\n onQuantityChange?.(1);\n };\n\n const minus = () => {\n if (qty <= 1) {\n if (locked) return;\n lock();\n setAdded(false);\n onQuantityChange?.(0);\n return;\n }\n const n = qty - 1;\n setQty(n);\n onQuantityChange?.(n);\n };\n\n const plus = () => {\n const n = Math.min(qty + 1, maxQuantity);\n setQty(n);\n onQuantityChange?.(n);\n };\n\n return (\n