{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cart-subscribe-switch",
  "type": "registry:component",
  "title": "Cart subscribe switch",
  "description": "Per-line Subscribe and save switch that updates the line to the per-delivery price and reveals a cadence picker.",
  "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/sk-analytics.json"
  ],
  "files": [
    {
      "path": "cart/cart-subscribe-switch.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\nimport { Repeat } from \"lucide-react\"\nimport { cn, money } from \"@/components/storekit/lib/cn\"\nimport { ProductImage } from \"@/components/storekit/ui/product-image\"\nimport { useSkBlock } from \"@/components/storekit/lib/sk-analytics\"\n\n/* Per-line subscribe and save in the cart. A switch converts the line to a\n   subscription, the line price drops to the per-delivery price with the\n   one-time price struck through, and a cadence picker appears. Terms are\n   stated plainly under it. */\n\nexport type SubscribeLine = { id: string; title: string; variant?: string; price: number; image: string; quantity: number; subscribed?: boolean; every?: number }\n\nconst SAMPLE: SubscribeLine[] = [\n  { id: \"magnesium-night\", title: \"Magnesium Night\", variant: \"60 gummies\", price: 2900, image: \"/brands/morrow/magnesium.webp\", quantity: 1, subscribed: true, every: 30 },\n  { id: \"electrolytes\", title: \"Electrolytes\", variant: \"30 sticks\", price: 3200, image: \"/brands/morrow/electrolytes.webp\", quantity: 1 },\n]\n\nexport function CartSubscribeSwitch({\n  lines: initial = SAMPLE,\n  percentOff = 15,\n  cadences = [30, 60, 90],\n  onChange,\n  className,\n}: {\n  lines?: SubscribeLine[]\n  percentOff?: number\n  /** Delivery cadences in days. */\n  cadences?: number[]\n  onChange?: (lines: SubscribeLine[]) => void\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  const sk = useSkBlock(\"cart\", \"cart-subscribe-switch\")\n  const [lines, setLines] = React.useState(initial)\n  const sub = (cents: number) => Math.round((cents * (100 - percentOff)) / 100)\n  const total = lines.reduce((n, l) => n + (l.subscribed ? sub(l.price) : l.price) * l.quantity, 0)\n\n  const update = (id: string, patch: Partial<SubscribeLine>) => {\n    const next = lines.map((l) => (l.id === id ? { ...l, ...patch } : l))\n    setLines(next)\n    onChange?.(next)\n  }\n\n  return (\n    <div ref={sk.ref as React.Ref<HTMLDivElement>} {...sk.attrs} className={cn(\"w-full\", className)}>\n      <ul className=\"divide-y divide-sk-line border-y border-sk-line\">\n        {lines.map((l) => {\n          const on = !!l.subscribed\n          const unit = on ? sub(l.price) : l.price\n          const every = l.every ?? cadences[0]\n          return (\n            <li key={l.id} className=\"py-4\">\n              <div className=\"flex gap-3\">\n                <ProductImage src={l.image} alt={l.title} className=\"h-16 w-16 shrink-0 rounded-sk-card border border-sk-line\" fit=\"cover\" zoomOnHover={false} />\n                <div className=\"min-w-0 flex-1\">\n                  <div className=\"flex items-start justify-between gap-3\">\n                    <div className=\"min-w-0\">\n                      <p className=\"truncate text-sm font-medium\">{l.title}</p>\n                      <p className=\"text-[13px] text-sk-muted\">\n                        {l.variant ? `${l.variant} / ` : null}Qty <span className=\"tabular-nums\">{l.quantity}</span>\n                      </p>\n                    </div>\n                    <p className=\"shrink-0 text-right text-sm tabular-nums\">\n                      <span className={cn(\"block font-semibold\", on ? \"text-sk-savings\" : \"text-sk-price\")}>{money(unit * l.quantity)}</span>\n                      {on ? <s className=\"block text-xs text-sk-muted\">{money(l.price * l.quantity)}</s> : null}\n                    </p>\n                  </div>\n                  <button\n                    type=\"button\"\n                    role=\"switch\"\n                    aria-checked={on}\n                    onClick={() => {\n                      update(l.id, { subscribed: !on, every })\n                      sk.emit(on ? \"interact\" : \"convert\", on ? \"subscribe_off\" : \"subscribe_on\", sub(l.price) * l.quantity)\n                    }}\n                    className=\"sk-btn mt-3 flex min-h-11 w-full items-center gap-3 rounded-sk-btn border border-sk-line px-3 py-2 text-left transition-colors duration-200 hover:bg-sk-ink/[0.03] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sk-accent active:scale-[0.99]\"\n                  >\n                    <Repeat size={15} className={cn(\"shrink-0\", on ? \"text-sk-accent\" : \"text-sk-muted\")} />\n                    <span className=\"min-w-0 flex-1 text-[13px] font-medium text-sk-ink\">\n                      Subscribe and save <span className=\"tabular-nums\">{percentOff}%</span>\n                    </span>\n                    <span aria-hidden className={cn(\"relative flex h-6 w-10 shrink-0 items-center rounded-sk-chip p-0.5 transition-colors duration-200\", on ? \"bg-sk-accent\" : \"bg-sk-ink/20\")}>\n                      <motion.span\n                        initial={false}\n                        animate={{ x: on ? 16 : 0 }}\n                        transition={reduce ? { duration: 0 } : { type: \"spring\", stiffness: 600, damping: 34 }}\n                        className={cn(\"h-5 w-5 rounded-sk-chip shadow-[0_1px_2px_rgb(0_0_0/0.25)]\", on ? \"bg-sk-accent-ink\" : \"bg-sk-paper\")}\n                      />\n                    </span>\n                  </button>\n                  <AnimatePresence initial={false}>\n                    {on ? (\n                      <motion.div\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=\"flex flex-wrap items-center gap-x-3 gap-y-2 pt-3\">\n                          <label htmlFor={`${l.id}-every`} className=\"text-[13px] text-sk-muted\">\n                            Deliver every\n                          </label>\n                          <select\n                            id={`${l.id}-every`}\n                            value={every}\n                            onChange={(e) => update(l.id, { every: Number(e.target.value) })}\n                            className=\"sk-btn h-10 rounded-sk-btn border border-sk-line bg-sk-card px-2 text-[13px] text-sk-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sk-accent\"\n                          >\n                            {cadences.map((c) => (\n                              <option key={c} value={c}>\n                                {c} days\n                              </option>\n                            ))}\n                          </select>\n                          <span className=\"text-xs tabular-nums text-sk-muted\">{money(unit)} per delivery. Skip or cancel any time.</span>\n                        </div>\n                      </motion.div>\n                    ) : null}\n                  </AnimatePresence>\n                </div>\n              </div>\n            </li>\n          )\n        })}\n      </ul>\n      <div className=\"flex items-baseline justify-between pt-3 text-sm\">\n        <span className=\"text-sk-muted\">Subtotal</span>\n        <span className=\"font-semibold tabular-nums\">{money(total)}</span>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/storekit/cart/cart-subscribe-switch.tsx"
    }
  ],
  "meta": {
    "section": "cart",
    "variant": "subscribe",
    "niche": [
      "any"
    ],
    "commerce": [
      "subscribe",
      "cart"
    ],
    "previewHeight": 420
  }
}