{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "atc-toast",
  "type": "registry:component",
  "title": "Add to cart toast",
  "description": "Confirmation toast with thumbnail, item and a View cart action. Auto-dismisses after 4s and pauses on hover or focus.",
  "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/atc-toast.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\nimport { Check, X } 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/* Add-to-cart confirmation toast. Thumbnail, item, price and a View cart\n   action. Dismisses itself after `duration` ms; hovering or focusing it\n   pauses the clock and leaving resumes with the time that was left.\n   Announced politely, never steals focus. */\n\nexport type AtcToastItem = { title: string; variant?: string; price: number; image: string; quantity?: number }\n\nconst SAMPLE_ITEM: AtcToastItem = { title: \"Electrolytes\", variant: \"30 sticks\", price: 3200, image: \"/brands/morrow/electrolytes.webp\", quantity: 1 }\n\nexport function AtcToast({\n  open,\n  onOpenChange,\n  item = SAMPLE_ITEM,\n  duration = 4000,\n  contained = false,\n  onViewCart,\n  className,\n}: {\n  open: boolean\n  onOpenChange: (open: boolean) => void\n  item?: AtcToastItem\n  /** Auto-dismiss delay in ms. */\n  duration?: number\n  /** Pin to the nearest relative parent instead of the viewport. */\n  contained?: boolean\n  onViewCart?: () => void\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  const sk = useSkBlock(\"cart\", \"atc-toast\")\n  const [paused, setPaused] = React.useState(false)\n  const remaining = React.useRef(duration)\n  const startedAt = React.useRef(0)\n\n  // Reset the clock each time the toast opens.\n  React.useEffect(() => {\n    if (open) remaining.current = duration\n  }, [open, duration])\n\n  React.useEffect(() => {\n    if (!open || paused) return\n    startedAt.current = Date.now()\n    const t = window.setTimeout(() => onOpenChange(false), remaining.current)\n    return () => window.clearTimeout(t)\n  }, [open, paused, onOpenChange])\n\n  const qty = item.quantity ?? 1\n  const pause = () => {\n    if (paused) return\n    remaining.current = Math.max(0, remaining.current - (Date.now() - startedAt.current))\n    setPaused(true)\n  }\n  const resume = () => setPaused(false)\n\n  return (\n    <div\n      aria-live=\"polite\"\n      className={cn(contained ? \"absolute\" : \"fixed\", \"inset-x-4 top-[max(1rem,env(safe-area-inset-top))] z-50 flex justify-center sm:inset-x-auto sm:right-4 sm:justify-end\", className)}\n    >\n      <AnimatePresence>\n        {open ? (\n          <motion.div\n            key=\"sk-atc-toast\"\n            ref={sk.ref as React.Ref<HTMLDivElement>}\n            {...sk.attrs}\n            role=\"status\"\n            onMouseEnter={pause}\n            onMouseLeave={resume}\n            onFocus={pause}\n            onBlur={resume}\n            initial={reduce ? { opacity: 0 } : { opacity: 0, y: -12, scale: 0.98 }}\n            animate={{ opacity: 1, y: 0, scale: 1 }}\n            exit={reduce ? { opacity: 0 } : { opacity: 0, y: -8, scale: 0.98 }}\n            transition={{ duration: reduce ? 0 : 0.4, ease: [0.16, 1, 0.3, 1] }}\n            className=\"w-full max-w-sm overflow-hidden rounded-sk-card border border-sk-line bg-sk-card shadow-[0_24px_60px_-24px_rgb(0_0_0/0.4)]\"\n          >\n            <div className=\"flex items-start gap-3 p-3\">\n              <ProductImage src={item.image} alt=\"\" className=\"h-14 w-14 shrink-0 rounded-sk-card\" fit=\"cover\" zoomOnHover={false} />\n              <div className=\"min-w-0 flex-1\">\n                <p className=\"flex items-center gap-1.5 text-[13px] font-medium text-sk-savings\">\n                  <Check size={14} />\n                  Added to cart\n                </p>\n                <p className=\"mt-0.5 truncate text-sm font-medium text-sk-ink\">{item.title}</p>\n                <p className=\"text-[13px] tabular-nums text-sk-muted\">\n                  {item.variant ? `${item.variant} / ` : null}\n                  {qty > 1 ? `${qty} x ` : null}\n                  {money(item.price)}\n                </p>\n              </div>\n              <button\n                type=\"button\"\n                aria-label=\"Dismiss\"\n                onClick={() => onOpenChange(false)}\n                className=\"-mr-1 -mt-1 grid h-11 w-11 shrink-0 place-items-center rounded-sk-btn text-sk-muted transition-colors duration-200 hover:bg-sk-ink/[0.05] hover:text-sk-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sk-accent active:scale-[0.97]\"\n              >\n                <X size={16} />\n              </button>\n            </div>\n            <div className=\"flex items-center justify-between gap-3 border-t border-sk-line px-3 py-2\">\n              <span className=\"min-w-0 text-xs text-sk-muted\">{paused ? \"Paused while you read\" : \"Closes on its own\"}</span>\n              <button\n                type=\"button\"\n                onClick={() => {\n                  sk.emit(\"interact\", \"view_cart\")\n                  onViewCart?.()\n                  onOpenChange(false)\n                }}\n                className=\"sk-btn inline-flex h-10 shrink-0 items-center whitespace-nowrap rounded-sk-btn bg-sk-accent px-4 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                View cart\n              </button>\n            </div>\n            {/* Time left, scaled on X so only transform animates. Freezes while paused. */}\n            <motion.div\n              aria-hidden\n              key={paused ? \"paused\" : \"running\"}\n              className=\"h-0.5 origin-left bg-sk-accent\"\n              initial={{ scaleX: remaining.current / duration }}\n              animate={{ scaleX: paused ? remaining.current / duration : 0 }}\n              transition={{ duration: paused || reduce ? 0 : remaining.current / 1000, ease: \"linear\" }}\n            />\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/storekit/cart/atc-toast.tsx"
    }
  ],
  "docs": "Controlled by open/onOpenChange. Reopen it on each add; the clock resets every time it opens.",
  "meta": {
    "section": "cart",
    "variant": "toast",
    "niche": [
      "any"
    ],
    "commerce": [
      "atc",
      "cart"
    ],
    "previewHeight": 480
  }
}