{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "announcement-promo",
  "type": "registry:component",
  "title": "Dismissible promo bar",
  "description": "Promo strip with a copyable code chip and a remembered dismiss.",
  "author": "StoreKit <hello@storekit.dev>",
  "categories": [
    "announcement-bar",
    "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": "announcement-bar/announcement-promo.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useState } from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\nimport { Check, Copy, X } from \"lucide-react\"\nimport { cn } from \"@/components/storekit/lib/cn\"\nimport { useSkBlock } from \"@/components/storekit/lib/sk-analytics\"\n\n/* Dismissible promo bar with a copyable code chip. Dismissal collapses the\n   bar's height and is remembered in localStorage under `storageKey`. Storage\n   access is guarded, so private windows simply show the bar again. */\n\nexport function AnnouncementPromo({\n  message = \"First order: 15% off your daily essentials\",\n  code = \"MORNING15\",\n  storageKey = \"sk-promo-dismissed\",\n  className,\n}: {\n  message?: string\n  code?: string\n  storageKey?: string\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  const sk = useSkBlock(\"announcement-bar\", \"announcement-promo\")\n  const [open, setOpen] = useState(false)\n  const [copied, setCopied] = useState(false)\n\n  useEffect(() => {\n    let dismissed = false\n    try {\n      dismissed = window.localStorage.getItem(storageKey) === \"1\"\n    } catch {}\n    setOpen(!dismissed)\n  }, [storageKey])\n\n  useEffect(() => {\n    if (!copied) return\n    const t = window.setTimeout(() => setCopied(false), 1200)\n    return () => window.clearTimeout(t)\n  }, [copied])\n\n  const copy = async () => {\n    try {\n      await navigator.clipboard.writeText(code)\n    } catch {}\n    setCopied(true)\n    sk.emit(\"interact\", \"copy_code\")\n  }\n\n  const dismiss = () => {\n    setOpen(false)\n    try {\n      window.localStorage.setItem(storageKey, \"1\")\n    } catch {}\n  }\n\n  return (\n    <div ref={sk.ref as React.Ref<HTMLDivElement>} {...sk.attrs}>\n      <AnimatePresence initial={false}>\n        {open ? (\n          <motion.div\n            key=\"promo\"\n            role=\"region\"\n            aria-label=\"Promotion\"\n            initial={reduce ? { opacity: 0 } : { height: 0, opacity: 0 }}\n            animate={reduce ? { opacity: 1 } : { height: \"auto\", opacity: 1 }}\n            exit={reduce ? { opacity: 0 } : { height: 0, opacity: 0 }}\n            transition={{ duration: 0.4, ease: [0.16, 1, 0.3, 1] }}\n            className={cn(\"overflow-hidden bg-sk-bar text-sk-bar-ink\", className)}\n          >\n            <div className=\"relative flex min-h-11 items-center justify-center gap-3 py-1.5 pl-4 pr-12 text-[13px]\">\n              <p className=\"min-w-0 leading-[1.35] text-balance\">{message}</p>\n              <button\n                type=\"button\"\n                onClick={copy}\n                aria-label={copied ? \"Code copied\" : `Copy code ${code}`}\n                className=\"relative inline-flex h-7 shrink-0 items-center gap-1.5 rounded-sk-chip before:absolute before:-inset-y-1.5 before:inset-x-0 before:content-[''] border border-dashed border-sk-bar-ink/40 px-2.5 font-sk-mono text-[11px] font-semibold uppercase tracking-[0.14em] outline-none transition-colors duration-200 hover:bg-sk-bar-ink/10 active:scale-[0.97] focus-visible:ring-2 focus-visible:ring-sk-bar-ink focus-visible:ring-offset-2 focus-visible:ring-offset-sk-bar\"\n              >\n                <AnimatePresence initial={false} mode=\"wait\">\n                  <motion.span\n                    key={copied ? \"copied\" : \"code\"}\n                    initial={{ opacity: 0, y: reduce ? 0 : 4 }}\n                    animate={{ opacity: 1, y: 0 }}\n                    exit={{ opacity: 0, y: reduce ? 0 : -4 }}\n                    transition={{ duration: 0.18 }}\n                    className=\"inline-flex items-center gap-1.5\"\n                  >\n                    {copied ? <Check size={12} aria-hidden /> : <Copy size={12} aria-hidden />}\n                    {copied ? \"Copied\" : code}\n                  </motion.span>\n                </AnimatePresence>\n              </button>\n              <span aria-live=\"polite\" className=\"sr-only\">{copied ? \"Code copied\" : \"\"}</span>\n              <button\n                type=\"button\"\n                onClick={dismiss}\n                aria-label=\"Dismiss promotion\"\n                className=\"absolute right-0 top-1/2 grid h-11 w-11 -translate-y-1/2 place-items-center opacity-70 outline-none transition-opacity duration-200 hover:opacity-100 focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-sk-bar-ink\"\n              >\n                <X size={16} aria-hidden />\n              </button>\n            </div>\n          </motion.div>\n        ) : null}\n      </AnimatePresence>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/storekit/announcement-bar/announcement-promo.tsx"
    }
  ],
  "meta": {
    "section": "announcement-bar",
    "variant": "promo",
    "niche": [
      "any"
    ],
    "commerce": [
      "promo",
      "discount"
    ],
    "previewHeight": 160
  }
}