significant refactor

This commit is contained in:
2025-08-30 01:42:23 -05:00
parent 7136f646a3
commit 052f53444e
106 changed files with 1994 additions and 1701 deletions

View File

@@ -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;