import React, { useEffect, useRef, useState, useLayoutEffect } from "react"; interface Tab { id: string; label: string; } interface SlidingTabsProps { tabs: Tab[]; activeTab: string; onChange: (id: string) => void; className?: string; } export const SlidingTabs: React.FC = ({ tabs, activeTab, onChange, className = "", }) => { const [sliderStyle, setSliderStyle] = useState({ width: 0, transform: "translateX(0)", opacity: 0, }); const containerRef = useRef(null); const tabsRef = useRef<{ [key: string]: HTMLButtonElement | null }>({}); const updateSlider = () => { const activeElement = tabsRef.current[activeTab]; const container = containerRef.current; // Validate that we have valid elements and that activeTab exists in our tabs if ( !activeElement || !container || !tabs.some((tab) => tab.id === activeTab) ) { return; } try { const activeRect = activeElement.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); // Additional validation - ensure we have valid dimensions if (activeRect.width === 0 || containerRect.width === 0) { return; } const relativeLeft = activeRect.left - containerRect.left; setSliderStyle({ width: activeRect.width, transform: `translateX(${relativeLeft}px)`, // We set opacity to 1 once we have positions to avoid FOUC opacity: 1, }); } catch (error) { // Handle potential DOM measurement errors during zoom/resize console.warn("SlidingTabs: Error updating slider position:", error); } }; // Update when active tab changes useLayoutEffect(() => { // Initial calculation needs to happen after paint often to get correct widths // requestAnimationFrame ensures we are measuring at a good time requestAnimationFrame(updateSlider); }, [activeTab]); // Handle window resize useEffect(() => { let resizeTimeout: number; const handleResize = () => { // Debounce resize events to avoid excessive calculations during zoom clearTimeout(resizeTimeout); resizeTimeout = window.setTimeout(() => { // Use requestAnimationFrame to ensure DOM has finished updating requestAnimationFrame(updateSlider); }, 50); }; window.addEventListener("resize", handleResize, { passive: true }); // Also a failsafe timeout for font loading shifts etc const timeout = setTimeout(updateSlider, 150); return () => { window.removeEventListener("resize", handleResize); clearTimeout(timeout); clearTimeout(resizeTimeout); }; }, [activeTab]); // Add activeTab as dependency so resize handler is recreated when tab changes return (
{/* Animated Slider */} ); };