{
  "$schema": "https://www.cooki-ui.design/schema/registry-item.json",
  "name": "multi-select-combobox",
  "type": "registry:ui",
  "title": "Multi-Select Combobox",
  "description": "Searchable multi-select dropdown with animated tag removals, keyboard navigation, and custom counter.",
  "category": "forms",
  "dependencies": [
    "framer-motion",
    "@phosphor-icons/react"
  ],
  "files": [
    {
      "path": "registry/forms/multi-select-combobox/multi-select-combobox.tsx",
      "type": "registry:ui",
      "target": "components/ui/multi-select-combobox.tsx",
      "content": "import React, { useState, useRef, useEffect } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { Check, CaretDown, X, MagnifyingGlass } from '@phosphor-icons/react';\n\nconst OPTIONS = [\n  'React 19',\n  'Next.js App Router',\n  'Tailwind CSS v4',\n  'Framer Motion',\n  'TypeScript 5.7',\n  'shadcn/ui',\n  'WebAssembly',\n  'Vite 6',\n];\n\nexport const MultiSelectCombobox: React.FC = () => {\n  const [selected, setSelected] = useState<string[]>(['React 19', 'Tailwind CSS v4']);\n  const [isOpen, setIsOpen] = useState(false);\n  const [search, setSearch] = useState('');\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const handleOutside = (e: MouseEvent) => {\n      if (!containerRef.current?.contains(e.target as Node)) setIsOpen(false);\n    };\n    window.addEventListener('mousedown', handleOutside);\n    return () => window.removeEventListener('mousedown', handleOutside);\n  }, []);\n\n  const toggleOption = (opt: string) => {\n    setSelected((prev) =>\n      prev.includes(opt) ? prev.filter((o) => o !== opt) : [...prev, opt]\n    );\n  };\n\n  const filtered = OPTIONS.filter((o) => o.toLowerCase().includes(search.toLowerCase()));\n\n  return (\n    <div ref={containerRef} className=\"relative w-full max-w-sm mx-auto\">\n      <div\n        onClick={() => setIsOpen(!isOpen)}\n        className=\"flex min-h-[46px] items-center justify-between rounded-2xl border border-zinc-200 dark:border-white/10 bg-white dark:bg-zinc-900/80 p-2 cursor-pointer shadow-xs transition-colors\"\n      >\n        <div className=\"flex flex-wrap gap-1.5 flex-1 items-center\">\n          {selected.length === 0 && (\n            <span className=\"text-xs font-mono text-zinc-500 pl-2\">Select framework tokens...</span>\n          )}\n          {selected.map((item) => (\n            <motion.span\n              key={item}\n              layout\n              className=\"inline-flex items-center gap-1 rounded-lg bg-indigo-500/10 text-indigo-600 dark:text-indigo-300 border border-indigo-500/20 px-2 py-0.5 text-xs font-mono\"\n            >\n              <span>{item}</span>\n              <button\n                type=\"button\"\n                onClick={(e) => {\n                  e.stopPropagation();\n                  toggleOption(item);\n                }}\n                className=\"hover:text-rose-500 cursor-pointer\"\n              >\n                <X size={10} weight=\"bold\" />\n              </button>\n            </motion.span>\n          ))}\n        </div>\n        <CaretDown size={14} className={`text-zinc-400 transition-transform ${isOpen ? 'rotate-180' : ''}`} />\n      </div>\n\n      <AnimatePresence>\n        {isOpen && (\n          <motion.div\n            initial={{ opacity: 0, y: 8, scale: 0.98 }}\n            animate={{ opacity: 1, y: 0, scale: 1 }}\n            exit={{ opacity: 0, y: 8, scale: 0.98 }}\n            transition={{ duration: 0.15 }}\n            className=\"absolute left-0 right-0 top-full mt-2 z-50 rounded-2xl border border-zinc-200 dark:border-white/15 bg-white dark:bg-zinc-950 p-2 shadow-2xl overflow-hidden\"\n          >\n            <div className=\"flex items-center gap-2 rounded-xl border border-zinc-200 dark:border-white/10 bg-zinc-100 dark:bg-zinc-900 px-3 py-1.5 mb-2\">\n              <MagnifyingGlass size={13} className=\"text-zinc-400\" />\n              <input\n                type=\"text\"\n                value={search}\n                onChange={(e) => setSearch(e.target.value)}\n                placeholder=\"Filter tokens...\"\n                className=\"w-full bg-transparent text-xs font-mono text-zinc-900 dark:text-white outline-none\"\n              />\n            </div>\n            <div className=\"max-h-48 overflow-y-auto space-y-1\">\n              {filtered.map((opt) => {\n                const isChecked = selected.includes(opt);\n                return (\n                  <button\n                    key={opt}\n                    type=\"button\"\n                    onClick={() => toggleOption(opt)}\n                    className={`flex w-full items-center justify-between rounded-xl px-3 py-2 text-xs font-mono transition-colors cursor-pointer ${\n                      isChecked\n                        ? 'bg-indigo-500/15 text-indigo-600 dark:text-indigo-300 font-semibold'\n                        : 'text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-white/5'\n                    }`}\n                  >\n                    <span>{opt}</span>\n                    {isChecked && <Check size={14} weight=\"bold\" className=\"text-indigo-500\" />}\n                  </button>\n                );\n              })}\n            </div>\n          </motion.div>\n        )}\n      </AnimatePresence>\n    </div>\n  );\n};\n"
    }
  ]
}