{
  "name": "smooth-tab",
  "type": "registry:component",
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "files": [
    {
      "type": "registry:component",
      "content": "\"use client\";\n\n/**\n * @author: @dorianbaffier\n * @description: Smooth Tab\n * @version: 1.0.0\n * @date: 2025-06-26\n * @license: MIT\n * @website: https://kokonutui.com\n * @github: https://github.com/kokonut-labs/kokonutui\n */\n\nimport type { LucideIcon } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface TabItem {\n  id: string;\n  title: string;\n  description?: string;\n  icon?: LucideIcon;\n  content?: React.ReactNode;\n  cardContent?: React.ReactNode;\n  color: string;\n}\n\nconst WaveformPath = () => (\n  <motion.path\n    animate={{\n      x: [0, 10, 0],\n      transition: {\n        duration: 5,\n        ease: \"linear\",\n        repeat: Number.POSITIVE_INFINITY,\n      },\n    }}\n    d=\"M0 50 \n           C 20 40, 40 30, 60 50\n           C 80 70, 100 60, 120 50\n           C 140 40, 160 30, 180 50\n           C 200 70, 220 60, 240 50\n           C 260 40, 280 30, 300 50\n           C 320 70, 340 60, 360 50\n           C 380 40, 400 30, 420 50\n           L 420 100 L 0 100 Z\"\n    initial={false}\n  />\n);\n\nfunction TabCardContent({\n  title,\n  description,\n  fillClass,\n}: {\n  title: string;\n  description: string;\n  fillClass: string;\n}) {\n  return (\n    <div className=\"relative h-full\">\n      <div className=\"absolute inset-0 overflow-hidden\">\n        <svg\n          aria-hidden=\"true\"\n          className=\"absolute bottom-0 h-32 w-full\"\n          preserveAspectRatio=\"none\"\n          role=\"presentation\"\n          viewBox=\"0 0 420 100\"\n        >\n          <motion.g\n            animate={{ opacity: 0.15 }}\n            className={`fill-${fillClass} stroke-${fillClass}`}\n            initial={{ opacity: 0 }}\n            style={{ strokeWidth: 1 }}\n            transition={{ duration: 0.5 }}\n          >\n            <WaveformPath />\n          </motion.g>\n          <motion.g\n            animate={{ opacity: 0.1 }}\n            className={`fill-${fillClass} stroke-${fillClass}`}\n            initial={{ opacity: 0 }}\n            style={{ strokeWidth: 1, transform: \"translateY(10px)\" }}\n            transition={{ duration: 0.5 }}\n          >\n            <WaveformPath />\n          </motion.g>\n        </svg>\n      </div>\n      <div className=\"relative flex h-full flex-col p-6\">\n        <div className=\"space-y-2\">\n          <h3 className=\"bg-gradient-to-r from-foreground via-foreground/90 to-foreground/70 font-semibold text-2xl tracking-tight [text-shadow:_0_1px_1px_rgb(0_0_0_/_10%)]\">\n            {title}\n          </h3>\n          <p className=\"max-w-[90%] text-black/50 text-sm leading-relaxed dark:text-white/50\">\n            {description}\n          </p>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nconst DEFAULT_TABS: TabItem[] = [\n  {\n    id: \"Models\",\n    title: \"Models\",\n    description: \"Choose the model you want to use\",\n    color: \"bg-blue-500 hover:bg-blue-600\",\n  },\n  {\n    id: \"MCPs\",\n    title: \"MCPs\",\n    description: \"Choose the MCP you want to use\",\n    color: \"bg-purple-500 hover:bg-purple-600\",\n  },\n  {\n    id: \"Agents\",\n    title: \"Agents\",\n    description: \"Choose the agent you want to use\",\n    color: \"bg-emerald-500 hover:bg-emerald-600\",\n  },\n  {\n    id: \"Users\",\n    title: \"Users\",\n    description: \"Choose the user you want to use\",\n    color: \"bg-amber-500 hover:bg-amber-600\",\n  },\n];\n\ninterface SmoothTabProps {\n  items?: TabItem[];\n  defaultTabId?: string;\n  className?: string;\n  activeColor?: string;\n  onChange?: (tabId: string) => void;\n}\n\nconst slideVariants = {\n  enter: (direction: number) => ({\n    x: direction > 0 ? \"100%\" : \"-100%\",\n    opacity: 0,\n    filter: \"blur(8px)\",\n    scale: 0.95,\n    position: \"absolute\" as const,\n  }),\n  center: {\n    x: 0,\n    opacity: 1,\n    filter: \"blur(0px)\",\n    scale: 1,\n    position: \"absolute\" as const,\n  },\n  exit: (direction: number) => ({\n    x: direction < 0 ? \"100%\" : \"-100%\",\n    opacity: 0,\n    filter: \"blur(8px)\",\n    scale: 0.95,\n    position: \"absolute\" as const,\n  }),\n};\n\nconst transition = {\n  duration: 0.4,\n  ease: [0.32, 0.72, 0, 1],\n};\n\nexport default function SmoothTab({\n  items = DEFAULT_TABS,\n  defaultTabId = DEFAULT_TABS[0].id,\n  className,\n  activeColor = \"bg-[#1F9CFE]\",\n  onChange,\n}: SmoothTabProps) {\n  const [selected, setSelected] = React.useState<string>(defaultTabId);\n  const [direction, setDirection] = React.useState(0);\n  const [dimensions, setDimensions] = React.useState({ width: 0, left: 0 });\n\n  // Reference for the selected button\n  const buttonRefs = React.useRef<Map<string, HTMLButtonElement>>(new Map());\n  const containerRef = React.useRef<HTMLDivElement>(null);\n\n  // Update dimensions whenever selected tab changes or on mount\n  React.useLayoutEffect(() => {\n    const updateDimensions = () => {\n      const selectedButton = buttonRefs.current.get(selected);\n      const container = containerRef.current;\n\n      if (selectedButton && container) {\n        const rect = selectedButton.getBoundingClientRect();\n        const containerRect = container.getBoundingClientRect();\n\n        setDimensions({\n          width: rect.width,\n          left: rect.left - containerRect.left,\n        });\n      }\n    };\n\n    // Initial update\n    requestAnimationFrame(() => {\n      updateDimensions();\n    });\n\n    // Update on resize\n    window.addEventListener(\"resize\", updateDimensions);\n    return () => window.removeEventListener(\"resize\", updateDimensions);\n  }, [selected]);\n\n  const handleTabClick = (tabId: string) => {\n    const currentIndex = items.findIndex((item) => item.id === selected);\n    const newIndex = items.findIndex((item) => item.id === tabId);\n    setDirection(newIndex > currentIndex ? 1 : -1);\n    setSelected(tabId);\n    onChange?.(tabId);\n  };\n\n  const handleKeyDown = (\n    e: React.KeyboardEvent<HTMLButtonElement>,\n    tabId: string\n  ) => {\n    if (e.key === \"Enter\" || e.key === \" \") {\n      e.preventDefault();\n      handleTabClick(tabId);\n    }\n  };\n\n  const selectedItem = items.find((item) => item.id === selected);\n\n  return (\n    <div className=\"flex h-full flex-col\">\n      {/* Card Content Area */}\n      <div className=\"relative mb-4 flex-1\">\n        <div className=\"relative h-[200px] w-full rounded-lg border bg-card\">\n          <div className=\"absolute inset-0 overflow-hidden rounded-lg\">\n            <AnimatePresence\n              custom={direction}\n              initial={false}\n              mode=\"popLayout\"\n            >\n              <motion.div\n                animate=\"center\"\n                className=\"absolute inset-0 h-full w-full bg-card will-change-transform\"\n                custom={direction}\n                exit=\"exit\"\n                initial=\"enter\"\n                key={`card-${selected}`}\n                style={{\n                  backfaceVisibility: \"hidden\",\n                  WebkitBackfaceVisibility: \"hidden\",\n                }}\n                transition={transition as any}\n                variants={slideVariants as any}\n              >\n                {selectedItem?.cardContent ??\n                  (selectedItem && (\n                    <TabCardContent\n                      description={selectedItem.description ?? \"\"}\n                      fillClass={\n                        selectedItem.color\n                          .split(\" \")\n                          .at(0)\n                          ?.replace(\"bg-\", \"\") ?? \"blue-500\"\n                      }\n                      title={selectedItem.title}\n                    />\n                  ))}\n              </motion.div>\n            </AnimatePresence>\n          </div>\n        </div>\n      </div>\n\n      {/* Bottom Toolbar */}\n      <div\n        aria-label=\"Smooth tabs\"\n        className={cn(\n          \"relative mt-auto flex items-center justify-between gap-1 py-1\",\n          \"mx-auto w-[400px] bg-background\",\n          \"rounded-xl border\",\n          \"transition-all duration-200\",\n          className\n        )}\n        ref={containerRef}\n        role=\"tablist\"\n      >\n        {/* Sliding Background */}\n        <motion.div\n          animate={{\n            width: dimensions.width - 8,\n            x: dimensions.left + 4,\n            opacity: 1,\n          }}\n          className={cn(\n            \"absolute z-[1] rounded-lg\",\n            selectedItem?.color || activeColor\n          )}\n          initial={false}\n          style={{ height: \"calc(100% - 8px)\", top: \"4px\" }}\n          transition={{\n            type: \"spring\",\n            stiffness: 400,\n            damping: 30,\n          }}\n        />\n\n        <div className=\"relative z-[2] grid w-full grid-cols-4 gap-1\">\n          {items.map((item) => {\n            const isSelected = selected === item.id;\n            return (\n              <motion.button\n                aria-controls={`panel-${item.id}`}\n                aria-selected={isSelected}\n                className={cn(\n                  \"relative flex items-center justify-center gap-0.5 rounded-lg px-2 py-1.5\",\n                  \"font-medium text-sm transition-all duration-300\",\n                  \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n                  \"truncate\",\n                  isSelected\n                    ? \"text-white\"\n                    : \"text-muted-foreground hover:bg-muted/50 hover:text-foreground\"\n                )}\n                id={`tab-${item.id}`}\n                key={item.id}\n                onClick={() => handleTabClick(item.id)}\n                onKeyDown={(e) => handleKeyDown(e, item.id)}\n                ref={(el) => {\n                  if (el) buttonRefs.current.set(item.id, el);\n                  else buttonRefs.current.delete(item.id);\n                }}\n                role=\"tab\"\n                tabIndex={isSelected ? 0 : -1}\n                type=\"button\"\n              >\n                <span className=\"truncate\">{item.title}</span>\n              </motion.button>\n            );\n          })}\n        </div>\n      </div>\n    </div>\n  );\n}\n",
      "path": "/components/kokonutui/smooth-tab.tsx",
      "target": "components/kokonutui/smooth-tab.tsx"
    }
  ]
}