sticky tab improvements

This commit is contained in:
yohlo
2025-08-24 23:52:32 -05:00
parent e8bb6a5a36
commit a413d4421b

View File

@@ -22,6 +22,8 @@ function SwipeableTabs({ tabs, defaultTab = 0, onTabChange, scrollPosition }: Sw
const [isSticky, setIsSticky] = useState(false); const [isSticky, setIsSticky] = useState(false);
const tabsRef = useRef<HTMLDivElement | null>(null); const tabsRef = useRef<HTMLDivElement | null>(null);
const originalPositionRef = useRef<number | null>(null); const originalPositionRef = useRef<number | null>(null);
const slideRefs = useRef<Record<number, HTMLDivElement | null>>({});
const [activeSlideHeight, setActiveSlideHeight] = useState<number | 'auto'>('auto');
const stickyThreshold = 0; const stickyThreshold = 0;
useEffect(() => { useEffect(() => {
@@ -30,6 +32,13 @@ function SwipeableTabs({ tabs, defaultTab = 0, onTabChange, scrollPosition }: Sw
const onSelect = () => { const onSelect = () => {
const newIndex = embla.selectedScrollSnap(); const newIndex = embla.selectedScrollSnap();
setActiveTab(newIndex); setActiveTab(newIndex);
// Update height based on active slide content
const activeSlideRef = slideRefs.current[newIndex];
if (activeSlideRef) {
const height = activeSlideRef.scrollHeight;
setActiveSlideHeight(height);
}
}; };
embla.on("select", onSelect); embla.on("select", onSelect);
@@ -39,6 +48,15 @@ function SwipeableTabs({ tabs, defaultTab = 0, onTabChange, scrollPosition }: Sw
}; };
}, [embla]); }, [embla]);
// Update height when activeTab changes
useEffect(() => {
const activeSlideRef = slideRefs.current[activeTab];
if (activeSlideRef) {
const height = activeSlideRef.scrollHeight;
setActiveSlideHeight(height);
}
}, [activeTab]);
useEffect(() => { useEffect(() => {
if (scrollPosition) { if (scrollPosition) {
setIsSticky(scrollPosition.y > stickyThreshold); setIsSticky(scrollPosition.y > stickyThreshold);
@@ -86,6 +104,10 @@ function SwipeableTabs({ tabs, defaultTab = 0, onTabChange, scrollPosition }: Sw
setControlsRefs(controlsRefs); setControlsRefs(controlsRefs);
}; };
const setSlideRef = (index: number) => (node: HTMLDivElement | null) => {
slideRefs.current[index] = node;
};
return ( return (
<Box> <Box>
{isSticky && ( {isSticky && (
@@ -130,7 +152,7 @@ function SwipeableTabs({ tabs, defaultTab = 0, onTabChange, scrollPosition }: Sw
? 'var(--mantine-color-blue-6)' ? 'var(--mantine-color-blue-6)'
: 'var(--mantine-color-text)', : 'var(--mantine-color-text)',
fontWeight: activeTab === index ? 600 : 400, fontWeight: activeTab === index ? 600 : 400,
transition: 'color 200ms ease, font-weight 200ms ease', transition: 'color 200ms ease',
backgroundColor: 'transparent', backgroundColor: 'transparent',
border: 'none', border: 'none',
borderRadius: 0, borderRadius: 0,
@@ -160,12 +182,21 @@ function SwipeableTabs({ tabs, defaultTab = 0, onTabChange, scrollPosition }: Sw
slideSize="100%" slideSize="100%"
initialSlide={activeTab} initialSlide={activeTab}
style={{ style={{
overflow: 'hidden' overflow: 'hidden',
height: activeSlideHeight === 'auto' ? 'auto' : `${activeSlideHeight}px`,
transition: 'height 300ms ease'
}} }}
> >
{tabs.map((tab, index) => ( {tabs.map((tab, index) => (
<Carousel.Slide key={`${tab.label}-content-${index}`}> <Carousel.Slide key={`${tab.label}-content-${index}`} style={{ height: 'auto' }}>
<Box> <Box
ref={setSlideRef(index)}
style={{
minHeight: 'fit-content',
height: 'auto',
visibility: index === activeTab ? 'visible' : 'hidden'
}}
>
{tab.content} {tab.content}
</Box> </Box>
</Carousel.Slide> </Carousel.Slide>