Animated Tabs
navdeep-singh/animated-tabsFreeComponent
A tab component with a moving active pill and directional content transitions.
Install it
npx shadcn@latest add https://ui.navdeepsingh.dev/r/animated-tabs.jsonSource
1'use client'23import * as React from 'react'4import { motion, useReducedMotion } from 'motion/react'5import { useControllableState } from '@radix-ui/react-use-controllable-state'67import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'8import { cn } from '@/lib/utils'910type AnimatedTabsContextValue = {11 value: string12 previousValue: string | null13 direction: number14 activationMode: 'automatic' | 'manual'15 shouldReduceMotion: boolean16 setValue: (value: string) => void17 registerTrigger: (value: string) => () => void18}1920const AnimatedTabsContext = React.createContext<AnimatedTabsContextValue | null>(null)2122function useAnimatedTabs() {23 const context = React.useContext(AnimatedTabsContext)2425 if (!context) {26 throw new Error('AnimatedTabs components must be used within <AnimatedTabs>.')27 }2829 return context30}3132function AnimatedTabs({33 value: valueProp,34 defaultValue,35 onValueChange,36 activationMode = 'automatic',37 className,38 children,39 ...props40}: React.ComponentProps<typeof Tabs>) {41 const triggerValues = React.useRef<string[]>([])42 const shouldReduceMotion = useReducedMotion()43 const lastValue = React.useRef(valueProp ?? defaultValue ?? '')44 const [previousValue, setPreviousValue] = React.useState<string | null>(null)45 const [direction, setDirection] = React.useState(0)46 const [value = '', setValue] = useControllableState({47 prop: valueProp,48 defaultProp: defaultValue ?? '',49 onChange: onValueChange,50 })5152 const registerTrigger = React.useCallback((triggerValue: string) => {53 if (!triggerValues.current.includes(triggerValue)) {54 triggerValues.current.push(triggerValue)55 }5657 return () => {58 triggerValues.current = triggerValues.current.filter((value) => value !== triggerValue)59 }60 }, [])6162 React.useLayoutEffect(() => {63 const previous = lastValue.current6465 if (!value || previous === value) return6667 const previousIndex = triggerValues.current.indexOf(previous)68 const nextIndex = triggerValues.current.indexOf(value)6970 setPreviousValue(previous || null)71 setDirection(previousIndex === -1 || nextIndex === -1 ? 0 : nextIndex > previousIndex ? 1 : -1)72 lastValue.current = value73 }, [value])7475 React.useEffect(() => {76 if (!previousValue) return7778 const timeout = window.setTimeout(() => setPreviousValue(null), 240)7980 return () => window.clearTimeout(timeout)81 }, [previousValue])8283 return (84 <AnimatedTabsContext.Provider85 value={{86// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from navdeep-singh
All 64 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Animated Groupanimated-group | navdeep-singh | Components | Free | 1 dep | |
| Animated Numbersanimated-numbers | navdeep-singh | Components | Free | 1 dep | |
| Approval Panel Illustrationapproval-panel | navdeep-singh | Components | Free | 2 deps · 2 files | |
| Brand Logobrand-logo | navdeep-singh | Components | Free | 1 dep | |
| Contribution Graphcontribution-graph | navdeep-singh | Components | Free | no deps | |
| Designed for Focus Illustrationdesigned-for-focus-illustration | navdeep-singh | Components | Free | 1 dep | |
| Expandable Cardexpandable-card | navdeep-singh | Components | Free | 4 deps | |
| Fast by Default Illustrationfast-by-default-illustration | navdeep-singh | Components | Free | 1 dep |
