optimize swipeable tabs

This commit is contained in:
yohlo
2025-08-27 09:58:55 -05:00
parent 1eb621dd34
commit e5f3bbe095

View File

@@ -1,6 +1,6 @@
import { FloatingIndicator, UnstyledButton, Box, Text } from "@mantine/core";
import { Carousel } from "@mantine/carousel";
import { useState, useEffect, ReactNode, useRef } from "react";
import { useState, useEffect, ReactNode, useRef, useCallback, useMemo } from "react";
import { useRouter } from "@tanstack/react-router";
interface TabItem {
@@ -19,22 +19,22 @@ function SwipeableTabs({ tabs, defaultTab = 0, onTabChange }: SwipeableTabsProps
const search = router.state.location.search as any;
const [embla, setEmbla] = useState<any>(null);
const getActiveTabFromUrl = () => {
const getActiveTabFromUrl = useCallback(() => {
const urlTab = search?.tab;
if (typeof urlTab === 'string') {
const tabIndex = tabs.findIndex(tab => tab.label.toLowerCase() === urlTab.toLowerCase());
return tabIndex !== -1 ? tabIndex : defaultTab;
}
return defaultTab;
};
}, [search?.tab, tabs, defaultTab]);
const [activeTab, setActiveTab] = useState(getActiveTabFromUrl);
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
const [controlsRefs, setControlsRefs] = useState<Record<number, HTMLSpanElement | null>>({});
const controlsRefs = useRef<Record<number, HTMLSpanElement | null>>({});
const slideRefs = useRef<Record<number, HTMLDivElement | null>>({});
const [carouselHeight, setCarouselHeight] = useState<number | 'auto'>('auto');
const changeTab = (index: number) => {
const changeTab = useCallback((index: number) => {
if (index === activeTab || index < 0 || index >= tabs.length) return;
setActiveTab(index);
@@ -47,19 +47,20 @@ function SwipeableTabs({ tabs, defaultTab = 0, onTabChange }: SwipeableTabsProps
url.searchParams.set('tab', tabLabel);
window.history.replaceState(null, '', url.toString());
}
};
}, [activeTab, tabs, embla, onTabChange]);
const handleEmblaSelect = useCallback(() => {
if (!embla) return;
const newIndex = embla.selectedScrollSnap();
changeTab(newIndex);
}, [embla, changeTab]);
useEffect(() => {
if (!embla) return;
const onSelect = () => {
const newIndex = embla.selectedScrollSnap();
changeTab(newIndex);
};
embla.on("select", onSelect);
return () => embla.off("select", onSelect);
}, [embla, activeTab, tabs]);
embla.on("select", handleEmblaSelect);
return () => embla.off("select", handleEmblaSelect);
}, [embla, handleEmblaSelect]);
useEffect(() => {
const newActiveTab = getActiveTabFromUrl();
@@ -77,14 +78,13 @@ function SwipeableTabs({ tabs, defaultTab = 0, onTabChange }: SwipeableTabsProps
}
}, [activeTab]);
const setControlRef = (index: number) => (node: HTMLSpanElement | null) => {
controlsRefs[index] = node;
setControlsRefs(controlsRefs);
};
const setControlRef = useCallback((index: number) => (node: HTMLSpanElement | null) => {
controlsRefs.current[index] = node;
}, []);
const setSlideRef = (index: number) => (node: HTMLDivElement | null) => {
const setSlideRef = useCallback((index: number) => (node: HTMLDivElement | null) => {
slideRefs.current[index] = node;
};
}, []);
return (
<Box>
@@ -100,7 +100,7 @@ function SwipeableTabs({ tabs, defaultTab = 0, onTabChange }: SwipeableTabsProps
}}
>
<FloatingIndicator
target={controlsRefs[activeTab]}
target={controlsRefs.current[activeTab]}
parent={rootRef}
styles={{
root: {