{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dialog-stack",
  "title": "Dialog stack",
  "description": "A modal with steps that stack behind each other, built on the native dialog element.",
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/manniche/dialog-stack/dialog-stack.tsx",
      "content": "// Based on Watermelon UI's “Dialog stack” (MIT, © 2026 Watermelon Platform Contributors,\n// github.com/WatermelonCorp/watermelon-platform). Rewritten on the native <dialog> with any content per step.\nimport { ArrowLeft, X } from 'lucide-react'\nimport { AnimatePresence, MotionConfig, motion } from 'motion/react'\nimport { useEffect, useRef, useState, type ReactNode } from 'react'\nimport { cn } from '@/lib/utils'\n\nexport type DialogStackControls = { next: () => void; back: () => void; close: () => void }\n\nexport type DialogStackStep = {\n  id: string\n  title: string\n  content: (controls: DialogStackControls) => ReactNode\n}\n\nexport type DialogStackProps = {\n  open: boolean\n  onOpenChange: (open: boolean) => void\n  steps: DialogStackStep[]\n  closeLabel?: string\n  backLabel?: string\n  className?: string\n}\n\n/**\n * A dialog where each next step lands on top and the earlier ones peek out behind it.\n * Esc or the back arrow goes one step down; closing on the first step closes the dialog.\n */\nexport function DialogStack({ open, onOpenChange, steps, closeLabel = 'Close', backLabel = 'Back', className }: DialogStackProps) {\n  const dialog = useRef<HTMLDialogElement>(null)\n  const [index, setIndex] = useState(0)\n\n  useEffect(() => {\n    const d = dialog.current\n    if (!d) return\n    if (open && !d.open) {\n      setIndex(0)\n      d.showModal()\n    } else if (!open && d.open) d.close()\n  }, [open])\n\n  const close = () => onOpenChange(false)\n  const controls: DialogStackControls = {\n    next: () => setIndex((i) => Math.min(i + 1, steps.length - 1)),\n    back: () => setIndex((i) => Math.max(i - 1, 0)),\n    close,\n  }\n\n  return (\n    <dialog\n      ref={dialog}\n      aria-label={steps[index]?.title}\n      onCancel={(e) => {\n        // Esc steps back first, like the arrow.\n        if (index > 0) {\n          e.preventDefault()\n          controls.back()\n        }\n      }}\n      onClose={close}\n      onClick={(e) => e.target === dialog.current && close()}\n      className={cn(\n        'm-auto w-[min(24rem,calc(100vw-2rem))] overflow-visible bg-transparent p-0 pt-10 text-card-foreground',\n        'backdrop:bg-black/30 backdrop:backdrop-blur-[2px]',\n        className,\n      )}\n    >\n      <MotionConfig reducedMotion=\"user\" transition={{ type: 'spring', stiffness: 300, damping: 28 }}>\n        <div className=\"relative grid\">\n          <AnimatePresence initial={false}>\n            {steps.slice(0, index + 1).map((step, i) => {\n              const depth = index - i\n              const top = depth === 0\n              return (\n                <motion.section\n                  key={step.id}\n                  inert={!top}\n                  aria-hidden={!top}\n                  initial={{ y: 48, opacity: 0, scale: 0.96 }}\n                  animate={{ y: depth * -18, scale: 1 - depth * 0.05, opacity: depth > 2 ? 0 : 1 - depth * 0.25 }}\n                  exit={{ y: 48, opacity: 0, scale: 0.96 }}\n                  style={{ zIndex: i, transformOrigin: 'top center' }}\n                  className=\"col-start-1 row-start-1 self-start overflow-hidden rounded-3xl border bg-card shadow-2xl\"\n                >\n                  <header className=\"flex items-center gap-1 border-b bg-muted/50 py-1.5 pr-1.5 pl-2\">\n                    {i > 0 && (\n                      <button\n                        type=\"button\"\n                        aria-label={backLabel}\n                        onClick={controls.back}\n                        className=\"grid size-9 place-items-center rounded-full text-muted-foreground hover:bg-muted hover:text-foreground\"\n                      >\n                        <ArrowLeft className=\"size-4.5\" aria-hidden />\n                      </button>\n                    )}\n                    <h2 className={cn('flex-1 text-sm font-medium text-muted-foreground', i === 0 && 'pl-3')}>{step.title}</h2>\n                    <button\n                      type=\"button\"\n                      aria-label={closeLabel}\n                      onClick={close}\n                      className=\"grid size-9 place-items-center rounded-full text-muted-foreground hover:bg-muted hover:text-foreground\"\n                    >\n                      <X className=\"size-4.5\" aria-hidden />\n                    </button>\n                  </header>\n                  <div className=\"p-5\">{step.content(controls)}</div>\n                </motion.section>\n              )\n            })}\n          </AnimatePresence>\n        </div>\n      </MotionConfig>\n    </dialog>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "meta": {
    "credit": "Adapted from Watermelon UI (MIT)."
  },
  "categories": [
    "overlays"
  ],
  "type": "registry:component"
}