{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "checkout-shipping-method",
  "type": "registry:component",
  "title": "Shipping method",
  "description": "Radio cards with a gliding accent frame and delivery dates computed from today in business days.",
  "author": "StoreKit <hello@storekit.dev>",
  "categories": [
    "checkout",
    "any"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://storekit-eight.vercel.app/r/cn.json",
    "https://storekit-eight.vercel.app/r/sk-analytics.json"
  ],
  "files": [
    {
      "path": "checkout/checkout-shipping-method.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, useReducedMotion } from \"motion/react\"\nimport { cn, money } from \"@/components/storekit/lib/cn\"\nimport { useSkBlock } from \"@/components/storekit/lib/sk-analytics\"\n\n/* Shipping method radio cards. Native radios (arrow keys work), an accent\n   frame that glides to the selected card via layoutId, and delivery dates\n   computed from today in business days. */\n\ntype Method = {\n  id: string\n  label: string\n  /** In cents. 0 renders \"Free\". */\n  price: number\n  /** Business days, [min, max]. */\n  days: [number, number]\n  note: string\n}\n\nconst METHODS: Method[] = [\n  { id: \"standard\", label: \"Standard\", price: 0, days: [3, 5], note: \"3 to 5 business days\" },\n  { id: \"express\", label: \"Express\", price: 1200, days: [1, 2], note: \"1 to 2 business days\" },\n  { id: \"overnight\", label: \"Overnight\", price: 2800, days: [1, 1], note: \"Next business day by 10am\" },\n]\n\nfunction addBusinessDays(from: Date, n: number) {\n  const d = new Date(from)\n  let left = n\n  while (left > 0) {\n    d.setDate(d.getDate() + 1)\n    const day = d.getDay()\n    if (day !== 0 && day !== 6) left--\n  }\n  return d\n}\n\nconst fmt = (d: Date) => d.toLocaleDateString(\"en-US\", { weekday: \"short\", month: \"short\", day: \"numeric\" })\n\nfunction estimate(m: Method, today: Date) {\n  const a = addBusinessDays(today, m.days[0])\n  if (m.days[0] === m.days[1]) return `Arrives ${fmt(a)}${m.id === \"overnight\" ? \" by 10am\" : \"\"}`\n  return `Arrives ${fmt(a)} to ${fmt(addBusinessDays(today, m.days[1]))}`\n}\n\nexport function CheckoutShippingMethod({\n  methods = METHODS,\n  defaultValue = \"standard\",\n  onChange,\n  className,\n}: {\n  methods?: Method[]\n  defaultValue?: string\n  onChange?: (id: string) => void\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  const sk = useSkBlock(\"checkout\", \"checkout-shipping-method\")\n  const [value, setValue] = React.useState(defaultValue)\n  const [today, setToday] = React.useState<Date | null>(null)\n  const uid = React.useId()\n  // Computed after mount so server and client agree on the markup.\n  React.useEffect(() => setToday(new Date()), [])\n\n  return (\n    <fieldset ref={sk.ref as React.Ref<HTMLFieldSetElement>} {...sk.attrs} className={cn(\"w-full\", className)}>\n      <legend className=\"font-sk-mono text-[11px] uppercase tracking-[0.14em] text-sk-muted\">Shipping method</legend>\n      <div className=\"mt-3 grid gap-2\">\n        {methods.map((m) => {\n          const selected = value === m.id\n          return (\n            <label\n              key={m.id}\n              className={cn(\n                \"relative flex min-h-[64px] cursor-pointer items-center gap-4 rounded-sk-card border border-sk-line bg-sk-card px-4 py-3 transition-colors duration-200\",\n                \"hover:bg-sk-ink/[0.02] has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-sk-accent has-[:focus-visible]:ring-offset-2 has-[:focus-visible]:ring-offset-sk-paper\"\n              )}\n            >\n              <input\n                type=\"radio\"\n                name={uid}\n                value={m.id}\n                checked={selected}\n                onChange={() => {\n                  setValue(m.id)\n                  sk.emit(\"interact\", `shipping_${m.id}`, m.price)\n                  onChange?.(m.id)\n                }}\n                className=\"sr-only\"\n              />\n              {selected ? (\n                <motion.span\n                  layoutId={`${uid}-frame`}\n                  aria-hidden\n                  className=\"pointer-events-none absolute -inset-px rounded-sk-card border-2 border-sk-accent\"\n                  transition={{ duration: reduce ? 0 : 0.35, ease: [0.16, 1, 0.3, 1] }}\n                />\n              ) : null}\n              <span aria-hidden className={cn(\"grid h-5 w-5 shrink-0 place-items-center rounded-full border transition-colors duration-200\", selected ? \"border-sk-accent\" : \"border-sk-muted/50\")}>\n                <span className={cn(\"h-2.5 w-2.5 rounded-full bg-sk-accent transition-[transform,opacity] duration-200\", selected ? \"scale-100 opacity-100\" : \"scale-50 opacity-0\")} />\n              </span>\n              <span className=\"min-w-0 flex-1\">\n                <span className=\"block text-sm font-medium text-sk-ink\">{m.label}</span>\n                <span className=\"block text-[13px] text-sk-muted\">{today ? estimate(m, today) : m.note}</span>\n              </span>\n              <span className={cn(\"text-sm font-medium tabular-nums\", m.price === 0 ? \"text-sk-savings\" : \"text-sk-price\")}>{m.price === 0 ? \"Free\" : money(m.price)}</span>\n            </label>\n          )\n        })}\n      </div>\n    </fieldset>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/storekit/checkout/checkout-shipping-method.tsx"
    }
  ],
  "meta": {
    "section": "checkout",
    "variant": "radio-cards",
    "niche": [
      "any"
    ],
    "commerce": [
      "checkout",
      "shipping-method",
      "delivery-estimate"
    ],
    "previewHeight": 320
  }
}