{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "vertical-tabs", "title": "Vertical Tabs", "description": "A vertical tab interaction with smooth content switching.", "dependencies": [ "motion", "@hugeicons/core-free-icons", "@hugeicons/react", "clsx", "tailwind-merge" ], "files": [ { "path": "registry/default/example/vertical-tabs.tsx", "content": "\"use client\";\r\n\r\nimport React, { useState, useEffect, useCallback } from \"react\";\r\nimport { motion, AnimatePresence } from \"motion/react\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { ArrowLeft01Icon, ArrowRight01Icon } from \"@hugeicons/core-free-icons\";\r\nimport { HugeiconsIcon } from \"@hugeicons/react\";\r\n\r\n// Change Here\r\nconst SERVICES = [\r\n {\r\n id: \"01\",\r\n title: \"Web Design\",\r\n description:\r\n \"Creating beautiful, functional, and user-centric digital experiences.\",\r\n image:\r\n \"https://images.unsplash.com/photo-1550745165-9bc0b252726f?q=80&w=1200\",\r\n },\r\n {\r\n id: \"02\",\r\n title: \"Framer Development\",\r\n description: \"Building high-performance, animated websites with Framer.\",\r\n image:\r\n \"https://images.unsplash.com/photo-1581291518633-83b4ebd1d83e?q=80&w=1200\",\r\n },\r\n {\r\n id: \"03\",\r\n title: \"Branding\",\r\n description:\r\n \"Defining your brand's visual identity and voice for a lasting impression.\",\r\n image:\r\n \"https://images.unsplash.com/photo-1555066931-4365d14bab8c?q=80&w=1200\",\r\n },\r\n];\r\n\r\nconst AUTO_PLAY_DURATION = 5000;\r\n\r\nexport default function VerticalTabs() {\r\n const [activeIndex, setActiveIndex] = useState(0);\r\n const [direction, setDirection] = useState(0);\r\n const [isPaused, setIsPaused] = useState(false);\r\n\r\n const handleNext = useCallback(() => {\r\n setDirection(1);\r\n setActiveIndex((prev) => (prev + 1) % SERVICES.length);\r\n }, []);\r\n\r\n const handlePrev = useCallback(() => {\r\n setDirection(-1);\r\n setActiveIndex((prev) => (prev - 1 + SERVICES.length) % SERVICES.length);\r\n }, []);\r\n\r\n const handleTabClick = (index: number) => {\r\n if (index === activeIndex) return;\r\n setDirection(index > activeIndex ? 1 : -1);\r\n setActiveIndex(index);\r\n setIsPaused(false);\r\n };\r\n\r\n useEffect(() => {\r\n if (isPaused) return;\r\n\r\n const interval = setInterval(() => {\r\n handleNext();\r\n }, AUTO_PLAY_DURATION);\r\n\r\n return () => clearInterval(interval);\r\n }, [activeIndex, isPaused, handleNext]);\r\n\r\n const variants = {\r\n enter: (direction: number) => ({\r\n y: direction > 0 ? \"-100%\" : \"100%\",\r\n opacity: 0,\r\n }),\r\n center: {\r\n zIndex: 1,\r\n y: 0,\r\n opacity: 1,\r\n },\r\n exit: (direction: number) => ({\r\n zIndex: 0,\r\n y: direction > 0 ? \"100%\" : \"-100%\",\r\n opacity: 0,\r\n }),\r\n };\r\n\r\n return (\r\n
\r\n
\r\n
\r\n {/* Left Column: Content */}\r\n
\r\n
\r\n

\r\n How I can help you\r\n

\r\n \r\n (SERVICES)\r\n \r\n
\r\n\r\n
\r\n {SERVICES.map((service, index) => {\r\n const isActive = activeIndex === index;\r\n return (\r\n handleTabClick(index)}\r\n className={cn(\r\n \"group relative flex items-start gap-4 py-6 md:py-8 text-left transition-all duration-500 border-t border-border/50 first:border-0\",\r\n isActive\r\n ? \"text-foreground\"\r\n : \"text-muted-foreground/60 hover:text-foreground\"\r\n )}\r\n >\r\n
\r\n {isActive && (\r\n \r\n )}\r\n
\r\n\r\n \r\n /{service.id}\r\n \r\n\r\n
\r\n \r\n {service.title}\r\n \r\n\r\n \r\n {isActive && (\r\n \r\n

\r\n {service.description}\r\n

\r\n \r\n )}\r\n
\r\n
\r\n \r\n );\r\n })}\r\n
\r\n
\r\n\r\n
\r\n setIsPaused(true)}\r\n onMouseLeave={() => setIsPaused(false)}\r\n >\r\n
\r\n \r\n \r\n \r\n\r\n
\r\n \r\n \r\n\r\n
\r\n {\r\n e.stopPropagation();\r\n handlePrev();\r\n }}\r\n className=\"w-10 h-10 md:w-12 md:h-12 rounded-full bg-background/80 backdrop-blur-md border border-border/50 flex items-center justify-center text-foreground hover:bg-background transition-all active:scale-90\"\r\n aria-label=\"Previous\"\r\n >\r\n \r\n \r\n {\r\n e.stopPropagation();\r\n handleNext();\r\n }}\r\n className=\"w-10 h-10 md:w-12 md:h-12 rounded-full bg-background/80 backdrop-blur-md border border-border/50 flex items-center justify-center text-foreground hover:bg-background transition-all active:scale-90\"\r\n aria-label=\"Next\"\r\n >\r\n \r\n \r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n );\r\n}\r\n", "type": "registry:component" } ], "type": "registry:component" }