{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "waveform-bars",
  "title": "Waveform Bars",
  "author": "mrap10",
  "description": "Mic toggle with animated waveform bars and pulse state.",
  "dependencies": ["motion"],
  "files": [
    {
      "path": "registry/default/button/waveform-bars.tsx",
      "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { motion } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\nconst EASE: [number, number, number, number] = [0.22, 1, 0.36, 1];\nconst BAR_HEIGHTS = [\"8px\", \"20px\", \"12px\", \"24px\", \"14px\", \"8px\"];\nconst BAR_WIDTH = 6; // px - matches w-1.5\nconst BAR_GAP = 6;\n\ninterface WaveformBarsProps {\n  color?: string;\n  iconSize?: string;\n  barCount?: number;\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n}\n\nfunction isTailwindClass(value: string) {\n  return /^bg-/.test(value);\n}\n\nexport default function WaveformBars({\n  color = \"#34d399\", // Tailwind's emerald-400\n  iconSize = \"size-5\",\n  barCount = 7,\n  defaultOpen = false,\n  open,\n  onOpenChange,\n}: WaveformBarsProps) {\n  const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n\n  const isControlled = open !== undefined;\n  const toggled = isControlled ? open : uncontrolledOpen;\n  const isTailwind = isTailwindClass(color);\n\n  const handleOpenChange = (newOpen: boolean) => {\n    if (!isControlled) {\n      setUncontrolledOpen(newOpen);\n    }\n    onOpenChange?.(newOpen);\n  };\n\n  return (\n    <div className=\"relative isolate flex items-center justify-center gap-3\">\n      <motion.div\n        className=\"relative\"\n        animate={{ x: toggled ? -20 : 0 }}\n        transition={{ duration: 0.42, ease: EASE }}\n      >\n        <motion.button\n          onClick={() => handleOpenChange(!toggled)}\n          whileHover={{ scale: toggled ? 1.02 : 1.05 }}\n          whileTap={{ scale: 0.95 }}\n          transition={{ duration: 0.2, ease: \"easeOut\" }}\n          className={cn(\n            \"relative z-10 rounded-full p-2 text-white transition-colors duration-300\",\n            !toggled && \"bg-neutral-800 dark:bg-neutral-100 dark:text-black\",\n            toggled && isTailwind && color\n          )}\n          style={\n            toggled && !isTailwind ? { backgroundColor: color } : undefined\n          }\n        >\n          <MicrophoneIcon className={cn(\"relative z-10\", iconSize)} />\n\n          {toggled && (\n            <motion.div\n              className={cn(\n                \"pointer-events-none absolute inset-0 rounded-full opacity-20\",\n                isTailwind && color\n              )}\n              style={!isTailwind ? { backgroundColor: color } : undefined}\n              initial={{ scale: 0.84 }}\n              animate={{ scale: [1, 1.2, 1] }}\n              transition={{ duration: 2, repeat: Infinity, ease: \"easeInOut\" }}\n            />\n          )}\n        </motion.button>\n      </motion.div>\n\n      <motion.div\n        className=\"overflow-hidden\"\n        initial={false}\n        animate={{\n          width: toggled ? barCount * BAR_WIDTH + (barCount - 1) * BAR_GAP : 0,\n          opacity: toggled ? 1 : 0,\n        }}\n        transition={{ duration: 0.35, ease: EASE }}\n      >\n        <Bars toggled={toggled} color={color} barCount={barCount} />\n      </motion.div>\n    </div>\n  );\n}\n\nfunction Bars({\n  toggled,\n  color,\n  barCount,\n}: {\n  toggled: boolean;\n  color: string;\n  barCount: number;\n}) {\n  const isTailwind = isTailwindClass(color);\n  return (\n    <motion.div\n      className=\"flex items-center gap-1.5\"\n      initial={false}\n      animate={{\n        opacity: toggled ? 1 : 0,\n        x: toggled ? 0 : 8,\n        scaleX: toggled ? 1 : 0.88,\n      }}\n      transition={{ duration: 0.32, ease: EASE }}\n      style={{ transformOrigin: \"left center\" }}\n    >\n      {Array.from({ length: barCount }).map((_, index) => (\n        <motion.div\n          key={index}\n          className={cn(\"w-1.5 shrink-0 rounded-full\", isTailwind && color)}\n          style={!isTailwind ? { backgroundColor: color } : undefined}\n          initial={{ height: \"8px\" }}\n          animate={{\n            height: toggled ? BAR_HEIGHTS : \"8px\",\n            opacity: toggled ? 1 : 0.45,\n          }}\n          transition={{\n            duration: 0.95,\n            repeat: toggled ? Infinity : 0,\n            ease: \"easeInOut\",\n            delay: index * 0.08,\n          }}\n        />\n      ))}\n    </motion.div>\n  );\n}\n\nfunction MicrophoneIcon({ className }: { className?: string }) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" className={className}>\n      <path d=\"M19 9a1 1 0 0 1 1 1a8 8 0 0 1 -6.999 7.938l-.001 2.062h3a1 1 0 0 1 0 2h-8a1 1 0 0 1 0 -2h3v-2.062a8 8 0 0 1 -7 -7.938a1 1 0 1 1 2 0a6 6 0 0 0 12 0a1 1 0 0 1 1 -1m-7 -8a4 4 0 0 1 4 4v5a4 4 0 1 1 -8 0v-5a4 4 0 0 1 4 -4\" />\n    </svg>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": ["buttons"],
  "type": "registry:component"
}
