Fix mobile tab header and swipe behavior

This commit is contained in:
2025-12-22 21:01:33 +01:00
parent 1f52680721
commit a1ae2a53a6
6 changed files with 38 additions and 6 deletions

View File

@@ -21,6 +21,7 @@ type SwipeTabsProps<T extends string | number> = {
swipeDisabled?: boolean;
threshold?: number;
renderWindow?: number;
scrollToTopOnChange?: boolean;
};
const DRAG_START_DISTANCE = 3;
@@ -55,7 +56,8 @@ export function SwipeTabs<T extends string | number>({
panelClassName,
swipeDisabled = false,
threshold = DEFAULT_THRESHOLD,
renderWindow = DEFAULT_RENDER_WINDOW
renderWindow = DEFAULT_RENDER_WINDOW,
scrollToTopOnChange = true
}: SwipeTabsProps<T>) {
const containerRef = useRef<HTMLDivElement>(null);
const trackRef = useRef<HTMLDivElement>(null);
@@ -77,6 +79,7 @@ export function SwipeTabs<T extends string | number>({
const pendingIndexRef = useRef<number | null>(null);
const isAnimatingRef = useRef(false);
const needsResetRef = useRef(false);
const hasScrolledRef = useRef(false);
const dragRef = useRef({
pointerId: null as number | null,
touchId: null as number | null,
@@ -164,6 +167,16 @@ export function SwipeTabs<T extends string | number>({
};
}, []);
useEffect(() => {
if (!scrollToTopOnChange) return;
if (!hasScrolledRef.current) {
hasScrolledRef.current = true;
return;
}
if (typeof window === 'undefined') return;
window.scrollTo({ top: 0, left: 0, behavior: 'auto' });
}, [activeTab, scrollToTopOnChange]);
const resetToIndex = useCallback(
(index: number) => {
displayIndexRef.current = index;

View File

@@ -16,6 +16,7 @@ type TabsScrollerProps = {
scrollStep?: number;
ariaLabelLeft?: string;
ariaLabelRight?: string;
showArrows?: boolean;
};
const SCROLL_EDGE_THRESHOLD = 8;
@@ -30,7 +31,8 @@ const TabsScroller = forwardRef<HTMLDivElement, TabsScrollerProps>(
children,
scrollStep,
ariaLabelLeft = 'Scroll tabs left',
ariaLabelRight = 'Scroll tabs right'
ariaLabelRight = 'Scroll tabs right',
showArrows = false
},
ref
) => {
@@ -63,10 +65,21 @@ const TabsScroller = forwardRef<HTMLDivElement, TabsScrollerProps>(
const updateOverflow = useCallback(() => {
const node = sliderRef.current;
if (!node) return;
if (!showArrows) {
setShowLeft(false);
setShowRight(false);
return;
}
const hasTabs = Boolean(node.querySelector('.page-tab-btn, .admin-tab-btn'));
if (!hasTabs) {
setShowLeft(false);
setShowRight(false);
return;
}
const maxScroll = node.scrollWidth - node.clientWidth;
setShowLeft(node.scrollLeft > SCROLL_EDGE_THRESHOLD);
setShowRight(node.scrollLeft < maxScroll - SCROLL_EDGE_THRESHOLD);
}, []);
}, [showArrows]);
const scheduleOverflowUpdate = useCallback(() => {
if (rafRef.current) return;

View File

@@ -25,7 +25,7 @@ export default function AdminPanel({ initialTab = 'general' }: { initialTab?: Ta
return (
<main className="main-content admin-panel-root">
<div className="page-tabs-container">
<TabsScroller className="page-tabs-slider">
<TabsScroller className="page-tabs-slider" showArrows>
<button className="mobile-menu-btn" onClick={toggleMobileMenu} aria-label={t.theme.toggleMenu}>
<span className="material-symbols-outlined">menu</span>
</button>

View File

@@ -744,7 +744,7 @@ export default function Features() {
return (
<main className="main-content admin-panel-root">
<div className="page-tabs-container">
<TabsScroller className="page-tabs-slider" ref={tabsContainerRef}>
<TabsScroller className="page-tabs-slider" ref={tabsContainerRef} showArrows>
<button className="mobile-menu-btn" onClick={toggleMobileMenu} aria-label={t.theme.toggleMenu}>
<span className="material-symbols-outlined">menu</span>
</button>

View File

@@ -661,7 +661,7 @@ export default function ThemeSettings() {
)}
{/* Modern Tab Navigation */}
<div className="page-tabs-container">
<TabsScroller className="page-tabs-slider">
<TabsScroller className="page-tabs-slider" showArrows>
<button className="mobile-menu-btn" onClick={toggleMobileMenu} aria-label={t.theme.toggleMenu}>
<span className="material-symbols-outlined">menu</span>
</button>

View File

@@ -464,6 +464,10 @@ body {
min-height: 48px;
}
.tabs-scroll-shell {
width: 100%;
}
.page-title-section,
.admin-title-section {
display: flex;
@@ -609,6 +613,8 @@ body {
width: 100%;
max-width: none;
margin: 0;
flex: 1 1 auto;
min-height: 0;
}
/* Swipeable tab panels */