{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "upsell-cross-sell-rail",
  "type": "registry:component",
  "title": "Cross-sell rail",
  "description": "Pairs well with: a compact snapping rail of mini cards with quick add buttons.",
  "author": "StoreKit <hello@storekit.dev>",
  "categories": [
    "upsell",
    "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": "upsell/upsell-cross-sell-rail.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef, useState } from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\nimport { Check, Plus } from \"lucide-react\"\nimport { cn, money } from \"@/components/storekit/lib/cn\"\nimport { useSkBlock } from \"@/components/storekit/lib/sk-analytics\"\n\n/* \"Pairs well with\" rail. Compact cards on a snapping horizontal track, each\n   with a quick add icon button that confirms with a check. */\n\ntype RailItem = { id: string; title: string; price: number; image: string }\n\nconst EASE = [0.23, 1, 0.32, 1] as const\nconst RING = \"outline-none focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-sk-paper\"\n\nconst DEFAULT_ITEMS: RailItem[] = [\n  { id: \"electrolytes\", title: \"Electrolytes\", price: 3200, image: \"/brands/morrow/electrolytes.webp\" },\n  { id: \"magnesium-night\", title: \"Magnesium Night\", price: 2900, image: \"/brands/morrow/magnesium.webp\" },\n  { id: \"focus\", title: \"Focus\", price: 3900, image: \"/brands/morrow/focus.webp\" },\n  { id: \"sparkling-recovery\", title: \"Sparkling Recovery\", price: 3600, image: \"/brands/morrow/sparkling.webp\" },\n]\n\nfunction AddButton({ title, onAdd }: { title: string; onAdd?: () => void }) {\n  const reduce = useReducedMotion()\n  const [added, setAdded] = useState(false)\n  const timer = useRef<number | undefined>(undefined)\n  useEffect(() => () => window.clearTimeout(timer.current), [])\n  return (\n    <button\n      type=\"button\"\n      aria-label={added ? `${title} added` : `Add ${title} to cart`}\n      onClick={() => {\n        if (added) return\n        setAdded(true)\n        onAdd?.()\n        timer.current = window.setTimeout(() => setAdded(false), 1200)\n      }}\n      className={cn(\n        \"relative grid h-11 w-11 shrink-0 place-items-center overflow-hidden rounded-sk-btn border transition-[background-color,border-color,color,transform] duration-200 active:scale-[0.97]\",\n        added ? \"border-sk-accent bg-sk-accent text-sk-accent-ink\" : \"border-sk-line text-sk-ink hover:bg-sk-ink/[0.04]\",\n        RING\n      )}\n    >\n      <AnimatePresence mode=\"popLayout\" initial={false}>\n        <motion.span\n          key={added ? \"y\" : \"n\"}\n          initial={{ opacity: 0, scale: reduce ? 1 : 0.6 }}\n          animate={{ opacity: 1, scale: 1 }}\n          exit={{ opacity: 0, scale: reduce ? 1 : 0.6 }}\n          transition={{ duration: reduce ? 0 : 0.2, ease: EASE }}\n        >\n          {added ? <Check size={16} strokeWidth={2.5} /> : <Plus size={16} />}\n        </motion.span>\n      </AnimatePresence>\n    </button>\n  )\n}\n\nexport function UpsellCrossSellRail({\n  items = DEFAULT_ITEMS,\n  title = \"Pairs well with\",\n  onAdd,\n  className,\n}: {\n  items?: RailItem[]\n  title?: string\n  onAdd?: (id: string) => void\n  className?: string\n}) {\n  const sk = useSkBlock(\"upsell\", \"upsell-cross-sell-rail\")\n  return (\n    <section {...sk.attrs} ref={sk.ref as React.RefObject<HTMLElement>} className={cn(\"w-full\", className)} aria-label={title}>\n      <h3 className=\"mb-3 font-sk-mono text-[11px] uppercase tracking-[0.14em] text-sk-muted\">{title}</h3>\n      <ul className=\"-mx-1 flex snap-x snap-mandatory gap-3 overflow-x-auto scroll-px-1 px-1 pb-2 [scrollbar-width:thin]\">\n        {items.map((item) => (\n          <li key={item.id} className=\"w-[240px] shrink-0 snap-start\">\n            <div className=\"flex h-full items-center gap-3 rounded-sk-card border border-sk-line bg-sk-card p-2.5\">\n              <span className=\"relative h-14 w-14 shrink-0 overflow-hidden rounded-sk-btn bg-sk-surface\">\n                <img src={item.image} alt=\"\" loading=\"lazy\" className=\"absolute inset-0 h-full w-full object-cover\" />\n              </span>\n              <span className=\"flex min-w-0 flex-1 flex-col\">\n                <span className=\"line-clamp-2 hyphens-auto break-words text-[13px] font-medium leading-[1.3] text-sk-ink\">{item.title}</span>\n                <span className=\"text-[13px] font-semibold tabular-nums text-sk-price\">{money(item.price)}</span>\n              </span>\n              <AddButton\n                title={item.title}\n                onAdd={() => {\n                  sk.emit(\"convert\", \"add_to_cart\", item.price)\n                  onAdd?.(item.id)\n                }}\n              />\n            </div>\n          </li>\n        ))}\n      </ul>\n    </section>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/storekit/upsell/upsell-cross-sell-rail.tsx"
    }
  ],
  "meta": {
    "section": "upsell",
    "variant": "rail",
    "niche": [
      "any"
    ],
    "commerce": [
      "atc"
    ],
    "previewHeight": 200
  }
}