{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pdp-preorder",
  "type": "registry:component",
  "title": "Pre-order state",
  "description": "Pre-order CTA with release date, ship window, charge timing, due today and cancel terms right above it.",
  "author": "StoreKit <hello@storekit.dev>",
  "categories": [
    "pdp",
    "any"
  ],
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://storekit-eight.vercel.app/r/cn.json",
    "https://storekit-eight.vercel.app/r/badge.json",
    "https://storekit-eight.vercel.app/r/button.json",
    "https://storekit-eight.vercel.app/r/sk-analytics.json"
  ],
  "files": [
    {
      "path": "pdp/pdp-preorder.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef, useState } from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\nimport { CalendarClock, Check } from \"lucide-react\"\nimport { cn, money } from \"@/components/storekit/lib/cn\"\nimport { Badge } from \"@/components/storekit/ui/badge\"\nimport { Button } from \"@/components/storekit/ui/button\"\nimport { useSkBlock } from \"@/components/storekit/lib/sk-analytics\"\n\n/* Pre-order state for the buy box. The CTA says Pre-order, and the four\n   things a shopper needs before paying for something that does not exist\n   yet sit right above it: release date, ship window, when the card is\n   charged, and how to cancel. Dates are ISO strings, formatted in UTC so the\n   server and client render the same text. Prices are integer cents. */\n\nconst EASE = [0.16, 1, 0.3, 1] as const\n\nfunction fmt(iso: string) {\n  return new Date(`${iso}T12:00:00Z`).toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\", timeZone: \"UTC\" })\n}\n\nexport function PdpPreorder({\n  price = 5900,\n  quantityLimit = 2,\n  releaseDate = \"2026-11-03\",\n  shipFrom = \"2026-11-10\",\n  shipTo = \"2026-11-17\",\n  charge = \"ship\",\n  cancelTerms = \"Cancel any time before it ships for a full refund.\",\n  onPreorder,\n  className,\n}: {\n  price?: number\n  /** Per-customer cap, shown as a fact, not a nudge. 0 hides it. */\n  quantityLimit?: number\n  /** ISO dates, YYYY-MM-DD. */\n  releaseDate?: string\n  shipFrom?: string\n  shipTo?: string\n  /** \"now\" charges at checkout, \"ship\" authorizes now and charges on dispatch. */\n  charge?: \"now\" | \"ship\"\n  cancelTerms?: string\n  onPreorder?: () => void\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  const sk = useSkBlock(\"pdp\", \"pdp-preorder\")\n  const [status, setStatus] = useState<\"idle\" | \"loading\" | \"added\">(\"idle\")\n  const timers = useRef<number[]>([])\n  useEffect(() => () => timers.current.forEach((t) => window.clearTimeout(t)), [])\n\n  const dueToday = charge === \"now\" ? price : 0\n  const rows = [\n    { k: \"Releases\", v: fmt(releaseDate) },\n    { k: \"Ships\", v: `${fmt(shipFrom)} to ${fmt(shipTo)}` },\n    { k: \"Charged\", v: charge === \"now\" ? \"Today, at checkout\" : \"When your order ships\" },\n    { k: \"Due today\", v: money(dueToday) },\n  ]\n\n  const reserve = () => {\n    if (status !== \"idle\") return\n    setStatus(\"loading\")\n    sk.emit(\"convert\", \"preorder\", price)\n    timers.current.push(\n      window.setTimeout(() => {\n        setStatus(\"added\")\n        onPreorder?.()\n        timers.current.push(window.setTimeout(() => setStatus(\"idle\"), 1200))\n      }, 700)\n    )\n  }\n\n  return (\n    <div {...sk.attrs} ref={sk.ref as React.RefObject<HTMLDivElement>} className={cn(\"flex w-full flex-col gap-4\", className)}>\n      <div className=\"flex flex-wrap items-center gap-2\">\n        <Badge tone=\"ink\">Pre-order</Badge>\n        <span className=\"text-[13px] text-sk-muted\">First batch, limited run</span>\n      </div>\n      <dl className=\"divide-y divide-sk-line rounded-sk-card border border-sk-line bg-sk-card\">\n        {rows.map((r) => (\n          <div key={r.k} className=\"flex items-center justify-between gap-4 px-4 py-3 text-sm\">\n            <dt className=\"text-sk-muted\">{r.k}</dt>\n            <dd className=\"text-right font-medium tabular-nums text-sk-ink\">{r.v}</dd>\n          </div>\n        ))}\n      </dl>\n      <Button size=\"lg\" className=\"h-12 w-full active:scale-[0.97]\" loading={status === \"loading\"} onClick={reserve}>\n        <AnimatePresence mode=\"wait\" initial={false}>\n          <motion.span\n            key={status === \"added\" ? \"added\" : \"idle\"}\n            initial={{ opacity: 0, y: reduce ? 0 : 6 }}\n            animate={{ opacity: 1, y: 0 }}\n            exit={{ opacity: 0, y: reduce ? 0 : -6 }}\n            transition={{ duration: reduce ? 0 : 0.2, ease: EASE }}\n            className=\"inline-flex items-center gap-2 tabular-nums\"\n          >\n            {status === \"added\" ? (\n              <>\n                <Check size={16} strokeWidth={2.5} /> Reserved\n              </>\n            ) : (\n              <>Pre-order · {money(price)}</>\n            )}\n          </motion.span>\n        </AnimatePresence>\n      </Button>\n      <p className=\"flex items-start gap-2 text-[13px] leading-[1.5] text-sk-muted\">\n        <CalendarClock size={15} aria-hidden className=\"mt-0.5 shrink-0 text-sk-ink\" />\n        <span>\n          {cancelTerms}\n          {quantityLimit > 0 ? ` Limit ${quantityLimit} per order.` : \"\"}\n        </span>\n      </p>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/storekit/pdp/pdp-preorder.tsx"
    }
  ],
  "docs": "Dates are ISO strings formatted in UTC, so server and client render the same text.",
  "meta": {
    "section": "pdp",
    "variant": "preorder",
    "niche": [
      "any"
    ],
    "commerce": [
      "atc",
      "preorder"
    ],
    "previewHeight": 460
  }
}