Tabs Subtle
fluid-functionalism/tabs-subtleFreeComponent
Horizontal tab navigation with proximity hover, animated pill selection indicator, and fluid font-weight transitions. Radix flavor.
Install it
npx shadcn@latest add https://fluidfunctionalism.com/r/tabs-subtle.jsonSource
1"use client";23import {4 useRef,5 useState,6 useCallback,7 useEffect,8 createContext,9 useContext,10 forwardRef,11 type ReactNode,12 type HTMLAttributes,13} from "react";14import * as TabsPrimitive from "@radix-ui/react-tabs";15import { motion, AnimatePresence } from "framer-motion";16import type { IconComponent } from "@/lib/icon-context";17import { cn } from "@/lib/utils";18import { spring } from "@/lib/springs";19import { fontWeights } from "@/lib/font-weight";20import { useShape } from "@/lib/shape-context";21import { useProximityHover } from "@/hooks/use-proximity-hover";2223interface TabsSubtleContextValue {24 registerTab: (index: number, element: HTMLElement | null) => void;25 hoveredIndex: number | null;26 selectedIndex: number;27 idPrefix: string | undefined;28 activeLabel: boolean;29}3031const TabsSubtleContext = createContext<TabsSubtleContextValue | null>(null);3233function useTabsSubtle() {34 const ctx = useContext(TabsSubtleContext);35 if (!ctx) throw new Error("useTabsSubtle must be used within a TabsSubtle");36 return ctx;37}3839interface TabsSubtleProps extends Omit<HTMLAttributes<HTMLDivElement>, "onSelect"> {40 children: ReactNode;41 selectedIndex: number;42 onSelect: (index: number) => void;43 idPrefix?: string;44 /** When true, only the selected tab shows its text label. Requires icons on tabs. */45 activeLabel?: boolean;46}4748const TabsSubtle = forwardRef<HTMLDivElement, TabsSubtleProps>(49 ({ children, selectedIndex, onSelect, idPrefix, activeLabel = false, className, ...props }, ref) => {50 const containerRef = useRef<HTMLDivElement>(null);51 const isMouseInside = useRef(false);52 const shape = useShape();5354 const {55 activeIndex: hoveredIndex,56 setActiveIndex: setHoveredIndex,57 itemRects: tabRects,58 handlers,59 registerItem,60 measureItems: measureTabs,61 } = useProximityHover(containerRef, { axis: "x" });6263 // Track tab elements locally so we can observe their individual resizes64 const tabElementsRef = useRef(new Map<number, HTMLElement>());65 const registerTab = useCallback(66 (index: number, element: HTMLElement | null) => {67 registerItem(index, element);68 if (element) {69 tabElementsRef.current.set(index, element);70 } else {71 tabElementsRef.current.delete(index);72 }73 },74 [registerItem]75 );7677 useEffect(() => {78 measureTabs();79 }, [measureTabs, children]);8081// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from fluid-functionalism
All 53 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | fluid-functionalism | Components | Free | 3 deps | |
| Accordion (Base UI)accordion-base | fluid-functionalism | Components | Free | 3 deps | |
| Ask User Questionsask-user-questions | fluid-functionalism | Components | Free | 2 deps | |
| Badgebadge | fluid-functionalism | Components | Free | 1 dep | |
| Buttonbutton | fluid-functionalism | Components | Free | 4 deps | |
| Button (Base UI)button-base | fluid-functionalism | Components | Free | 4 deps | |
| Cardcard | fluid-functionalism | Components | Free | 1 dep | |
| Chat Messagechat-message | fluid-functionalism | Components | Free | 1 dep |
