{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cart-auto-discount",
  "type": "registry:component",
  "title": "Automatic best offer",
  "description": "Prices every eligible offer against the cart, applies the best one without a code and explains why in a disclosure.",
  "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/sk-analytics.json"
  ],
  "files": [
    {
      "path": "cart/cart-auto-discount.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\nimport { BadgePercent, Check, ChevronDown } from \"lucide-react\"\nimport { cn, money } from \"@/components/storekit/lib/cn\"\nimport { useSkBlock } from \"@/components/storekit/lib/sk-analytics\"\n\n/* Automatic best discount. Every eligible offer is priced against the\n   current cart, the best one is applied without a code, and a disclosure\n   explains why it won and what the alternatives were worth. Offers do not\n   stack unless your backend says so. */\n\nexport type AutoOffer = {\n  id: string\n  label: string\n  /** Percent off the subtotal. */\n  percentOff?: number\n  /** Fixed cents off. */\n  amountOff?: number\n  /** Minimum subtotal in cents. */\n  minSubtotal?: number\n  /** Minimum item count. */\n  minItems?: number\n}\n\nconst SAMPLE_OFFERS: AutoOffer[] = [\n  { id: \"ten-over-150\", label: \"10% off orders over $150\", percentOff: 10, minSubtotal: 15000 },\n  { id: \"twenty-three-items\", label: \"$20 off 3 or more items\", amountOff: 2000, minItems: 3 },\n  { id: \"fifteen-over-250\", label: \"15% off orders over $250\", percentOff: 15, minSubtotal: 25000 },\n]\n\nexport function CartAutoDiscount({\n  subtotal = 20700,\n  itemCount = 3,\n  offers = SAMPLE_OFFERS,\n  shipping,\n  className,\n}: {\n  subtotal?: number\n  itemCount?: number\n  offers?: AutoOffer[]\n  /** Shipping in cents. Omit to show \"At checkout\". */\n  shipping?: number\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  const sk = useSkBlock(\"cart\", \"cart-auto-discount\")\n  const [open, setOpen] = React.useState(false)\n  const regionId = React.useId()\n\n  const priced = offers.map((o) => {\n    const eligible = subtotal >= (o.minSubtotal ?? 0) && itemCount >= (o.minItems ?? 0)\n    const value = Math.min(subtotal, (o.percentOff ? Math.round((subtotal * o.percentOff) / 100) : 0) + (o.amountOff ?? 0))\n    const gap = o.minSubtotal && subtotal < o.minSubtotal ? o.minSubtotal - subtotal : 0\n    const itemsGap = o.minItems && itemCount < o.minItems ? o.minItems - itemCount : 0\n    return { ...o, eligible, value, gap, itemsGap }\n  })\n  const best = priced.filter((o) => o.eligible).sort((a, b) => b.value - a.value)[0]\n  const total = subtotal - (best?.value ?? 0) + (shipping ?? 0)\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-5\", className)}>\n      <dl className=\"grid grid-cols-1 gap-2 text-sm\">\n        <div className=\"flex justify-between gap-4\">\n          <dt className=\"text-sk-muted\">Subtotal</dt>\n          <dd className=\"tabular-nums\">{money(subtotal)}</dd>\n        </div>\n        {best ? (\n          <div className=\"flex justify-between gap-4 text-sk-savings\">\n            <dt className=\"flex min-w-0 items-center gap-1.5\">\n              <BadgePercent size={14} className=\"shrink-0\" />\n              <span className=\"truncate\">{best.label}</span>\n            </dt>\n            <dd className=\"shrink-0 tabular-nums\">-{money(best.value)}</dd>\n          </div>\n        ) : null}\n        <div className=\"flex justify-between gap-4\">\n          <dt className=\"text-sk-muted\">Shipping</dt>\n          <dd className={cn(\"tabular-nums\", shipping === undefined && \"text-sk-muted\")}>{shipping === undefined ? \"At checkout\" : shipping === 0 ? \"Free\" : money(shipping)}</dd>\n        </div>\n        <div className=\"mt-1 flex items-baseline justify-between gap-4 border-t border-sk-line pt-3\">\n          <dt className=\"font-medium\">Total</dt>\n          <dd className=\"font-sk-display text-lg font-semibold tabular-nums text-sk-price\">{money(total)}</dd>\n        </div>\n      </dl>\n\n      <div className=\"mt-3 rounded-sk-btn bg-sk-savings/[0.08]\">\n        <button\n          type=\"button\"\n          aria-expanded={open}\n          aria-controls={regionId}\n          onClick={() => {\n            setOpen((o) => !o)\n            if (!open) sk.emit(\"interact\", \"why_this_offer\")\n          }}\n          className=\"flex min-h-11 w-full items-center gap-2 rounded-sk-btn px-3 text-left text-[13px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sk-accent active:scale-[0.99]\"\n        >\n          <Check size={14} className=\"shrink-0 text-sk-savings\" />\n          <span className=\"min-w-0 flex-1 text-sk-ink\">\n            {best ? \"Best offer applied. No code needed.\" : \"No offer applies yet.\"}\n          </span>\n          <span className=\"shrink-0 text-sk-muted\">Why</span>\n          <ChevronDown size={14} className={cn(\"shrink-0 text-sk-muted transition-transform duration-200\", open && \"rotate-180\")} />\n        </button>\n        <AnimatePresence initial={false}>\n          {open ? (\n            <motion.div\n              id={regionId}\n              initial={{ height: 0, opacity: 0 }}\n              animate={{ height: \"auto\", opacity: 1 }}\n              exit={{ height: 0, opacity: 0 }}\n              transition={{ duration: reduce ? 0 : 0.28, ease: [0.16, 1, 0.3, 1] }}\n              className=\"overflow-hidden\"\n            >\n              <div className=\"px-3 pb-3\">\n                <p className=\"text-xs leading-[1.5] text-sk-muted\">Offers do not combine. We compared each one against this cart and kept the largest saving.</p>\n                <ul className=\"mt-2 grid gap-1.5\">\n                  {priced.map((o) => (\n                    <li key={o.id} className=\"flex items-start justify-between gap-3 text-xs\">\n                      <span className={cn(\"min-w-0\", o.id === best?.id ? \"font-medium text-sk-ink\" : \"text-sk-muted\")}>{o.label}</span>\n                      <span className={cn(\"shrink-0 text-right tabular-nums\", o.id === best?.id ? \"font-medium text-sk-savings\" : \"text-sk-muted\")}>\n                        {o.eligible\n                          ? `${o.id === best?.id ? \"Applied \" : \"\"}-${money(o.value)}`\n                          : o.gap\n                            ? `${money(o.gap)} away`\n                            : `${o.itemsGap} more ${o.itemsGap === 1 ? \"item\" : \"items\"}`}\n                      </span>\n                    </li>\n                  ))}\n                </ul>\n              </div>\n            </motion.div>\n          ) : null}\n        </AnimatePresence>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/storekit/cart/cart-auto-discount.tsx"
    }
  ],
  "meta": {
    "section": "cart",
    "variant": "summary",
    "niche": [
      "any"
    ],
    "commerce": [
      "discount"
    ],
    "previewHeight": 420
  }
}