{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "list-stack",
  "title": "List stack",
  "description": "Cards stacked in a pile that fan out into a list when opened.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/manniche/list-stack/list-stack.tsx",
      "content": "// Based on Watermelon UI's “List stack” (MIT, © 2026 Watermelon Platform Contributors,\n// github.com/WatermelonCorp/watermelon-platform). Rewritten so the list keeps its height, reads in order and opens with a real button.\nimport { AnimatePresence, MotionConfig, motion } from 'motion/react'\nimport { useId, useState, type ReactNode } from 'react'\nimport { cn } from '@/lib/utils'\n\nexport type ListStackItem = { id: string; title: string; detail?: string; meta?: string; icon?: ReactNode }\n\nexport type ListStackProps = {\n  items: ListStackItem[]\n  showLabel?: string\n  hideLabel?: string\n  className?: string\n}\n\nconst ROW = 64\nconst GAP = 8\n\n/** A short list folded into a stack of cards. Open it and the cards fan out into rows. */\nexport function ListStack({ items, showLabel = 'Show all', hideLabel = 'Hide', className }: ListStackProps) {\n  const [open, setOpen] = useState(false)\n  const id = useId()\n  const n = items.length\n  const height = open ? n * (ROW + GAP) : ROW + 2 * 8\n\n  return (\n    <MotionConfig reducedMotion=\"user\" transition={{ type: 'spring', stiffness: 200, damping: 24 }}>\n      <div className={cn('mx-auto flex w-full max-w-xs flex-col items-center gap-3', className)}>\n        <motion.ul id={id} initial={false} animate={{ height }} className=\"relative w-full [perspective:1000px]\">\n          {items.map((it, i) => {\n            // The first item sits on top of the stack; the rest tuck in behind it, smaller.\n            const depth = i\n            return (\n              <motion.li\n                key={it.id}\n                initial={false}\n                animate={\n                  open\n                    ? { y: i * (ROW + GAP), scale: 1, opacity: 1 }\n                    : { y: Math.min(depth, 2) * 8, scale: 1 - Math.min(depth, 3) * 0.05, opacity: depth > 2 ? 0 : 1 }\n                }\n                style={{ height: ROW, zIndex: n - i, transformOrigin: 'top center' }}\n                aria-hidden={!open && i > 0}\n                className=\"absolute inset-x-0 top-0 flex items-center gap-3 rounded-2xl border bg-card px-3 shadow-sm\"\n              >\n                {it.icon && (\n                  <span className=\"grid size-10 shrink-0 place-items-center rounded-xl bg-foreground text-background [&_svg]:size-5\">{it.icon}</span>\n                )}\n                <span className=\"min-w-0 flex-1\">\n                  <span className=\"block truncate text-sm font-medium\">{it.title}</span>\n                  {it.detail && <span className=\"block truncate text-xs text-muted-foreground\">{it.detail}</span>}\n                </span>\n                {it.meta && <span className=\"shrink-0 self-end pb-3 text-xs text-muted-foreground\">{it.meta}</span>}\n              </motion.li>\n            )\n          })}\n        </motion.ul>\n        <motion.button\n          layout\n          type=\"button\"\n          aria-expanded={open}\n          aria-controls={id}\n          onClick={() => setOpen((o) => !o)}\n          className=\"min-h-11 overflow-hidden rounded-full border bg-card px-5 text-sm font-medium shadow-sm\"\n        >\n          <AnimatePresence mode=\"popLayout\" initial={false}>\n            <motion.span key={String(open)} initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -8 }} className=\"block\">\n              {open ? hideLabel : `${showLabel} (${n})`}\n            </motion.span>\n          </AnimatePresence>\n        </motion.button>\n      </div>\n    </MotionConfig>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "meta": {
    "credit": "Adapted from Watermelon UI (MIT)."
  },
  "categories": [
    "widgets"
  ],
  "type": "registry:component"
}