{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "magic-genie-tabs",
  "type": "registry:component",
  "title": "Magic Genie Tabs",
  "author": "darshitdev <darshit@darshitdev.in>",
  "description": "macOS-style genie animation with a sleek glass pill nav bar",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "src/registry/magic-genie-tabs/magic-genie-tabs.tsx",
      "content": "\"use client\";\n\nimport React, { useRef, useState } from \"react\";\nimport { motion, AnimatePresence } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\ntype Tab = {\n  title: string;\n  value: string;\n  content?: React.ReactNode;\n  gradient?: string; // optional CSS gradient string for panel background\n};\n\nexport const Tabs = ({\n  tabs: propTabs,\n  containerClassName,\n  activeTabClassName,\n  tabClassName,\n  contentClassName,\n}: {\n  tabs: Tab[];\n  containerClassName?: string;\n  activeTabClassName?: string;\n  tabClassName?: string;\n  contentClassName?: string;\n}) => {\n  const [activeIndex, setActiveIndex] = useState(0);\n  const active = propTabs[activeIndex];\n\n  const [stripHoverX, setStripHoverX] = useState<number | null>(null);\n\n  const stripRef = useRef<HTMLDivElement | null>(null);\n  const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);\n\n  const [genieOrigin, setGenieOrigin] = useState<{ x: number; y: number } | null>(\n    null\n  );\n  const panelContainerRef = useRef<HTMLDivElement | null>(null);\n\n  const [direction, setDirection] = useState<1 | -1>(1);\n\n  const handleTabClick = (idx: number, e?: React.MouseEvent<HTMLButtonElement>) => {\n    setDirection(idx > activeIndex ? 1 : -1);\n\n    if (e && panelContainerRef.current) {\n      const tabRect = e.currentTarget.getBoundingClientRect();\n      const panelRect = panelContainerRef.current.getBoundingClientRect();\n      const x = (tabRect.left + tabRect.right) / 2 - panelRect.left;\n      const y = tabRect.bottom - panelRect.top;\n      setGenieOrigin({ x, y });\n    } else {\n      setGenieOrigin(null);\n    }\n\n    setActiveIndex(idx);\n  };\n\n  const handleStripMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {\n    if (!stripRef.current) return;\n    const rect = stripRef.current.getBoundingClientRect();\n    setStripHoverX(e.clientX - rect.left);\n  };\n\n  const handleStripMouseLeave = () => {\n    setStripHoverX(null);\n  };\n\n  return (\n    <div className={cn(\"w-full flex flex-col gap-4\", containerClassName)}>\n      {/* Top tab strip – sleek & minimal */}\n      <div\n        ref={stripRef}\n        onMouseMove={handleStripMouseMove}\n        onMouseLeave={handleStripMouseLeave}\n        className={cn(\n          \"flex flex-row items-center justify-start gap-1\",\n          \"max-w-full w-full overflow-x-auto no-visible-scrollbar\",\n          \"px-2 py-1.5 rounded-2xl\",\n          \"backdrop-blur-xl\"\n        )}\n      >\n        {propTabs.map((tab, idx) => {\n          const isActive = idx === activeIndex;\n\n          // very subtle dock-like magnification\n          let tabScale = 1;\n          let tabOffsetY = 0;\n\n          if (stripHoverX != null && stripRef.current && buttonRefs.current[idx]) {\n            const btn = buttonRefs.current[idx]!;\n            const centerX = btn.offsetLeft + btn.offsetWidth / 2;\n            const distance = Math.abs(stripHoverX - centerX);\n            const maxInfluence = 130;\n\n            const t = Math.max(0, 1 - distance / maxInfluence);\n            const maxScale = 1.15;\n            const minScale = 0.98;\n            tabScale = minScale + t * (maxScale - minScale);\n            tabOffsetY = -4 * t;\n          } else if (isActive) {\n            tabScale = 1.05;\n            tabOffsetY = -2;\n          }\n\n          return (\n            <motion.button\n              key={tab.value}\n              ref={(el) => {\n                buttonRefs.current[idx] = el;\n              }}\n              onClick={(e) => handleTabClick(idx, e)}\n              type=\"button\"\n              className={cn(\n                \"relative px-4 py-1.5 rounded-full text-xs sm:text-sm font-medium whitespace-nowrap\",\n                \"transition-colors\",\n                tabClassName\n              )}\n              style={{ transformOrigin: \"50% 50%\" }}\n              animate={{\n                scale: tabScale,\n                y: tabOffsetY,\n              }}\n              transition={{\n                type: \"spring\",\n                stiffness: 260,\n                damping: 24,\n                mass: 0.4,\n              }}\n            >\n              {/* Active pill – only active tab has background */}\n              {isActive && (\n                <motion.div\n                  layoutId=\"tabs-active-pill\"\n                  className={cn(\n                    \"absolute inset-0 rounded-full\",\n                    \"bg-muted dark:bg-muted\",\n                    \"border border-border/50 dark:border-border/50\",\n                    activeTabClassName\n                  )}\n                  transition={{\n                    type: \"spring\",\n                    bounce: 0.2,\n                    duration: 0.4,\n                  }}\n                />\n              )}\n\n              <span\n                className={cn(\n                  \"relative z-10 font-semibold\",\n                  isActive\n                    ? \"text-foreground dark:text-foreground\"\n                    : \"text-foreground/70 dark:text-foreground/70\"\n                )}\n              >\n                {tab.title}\n              </span>\n            </motion.button>\n          );\n        })}\n      </div>\n\n      {/* Panel with genie effect */}\n      <div\n        ref={panelContainerRef}\n        className={cn(\"relative w-full\", contentClassName)}\n      >\n        <AnimatePresence mode=\"wait\">\n          {propTabs.map((tab, idx) => {\n            const isActive = idx === activeIndex;\n            if (!isActive) return null;\n\n            const fallbackGradient =\n              \"radial-gradient(ellipse at top, rgba(139, 92, 246, 0.25) 0%, rgba(168, 85, 247, 0.15) 30%, rgba(192, 132, 252, 0.1) 60%, transparent 100%)\";\n\n            return (\n              <div\n                key={tab.value}\n                className=\"relative w-full rounded-2xl overflow-hidden shadow-lg shadow-black/5 dark:shadow-black/20\"\n              >\n                <motion.div\n                  initial={\n                    genieOrigin\n                      ? {\n                          opacity: 0,\n                          scaleY: 0.02,\n                          scaleX: 0.9,\n                          y: 120,\n                          clipPath: \"inset(98% 25% 0% 25% round 20px)\",\n                        }\n                      : {\n                          opacity: 0,\n                          scaleY: 0.98,\n                          scaleX: 0.99,\n                          y: 12,\n                          clipPath: \"inset(12% 4% 0% 4% round 20px)\",\n                        }\n                  }\n                  animate={{\n                    opacity: 1,\n                    scaleY: 1,\n                    scaleX: 1,\n                    y: 0,\n                    clipPath: \"inset(0% 0% 0% 0% round 20px)\",\n                  }}\n                  exit={{\n                    opacity: 0,\n                    scaleY: 0.02,\n                    scaleX: 0.9,\n                    y: 120,\n                    clipPath: \"inset(98% 25% 0% 25% round 20px)\",\n                  }}\n                  transition={{\n                    duration: 0.7,\n                    ease: [0.23, 1, 0.32, 1],\n                  }}\n                  style={{\n                    transformOrigin: genieOrigin\n                      ? `${genieOrigin.x}px ${genieOrigin.y}px`\n                      : \"50% 0%\",\n                    willChange: \"transform, clip-path\",\n                    backfaceVisibility: \"hidden\",\n                    WebkitBackfaceVisibility: \"hidden\",\n                  }}\n                  className={cn(\n                    \"w-full h-full rounded-2xl overflow-hidden\",\n                    \"bg-card dark:bg-card\",\n                    \"relative\",\n                    \"transform-gpu\"\n                  )}\n                >\n\n                {/* Gradient/Background layer */}\n                {(() => {\n                  const isCssGradient = tab.gradient && (\n                    tab.gradient.includes(\"gradient\") ||\n                    tab.gradient.includes(\"rgb\") ||\n                    tab.gradient.includes(\"rgba\") ||\n                    tab.gradient.startsWith(\"#\") ||\n                    tab.gradient.startsWith(\"hsl\") ||\n                    tab.gradient.startsWith(\"hsla\")\n                  );\n\n                  return (\n                    <motion.div\n                      className={cn(\n                        \"absolute inset-0 pointer-events-none\",\n                        // If gradient is a Tailwind class, apply it here\n                        !isCssGradient && tab.gradient ? tab.gradient : \"\"\n                      )}\n                      style={{\n                        // If gradient is a CSS gradient/color string, use it as backgroundImage\n                        backgroundImage: isCssGradient\n                          ? tab.gradient\n                          : !tab.gradient\n                          ? fallbackGradient\n                          : undefined,\n                      }}\n                      animate={{\n                        opacity: [0.5, 0.7, 0.5],\n                        backgroundPosition: [\"0% 0%\", \"100% 100%\", \"0% 0%\"],\n                      }}\n                      transition={{\n                        duration: 12,\n                        repeat: Infinity,\n                        ease: \"easeInOut\",\n                      }}\n                    />\n                  );\n                })()}\n\n                {/* Content */}\n                <motion.div\n                  className=\"relative z-10 w-full h-full max-h-[26rem] sm:max-h-[30rem] overflow-y-auto overflow-x-hidden\n                             [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-transparent\n                             [&::-webkit-scrollbar-thumb]:bg-muted-foreground/30 [&::-webkit-scrollbar-thumb]:rounded\n                             [&::-webkit-scrollbar-thumb:hover]:bg-muted-foreground/50\n                             scrollbar-thin\"\n                  initial={{ opacity: 0, y: 8, filter: \"blur(4px)\" }}\n                  animate={{ opacity: 1, y: 0, filter: \"blur(0px)\" }}\n                  exit={{ opacity: 0, y: 6, filter: \"blur(4px)\" }}\n                  transition={{\n                    duration: 0.35,\n                    ease: [0.25, 0.8, 0.4, 1],\n                  }}\n                >\n                  <div className=\"p-4 sm:p-5 md:p-6 text-sm sm:text-base text-foreground dark:text-foreground leading-relaxed\">\n                    {tab.content}\n                  </div>\n                </motion.div>\n                </motion.div>\n              </div>\n            );\n          })}\n        </AnimatePresence>\n      </div>\n    </div>\n  );\n};\n",
      "type": "registry:component"
    }
  ],
  "docs": "https://darshitdev.in/arts/components/magic-genie-tabs"
}