{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pricing-calculator",
  "type": "registry:ui",
  "title": "Pricing calculator",
  "description": "Explains changing price through quantity, tier, and billing interval.",
  "dependencies": [
    "motion"
  ],
  "categories": [
    "data-commerce"
  ],
  "docs": "https://motion-lexicon.pages.dev/en/components/pricing-calculator/",
  "meta": {
    "engines": [
      "motion"
    ],
    "runtimeCost": "light",
    "signature": "Controls, breakdown, and total update together",
    "sceneFamily": "product-mono",
    "motionRole": "ui",
    "primaryState": "Annual studio-plan breakdown",
    "assetProvenance": "self-contained"
  },
  "files": [
    {
      "path": "src/registry/components/pricing-calculator.tsx",
      "type": "registry:ui",
      "target": "components/motion-lexicon/pricing-calculator.tsx",
      "content": "\"use client\";\n\nimport { useId, useMemo, useState } from \"react\";\nimport { motion, useReducedMotion } from \"motion/react\";\n\nexport type PricingTier = { id: string; label: string; unitPrice: number; description?: string };\nexport type PricingCalculatorProps = { tiers: readonly PricingTier[]; currency?: string; label?: string; className?: string; onChange?: (total: number) => void };\n\nexport function PricingCalculator({ tiers, currency = \"USD\", label = \"Pricing calculator\", className = \"\", onChange }: PricingCalculatorProps) {\n  const reduced = useReducedMotion() === true; const id = useId(); const [tierIndex, setTierIndex] = useState(0); const [quantity, setQuantity] = useState(12); const [annual, setAnnual] = useState(true); const tier = tiers[tierIndex]; const subtotal = (tier?.unitPrice ?? 0) * quantity; const discount = annual ? Math.round(subtotal * .18) : 0; const total = subtotal - discount; const money = useMemo(() => new Intl.NumberFormat(\"en\", { style: \"currency\", currency, maximumFractionDigits: 0 }), [currency]);\n  const updateQuantity = (next: number) => { const safe = Math.max(1, Math.min(250, next)); setQuantity(safe); onChange?.(((tiers[tierIndex]?.unitPrice ?? 0) * safe) * (annual ? .82 : 1)); };\n  return <section aria-labelledby={id} className={`rounded-[16px] border border-black/[.1] bg-white p-4 shadow-[0_14px_34px_-27px_rgba(31,31,31,.38)] ${className}`}><div className=\"flex items-start justify-between gap-3\"><div><span className=\"font-mono text-[10px] uppercase tracking-[.16em] text-neutral-500\">{label}</span><h3 id={id} className=\"mt-1 text-[15px] font-medium text-[#292929]\">Build your plan</h3></div><button type=\"button\" aria-pressed={annual} onClick={() => setAnnual((value) => !value)} className=\"min-h-11 rounded-full border border-black/[.1] px-3 text-[10px] outline-none hover:bg-neutral-50 focus-visible:ring-2 focus-visible:ring-[#4568FF]\">{annual ? \"Annual · 18% off\" : \"Monthly\"}</button></div><div role=\"radiogroup\" aria-label=\"Pricing tier\" className=\"mt-5 grid grid-cols-3 gap-1.5\">{tiers.map((item, index) => <button key={item.id} type=\"button\" role=\"radio\" aria-checked={index === tierIndex} onClick={() => { setTierIndex(index); onChange?.((item.unitPrice * quantity) * (annual ? .82 : 1)); }} className={`min-h-11 rounded-lg border px-2 text-[11px] outline-none focus-visible:ring-2 focus-visible:ring-[#4568FF] ${index === tierIndex ? \"border-[#242424] bg-[#242424] text-white\" : \"border-black/[.1] text-neutral-600\"}`}>{item.label}</button>)}</div><label className=\"mt-5 block text-[11px] text-neutral-600\" htmlFor={`${id}-quantity`}>Seats <span className=\"float-right font-mono\">{quantity}</span><input id={`${id}-quantity`} type=\"range\" min=\"1\" max=\"250\" value={quantity} onChange={(event) => updateQuantity(Number(event.target.value))} className=\"mt-2 w-full accent-[#242424]\" /></label><dl className=\"mt-4 space-y-1.5 border-t border-black/[.08] pt-3 text-[11px] text-neutral-600\"><div className=\"flex justify-between\"><dt>{tier?.label ?? \"Plan\"} × {quantity}</dt><dd>{money.format(subtotal)}</dd></div>{annual ? <div className=\"flex justify-between text-emerald-700\"><dt>Annual saving</dt><dd>−{money.format(discount)}</dd></div> : null}<div className=\"flex justify-between border-t border-black/[.08] pt-2 text-[14px] font-medium text-[#292929]\"><dt>Total / month</dt><motion.dd key={total} initial={reduced ? false : { opacity: 0, y: 6 }} animate={{ opacity: 1, y: 0 }}>{money.format(total)}</motion.dd></div></dl></section>;\n}\n"
    }
  ]
}
