Tab component with animated highlight on hover.
Last Updated:JSXimport { useState, useRef } from 'react'; const tabData = [ { label: 'About', pathname: '/about' }, { label: 'Bookmarks', pathname: '/bookmarks' }, { label: 'Snippets', pathname: '/snippets' }, { label: 'Projects', pathname: '/projects' }, ]; const Tabs = () => { const [tabBoundingBox, setTabBoundingBox] = useState(null); const [wrapperBoundingBox, setWrapperBoundingBox] = useState(null); const [highlightedTab, setHighlightedTab] = useState(null); const [isHoveredFromNull, setIsHoveredFromNull] = useState(true); const highlightRef = useRef(null); const wrapperRef = useRef(null); const repositionHighlight = (e, tab) => { setTabBoundingBox(e.target.getBoundingClientRect()); setWrapperBoundingBox(wrapperRef.current.getBoundingClientRect()); setIsHoveredFromNull(!highlightedTab); setHighlightedTab(tab); }; const resetHighlight = () => setHighlightedTab(null); const highlightStyles = {}; if (tabBoundingBox && wrapperBoundingBox) { highlightStyles.transitionDuration = isHoveredFromNull ? '0ms' : '150ms'; highlightStyles.opacity = highlightedTab ? 1 : 0; highlightStyles.width = `${tabBoundingBox.width}px`; highlightStyles.transform = `translateX(${tabBoundingBox?.left - wrapperBoundingBox?.left}px)`; } return ( <div className="relative" ref={wrapperRef} onMouseLeave={resetHighlight}> <div className="absolute left-0 top-1 rounded-md bg-gray-300 py-4 duration-150 dark:bg-gray-600" ref={highlightRef} style={{ ...highlightStyles, transitionProperty: 'width, transform, opacity', }} /> {tabData.map((tab) => ( <a className="relative inline-block cursor-pointer p-2 text-gray-700 transition duration-100 hover:text-gray-700 dark:text-gray-300 dark:hover:text-gray-300" key={tab.label} href={tab.pathname} onMouseOver={(ev) => repositionHighlight(ev, tab)} > {tab.label} </a> ))} </div> ); }; export default Tabs;