{ "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 \n);\n\nfunction TabCardContent({\n title,\n description,\n fillClass,\n}: {\n title: string;\n description: string;\n fillClass: string;\n}) {\n return (\n
\n
\n \n \n \n \n \n \n \n \n
\n
\n
\n

\n {title}\n

\n

\n {description}\n

\n
\n
\n
\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(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>(new Map());\n const containerRef = React.useRef(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,\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
\n {/* Card Content Area */}\n
\n
\n
\n \n \n {selectedItem?.cardContent ??\n (selectedItem && (\n \n ))}\n \n \n
\n
\n
\n\n {/* Bottom Toolbar */}\n \n {/* Sliding Background */}\n \n\n
\n {items.map((item) => {\n const isSelected = selected === item.id;\n return (\n 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 {item.title}\n \n );\n })}\n
\n
\n \n );\n}\n", "path": "/components/kokonutui/smooth-tab.tsx", "target": "components/kokonutui/smooth-tab.tsx" } ] }