{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pdp-bundle-tiers",
  "type": "registry:component",
  "title": "Bundle tiers",
  "description": "Buy 1, 3 or 5 tiles with per unit price, total and savings. The selection frame glides between tiles.",
  "author": "StoreKit <hello@storekit.dev>",
  "categories": [
    "pdp",
    "any"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://storekit-eight.vercel.app/r/cn.json",
    "https://storekit-eight.vercel.app/r/sk-analytics.json"
  ],
  "files": [
    {
      "path": "pdp/pdp-bundle-tiers.tsx",
      "content": "\"use client\"\n\nimport { useId, useRef, useState } 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/* Quantity bundle tiles for inside a buy box. The accent frame travels to\n   the chosen tile with a shared layoutId. Each tile shows per unit, total and\n   what you save against buying singles. */\n\ntype Tier = { qty: number; discount: number; label?: string; popular?: boolean }\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_TIERS: Tier[] = [\n  { qty: 1, discount: 0 },\n  { qty: 3, discount: 0.1, popular: true },\n  { qty: 5, discount: 0.18 },\n]\n\nexport function PdpBundleTiers({\n  unitPrice = 5900,\n  tiers = DEFAULT_TIERS,\n  defaultQty,\n  onChange,\n  className,\n}: {\n  unitPrice?: number\n  tiers?: Tier[]\n  defaultQty?: number\n  onChange?: (tier: Tier) => void\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  const uid = useId()\n  const sk = useSkBlock(\"pdp\", \"pdp-bundle-tiers\")\n  const [qty, setQty] = useState(defaultQty ?? tiers.find((t) => t.popular)?.qty ?? tiers[0]?.qty)\n  const refs = useRef<(HTMLButtonElement | null)[]>([])\n  const current = Math.max(0, tiers.findIndex((t) => t.qty === qty))\n\n  const select = (i: number, focus = false) => {\n    const t = tiers[i]\n    if (t.qty !== qty) sk.emit(\"interact\", \"bundle_tier\", Math.round(unitPrice * (1 - t.discount)) * t.qty)\n    setQty(t.qty)\n    onChange?.(t)\n    if (focus) refs.current[i]?.focus()\n  }\n\n  const onKeyDown = (e: React.KeyboardEvent) => {\n    const n = tiers.length\n    if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") {\n      e.preventDefault()\n      select((current + 1) % n, true)\n    } else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") {\n      e.preventDefault()\n      select((current - 1 + n) % n, true)\n    }\n  }\n\n  return (\n    <div {...sk.attrs} ref={sk.ref as React.RefObject<HTMLDivElement>} role=\"radiogroup\" aria-label=\"Bundle size\" className={cn(\"grid w-full grid-cols-3 gap-2 pt-3\", className)}>\n      {tiers.map((t, i) => {\n        const selected = t.qty === qty\n        const each = Math.round(unitPrice * (1 - t.discount))\n        const total = each * t.qty\n        const saved = unitPrice * t.qty - total\n        return (\n          <button\n            key={t.qty}\n            ref={(el) => {\n              refs.current[i] = el\n            }}\n            type=\"button\"\n            role=\"radio\"\n            aria-checked={selected}\n            tabIndex={selected ? 0 : -1}\n            onClick={() => select(i)}\n            onKeyDown={onKeyDown}\n            className={cn(\n              \"relative flex min-h-[44px] flex-col items-center gap-1 rounded-sk-card border border-sk-line bg-sk-card px-1.5 pb-3 pt-4 text-center sm:px-2 transition-[border-color,transform] duration-200 hover:border-sk-ink/30 active:scale-[0.97]\",\n              RING\n            )}\n          >\n            {selected ? (\n              <motion.span\n                layoutId={`${uid}-frame`}\n                aria-hidden\n                transition={{ duration: reduce ? 0 : 0.4, ease: EASE }}\n                className=\"pointer-events-none absolute -inset-px rounded-sk-card border-2 border-sk-accent bg-sk-accent/[0.05]\"\n              />\n            ) : null}\n            {t.popular ? (\n              <span className=\"absolute -top-2.5 left-1/2 -translate-x-1/2 whitespace-nowrap rounded-sk-chip bg-sk-accent px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.06em] text-sk-accent-ink\">\n                <span className=\"sm:hidden\">Popular</span>\n                <span className=\"hidden sm:inline\">Most popular</span>\n              </span>\n            ) : t.discount > 0 ? (\n              <span className=\"absolute -top-2.5 left-1/2 -translate-x-1/2 whitespace-nowrap rounded-sk-chip border border-sk-savings/40 bg-sk-card px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.06em] text-sk-savings\">\n                Save {Math.round(t.discount * 100)}%\n              </span>\n            ) : null}\n            <span className=\"relative text-sm font-medium text-sk-ink\">{t.label ?? `Buy ${t.qty}`}</span>\n            <span className=\"relative text-[15px] font-semibold tabular-nums text-sk-price sm:text-base\">{money(each)}</span>\n            <span className=\"relative font-sk-mono text-[11px] tabular-nums text-sk-muted\">each</span>\n            <span className=\"relative mt-1 flex flex-wrap justify-center gap-x-1 text-[11px] leading-tight tabular-nums text-sk-muted sm:text-[12px]\">\n              <span className=\"whitespace-nowrap\">{money(total)}</span>\n              <span>total</span>\n            </span>\n            <span className={cn(\"relative flex flex-wrap justify-center gap-x-1 text-[11px] font-medium leading-tight tabular-nums sm:text-[12px]\", saved > 0 ? \"text-sk-savings\" : \"text-sk-muted\")}>\n              {saved > 0 ? (\n                <>\n                  <span>Save</span>\n                  <span className=\"whitespace-nowrap\">{money(saved)}</span>\n                </>\n              ) : (\n                <>\n                  <span>Single</span>\n                  <span>unit</span>\n                </>\n              )}\n            </span>\n          </button>\n        )\n      })}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/storekit/pdp/pdp-bundle-tiers.tsx"
    }
  ],
  "meta": {
    "section": "pdp",
    "variant": "bundle",
    "niche": [
      "any"
    ],
    "commerce": [
      "bundle",
      "quantity"
    ],
    "previewHeight": 300
  }
}