{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cart-buy-more",
  "type": "registry:component",
  "title": "Buy more, save more",
  "description": "Line-level quantity break message with one-click add of the missing units; shows the applied discount once reached.",
  "author": "StoreKit <hello@storekit.dev>",
  "categories": [
    "cart",
    "any"
  ],
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://storekit-eight.vercel.app/r/cn.json",
    "https://storekit-eight.vercel.app/r/product-image.json",
    "https://storekit-eight.vercel.app/r/stepper.json",
    "https://storekit-eight.vercel.app/r/sk-analytics.json"
  ],
  "files": [
    {
      "path": "cart/cart-buy-more.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\nimport { Check, Plus, Tag } from \"lucide-react\"\nimport { cn, money } from \"@/components/storekit/lib/cn\"\nimport { ProductImage } from \"@/components/storekit/ui/product-image\"\nimport { Stepper } from \"@/components/storekit/ui/stepper\"\nimport { useSkBlock } from \"@/components/storekit/lib/sk-analytics\"\n\n/* Buy more, save more at the line level. The line names the next quantity\n   break and adds the missing units in one click. Once a break is reached the\n   discount shows as applied and the message moves to the next break. */\n\nexport type QtyBreak = { qty: number; percentOff: number }\n\nexport type BuyMoreLine = { id: string; title: string; variant?: string; price: number; image: string; quantity: number }\n\nconst SAMPLE: BuyMoreLine = { id: \"daily-greens\", title: \"Daily Greens\", variant: \"30 servings\", price: 4900, image: \"/brands/morrow/greens.webp\", quantity: 1 }\nconst BREAKS: QtyBreak[] = [\n  { qty: 2, percentOff: 10 },\n  { qty: 3, percentOff: 15 },\n]\n\nexport function CartBuyMore({\n  line = SAMPLE,\n  breaks = BREAKS,\n  max = 10,\n  onQuantityChange,\n  className,\n}: {\n  line?: BuyMoreLine\n  breaks?: QtyBreak[]\n  max?: number\n  onQuantityChange?: (qty: number) => void\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  const sk = useSkBlock(\"cart\", \"cart-buy-more\")\n  const [qty, setQty] = React.useState(line.quantity)\n  const sorted = React.useMemo(() => [...breaks].sort((a, b) => a.qty - b.qty), [breaks])\n  const active = [...sorted].reverse().find((b) => qty >= b.qty)\n  const next = sorted.find((b) => qty < b.qty)\n  const pct = active?.percentOff ?? 0\n  const full = line.price * qty\n  const total = Math.round((full * (100 - pct)) / 100)\n\n  const set = (q: number) => {\n    setQty(q)\n    onQuantityChange?.(q)\n  }\n  const addMissing = () => {\n    if (!next) return\n    const add = next.qty - qty\n    set(next.qty)\n    sk.emit(\"convert\", \"buy_more\", line.price * add)\n  }\n\n  return (\n    <div ref={sk.ref as React.Ref<HTMLDivElement>} {...sk.attrs} className={cn(\"w-full rounded-sk-card border border-sk-line bg-sk-card p-4\", className)}>\n      <div className=\"flex gap-3\">\n        <ProductImage src={line.image} alt={line.title} className=\"h-20 w-20 shrink-0 rounded-sk-card border border-sk-line\" fit=\"cover\" zoomOnHover={false} />\n        <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n          <div className=\"flex items-start justify-between gap-3\">\n            <div className=\"min-w-0\">\n              <p className=\"truncate text-sm font-medium\">{line.title}</p>\n              {line.variant ? <p className=\"text-[13px] text-sk-muted\">{line.variant}</p> : null}\n            </div>\n            <p className=\"shrink-0 text-right text-sm tabular-nums\">\n              <span className={cn(\"block font-semibold\", pct ? \"text-sk-savings\" : \"text-sk-price\")}>{money(total)}</span>\n              {pct ? <s className=\"block text-xs text-sk-muted\">{money(full)}</s> : null}\n            </p>\n          </div>\n          <Stepper value={qty} onChange={set} size=\"sm\" max={max} className=\"self-start\" />\n        </div>\n      </div>\n\n      <div className=\"mt-3 grid grid-cols-1 gap-2\" aria-live=\"polite\">\n        <AnimatePresence initial={false} mode=\"popLayout\">\n          {active ? (\n            <motion.p\n              key={`on-${active.qty}`}\n              initial={reduce ? { opacity: 0 } : { opacity: 0, y: 6 }}\n              animate={{ opacity: 1, y: 0 }}\n              exit={{ opacity: 0 }}\n              transition={{ duration: reduce ? 0 : 0.3, ease: [0.16, 1, 0.3, 1] }}\n              className=\"flex items-center gap-2 text-[13px] font-medium text-sk-savings\"\n            >\n              <Check size={14} />\n              <span>\n                <span className=\"tabular-nums\">{active.percentOff}%</span> off applied, you save <span className=\"tabular-nums\">{money(full - total)}</span>\n              </span>\n            </motion.p>\n          ) : null}\n        </AnimatePresence>\n        {next && qty < max ? (\n          <div className=\"flex items-center gap-3 rounded-sk-btn bg-sk-surface px-3 py-2\">\n            <Tag size={14} className=\"shrink-0 text-sk-muted\" />\n            <p className=\"min-w-0 flex-1 text-[13px] text-sk-ink\">\n              Add <span className=\"tabular-nums\">{next.qty - qty}</span> more for <span className=\"font-semibold tabular-nums\">{next.percentOff}%</span> off this item\n            </p>\n            <button\n              type=\"button\"\n              onClick={addMissing}\n              className=\"sk-btn inline-flex h-10 shrink-0 items-center gap-1 rounded-sk-btn bg-sk-accent px-3 text-[13px] font-medium text-sk-accent-ink transition-[filter,transform] duration-200 hover:brightness-[1.06] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-sk-card active:scale-[0.97]\"\n            >\n              <Plus size={14} />\n              Add <span className=\"tabular-nums\">{next.qty - qty}</span>\n            </button>\n          </div>\n        ) : null}\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/storekit/cart/cart-buy-more.tsx"
    }
  ],
  "meta": {
    "section": "cart",
    "variant": "tiers",
    "niche": [
      "any"
    ],
    "commerce": [
      "quantity",
      "discount",
      "bulk"
    ],
    "previewHeight": 440
  }
}