optimize swipeable tabs
This commit is contained in:
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user