significant refactor
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
import { FloatingIndicator, UnstyledButton, Box, Text } from "@mantine/core";
|
||||
import { Carousel } from "@mantine/carousel";
|
||||
import { useState, useEffect, ReactNode, useRef, useCallback, useMemo } from "react";
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
ReactNode,
|
||||
useRef,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from "react";
|
||||
import { useRouter } from "@tanstack/react-router";
|
||||
|
||||
interface TabItem {
|
||||
@@ -14,15 +21,21 @@ interface SwipeableTabsProps {
|
||||
onTabChange?: (index: number, tab: TabItem) => void;
|
||||
}
|
||||
|
||||
function SwipeableTabs({ tabs, defaultTab = 0, onTabChange }: SwipeableTabsProps) {
|
||||
function SwipeableTabs({
|
||||
tabs,
|
||||
defaultTab = 0,
|
||||
onTabChange,
|
||||
}: SwipeableTabsProps) {
|
||||
const router = useRouter();
|
||||
const search = router.state.location.search as any;
|
||||
const [embla, setEmbla] = useState<any>(null);
|
||||
|
||||
|
||||
const getActiveTabFromUrl = useCallback(() => {
|
||||
const urlTab = search?.tab;
|
||||
if (typeof urlTab === 'string') {
|
||||
const tabIndex = tabs.findIndex(tab => tab.label.toLowerCase() === urlTab.toLowerCase());
|
||||
if (typeof urlTab === "string") {
|
||||
const tabIndex = tabs.findIndex(
|
||||
(tab) => tab.label.toLowerCase() === urlTab.toLowerCase()
|
||||
);
|
||||
return tabIndex !== -1 ? tabIndex : defaultTab;
|
||||
}
|
||||
return defaultTab;
|
||||
@@ -32,22 +45,25 @@ function SwipeableTabs({ tabs, defaultTab = 0, onTabChange }: SwipeableTabsProps
|
||||
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
|
||||
const controlsRefs = useRef<Record<number, HTMLSpanElement | null>>({});
|
||||
const slideRefs = useRef<Record<number, HTMLDivElement | null>>({});
|
||||
const [carouselHeight, setCarouselHeight] = useState<number | 'auto'>('auto');
|
||||
const [carouselHeight, setCarouselHeight] = useState<number | "auto">("auto");
|
||||
|
||||
const changeTab = useCallback((index: number) => {
|
||||
if (index === activeTab || index < 0 || index >= tabs.length) return;
|
||||
|
||||
setActiveTab(index);
|
||||
embla?.scrollTo(index);
|
||||
onTabChange?.(index, tabs[index]);
|
||||
|
||||
const tabLabel = tabs[index].label.toLowerCase();
|
||||
if (typeof window !== 'undefined') {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('tab', tabLabel);
|
||||
window.history.replaceState(null, '', url.toString());
|
||||
}
|
||||
}, [activeTab, tabs, embla, onTabChange]);
|
||||
const changeTab = useCallback(
|
||||
(index: number) => {
|
||||
if (index === activeTab || index < 0 || index >= tabs.length) return;
|
||||
|
||||
setActiveTab(index);
|
||||
embla?.scrollTo(index);
|
||||
onTabChange?.(index, tabs[index]);
|
||||
|
||||
const tabLabel = tabs[index].label.toLowerCase();
|
||||
if (typeof window !== "undefined") {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("tab", tabLabel);
|
||||
window.history.replaceState(null, "", url.toString());
|
||||
}
|
||||
},
|
||||
[activeTab, tabs, embla, onTabChange]
|
||||
);
|
||||
|
||||
const handleEmblaSelect = useCallback(() => {
|
||||
if (!embla) return;
|
||||
@@ -78,13 +94,19 @@ function SwipeableTabs({ tabs, defaultTab = 0, onTabChange }: SwipeableTabsProps
|
||||
}
|
||||
}, [activeTab]);
|
||||
|
||||
const setControlRef = useCallback((index: number) => (node: HTMLSpanElement | null) => {
|
||||
controlsRefs.current[index] = node;
|
||||
}, []);
|
||||
const setControlRef = useCallback(
|
||||
(index: number) => (node: HTMLSpanElement | null) => {
|
||||
controlsRefs.current[index] = node;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const setSlideRef = useCallback((index: number) => (node: HTMLDivElement | null) => {
|
||||
slideRefs.current[index] = node;
|
||||
}, []);
|
||||
const setSlideRef = useCallback(
|
||||
(index: number) => (node: HTMLDivElement | null) => {
|
||||
slideRefs.current[index] = node;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
@@ -93,10 +115,10 @@ function SwipeableTabs({ tabs, defaultTab = 0, onTabChange }: SwipeableTabsProps
|
||||
pos="sticky"
|
||||
top={0}
|
||||
style={{
|
||||
display: 'flex',
|
||||
marginBottom: 'var(--mantine-spacing-md)',
|
||||
display: "flex",
|
||||
marginBottom: "var(--mantine-spacing-md)",
|
||||
zIndex: 100,
|
||||
backgroundColor: 'var(--mantine-color-body)'
|
||||
backgroundColor: "var(--mantine-color-body)",
|
||||
}}
|
||||
>
|
||||
<FloatingIndicator
|
||||
@@ -104,9 +126,9 @@ function SwipeableTabs({ tabs, defaultTab = 0, onTabChange }: SwipeableTabsProps
|
||||
parent={rootRef}
|
||||
styles={{
|
||||
root: {
|
||||
borderBottom: '2px solid var(--mantine-primary-color-filled)',
|
||||
paddingInline: '0.5rem'
|
||||
}
|
||||
borderBottom: "2px solid var(--mantine-primary-color-filled)",
|
||||
paddingInline: "0.5rem",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{tabs.map((tab, index) => (
|
||||
@@ -115,25 +137,26 @@ function SwipeableTabs({ tabs, defaultTab = 0, onTabChange }: SwipeableTabsProps
|
||||
onClick={() => changeTab(index)}
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: 'var(--mantine-spacing-sm) var(--mantine-spacing-md)',
|
||||
textAlign: 'center',
|
||||
color: activeTab === index
|
||||
? 'var(--mantine-color-blue-6)'
|
||||
: 'var(--mantine-color-text)',
|
||||
padding: "var(--mantine-spacing-sm) var(--mantine-spacing-md)",
|
||||
textAlign: "center",
|
||||
color:
|
||||
activeTab === index
|
||||
? "var(--mantine-primary-color-filled)"
|
||||
: "var(--mantine-color-text)",
|
||||
fontWeight: activeTab === index ? 600 : 400,
|
||||
transition: 'color 200ms ease, font-weight 200ms ease',
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
transition: "color 200ms ease, font-weight 200ms ease",
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
borderRadius: 0,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
<Text
|
||||
size="sm"
|
||||
component="span"
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
paddingInline: '1rem',
|
||||
paddingBottom: '0.25rem'
|
||||
style={{
|
||||
display: "inline-block",
|
||||
paddingInline: "1rem",
|
||||
paddingBottom: "0.25rem",
|
||||
}}
|
||||
ref={setControlRef(index)}
|
||||
>
|
||||
@@ -150,17 +173,14 @@ function SwipeableTabs({ tabs, defaultTab = 0, onTabChange }: SwipeableTabsProps
|
||||
slideSize="100%"
|
||||
initialSlide={activeTab}
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
height: carouselHeight === 'auto' ? 'auto' : `${carouselHeight}px`,
|
||||
transition: 'height 300ms ease'
|
||||
overflow: "hidden",
|
||||
height: carouselHeight === "auto" ? "auto" : `${carouselHeight}px`,
|
||||
transition: "height 300ms ease",
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, index) => (
|
||||
<Carousel.Slide key={`${tab.label}-content-${index}`}>
|
||||
<Box
|
||||
ref={setSlideRef(index)}
|
||||
style={{ height: 'auto' }}
|
||||
>
|
||||
<Box ref={setSlideRef(index)} style={{ height: "auto" }}>
|
||||
{tab.content}
|
||||
</Box>
|
||||
</Carousel.Slide>
|
||||
@@ -170,4 +190,4 @@ function SwipeableTabs({ tabs, defaultTab = 0, onTabChange }: SwipeableTabsProps
|
||||
);
|
||||
}
|
||||
|
||||
export default SwipeableTabs;
|
||||
export default SwipeableTabs;
|
||||
|
||||
Reference in New Issue
Block a user