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