Files
wehub-resource-sync e30e75b5d4
Code Quality / Oxlint + Oxfmt (push) Waiting to run
Code Quality / Template Sync (push) Waiting to run
Code Quality / Build Changed Packages (push) Waiting to run
Code Quality / Test Changed Packages (push) Waiting to run
Deploy Expo Example / Deploy Production (push) Waiting to run
Deploy Ink Example / Deploy Production (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.12) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Waiting to run
Deploy Shadcn Registry / Deploy Production (push) Waiting to run
Template Metrics / LOC + Bundle Size (push) Waiting to run
Changesets / Create Version PR (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

76 lines
2.1 KiB
TypeScript

"use client";
import { useEffect, useLayoutEffect, useRef, useState } from "react";
const useIsomorphicLayoutEffect =
typeof window !== "undefined" ? useLayoutEffect : useEffect;
type TabStyle = { left: number; width: number };
type UseAnimatedTabsOptions = {
activeIndex: number;
};
type UseAnimatedTabsResult = {
containerRef: React.RefObject<HTMLDivElement | null>;
tabRefs: React.MutableRefObject<(HTMLButtonElement | null)[]>;
hoveredIndex: number | null;
setHoveredIndex: (index: number | null) => void;
activeStyle: TabStyle;
hoverStyle: TabStyle;
};
export function useAnimatedTabs({
activeIndex,
}: UseAnimatedTabsOptions): UseAnimatedTabsResult {
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const [activeStyle, setActiveStyle] = useState<TabStyle>({
left: 0,
width: 0,
});
const [hoverStyle, setHoverStyle] = useState<TabStyle>({ left: 0, width: 0 });
const tabRefs = useRef<(HTMLButtonElement | null)[]>([]);
const containerRef = useRef<HTMLDivElement>(null);
// Update active indicator position
useIsomorphicLayoutEffect(() => {
const el = tabRefs.current[activeIndex];
if (el && el.offsetWidth > 0) {
setActiveStyle({ left: el.offsetLeft, width: el.offsetWidth });
}
}, [activeIndex]);
// Re-calculate when container resizes (e.g., becomes visible after tab switch)
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const observer = new ResizeObserver(() => {
const el = tabRefs.current[activeIndex];
if (el && el.offsetWidth > 0) {
setActiveStyle({ left: el.offsetLeft, width: el.offsetWidth });
}
});
observer.observe(container);
return () => observer.disconnect();
}, [activeIndex]);
// Update hover indicator position
useEffect(() => {
if (hoveredIndex === null) return;
const el = tabRefs.current[hoveredIndex];
if (el) {
setHoverStyle({ left: el.offsetLeft, width: el.offsetWidth });
}
}, [hoveredIndex]);
return {
containerRef,
tabRefs,
hoveredIndex,
setHoveredIndex,
activeStyle,
hoverStyle,
};
}