{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cart-service-addon",
  "type": "registry:component",
  "title": "Service add-ons",
  "description": "One-tap switches for gift wrap and priority handling with prices on the label and a running add-on total.",
  "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-service-addon.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, useReducedMotion } from \"motion/react\"\nimport { Gift, Zap, type LucideIcon } from \"lucide-react\"\nimport { cn, money } from \"@/components/storekit/lib/cn\"\nimport { useSkBlock } from \"@/components/storekit/lib/sk-analytics\"\n\n/* One-tap service add-ons for the cart: gift wrap, priority handling and\n   the like. Each is a real switch with its price on the label, off by\n   default, and the running add-on total is shown underneath. */\n\nexport type ServiceAddon = { id: string; title: string; detail: string; price: number; icon?: \"gift\" | \"priority\" }\n\nconst ICONS: Record<NonNullable<ServiceAddon[\"icon\"]>, LucideIcon> = { gift: Gift, priority: Zap }\n\nconst SAMPLE: ServiceAddon[] = [\n  { id: \"gift-wrap\", title: \"Gift wrap\", detail: \"Recycled paper, no prices on the packing slip.\", price: 500, icon: \"gift\" },\n  { id: \"priority\", title: \"Priority handling\", detail: \"Packed first, ships the same business day before 2pm ET.\", price: 800, icon: \"priority\" },\n]\n\nexport function CartServiceAddon({\n  addons = SAMPLE,\n  defaultSelected = [],\n  onChange,\n  className,\n}: {\n  addons?: ServiceAddon[]\n  defaultSelected?: string[]\n  onChange?: (selected: ServiceAddon[], total: number) => void\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  const sk = useSkBlock(\"cart\", \"cart-service-addon\")\n  const [on, setOn] = React.useState<Record<string, boolean>>(() => Object.fromEntries(defaultSelected.map((id) => [id, true])))\n  const total = addons.reduce((n, a) => n + (on[a.id] ? a.price : 0), 0)\n\n  const toggle = (a: ServiceAddon) => {\n    const nextOn = { ...on, [a.id]: !on[a.id] }\n    setOn(nextOn)\n    sk.emit(\"interact\", `${a.id}_${nextOn[a.id] ? \"on\" : \"off\"}`, a.price)\n    const sel = addons.filter((x) => nextOn[x.id])\n    onChange?.(sel, sel.reduce((n, x) => n + x.price, 0))\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\", className)}>\n      <ul className=\"divide-y divide-sk-line\">\n        {addons.map((a) => {\n          const checked = !!on[a.id]\n          const Icon = a.icon ? ICONS[a.icon] : Gift\n          return (\n            <li key={a.id}>\n              <button\n                type=\"button\"\n                role=\"switch\"\n                aria-checked={checked}\n                onClick={() => toggle(a)}\n                className=\"flex min-h-[64px] w-full items-center gap-3 rounded-sk-card px-4 py-3 text-left transition-colors duration-200 hover:bg-sk-ink/[0.03] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-sk-accent active:scale-[0.99]\"\n              >\n                <span className={cn(\"grid h-9 w-9 shrink-0 place-items-center rounded-sk-chip transition-colors duration-200\", checked ? \"bg-sk-accent/15 text-sk-accent\" : \"bg-sk-ink/[0.06] text-sk-muted\")}>\n                  <Icon size={16} />\n                </span>\n                <span className=\"min-w-0 flex-1\">\n                  <span className=\"block text-sm font-medium text-sk-ink\">\n                    {a.title}{\" \"}\n                    <span className=\"ml-1 whitespace-nowrap tabular-nums text-sk-muted\">+{money(a.price)}</span>\n                  </span>\n                  <span className=\"block text-[13px] leading-[1.5] text-sk-muted\">{a.detail}</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\", checked ? \"bg-sk-accent\" : \"bg-sk-ink/20\")}>\n                  <motion.span\n                    initial={false}\n                    animate={{ x: checked ? 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)]\", checked ? \"bg-sk-accent-ink\" : \"bg-sk-paper\")}\n                  />\n                </span>\n              </button>\n            </li>\n          )\n        })}\n      </ul>\n      <div className=\"flex items-center justify-between border-t border-sk-line px-4 py-3 text-sm\" aria-live=\"polite\">\n        <span className=\"text-sk-muted\">Add-ons</span>\n        <span className=\"font-medium tabular-nums\">{total ? money(total) : \"None\"}</span>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/storekit/cart/cart-service-addon.tsx"
    }
  ],
  "meta": {
    "section": "cart",
    "variant": "switch",
    "niche": [
      "any"
    ],
    "commerce": [
      "addon"
    ],
    "previewHeight": 300
  }
}