Add automatic tab centering to TabsScroller

- Clicked tabs now scroll to center of container automatically
- Behavior works on all pages using TabsScroller
- Removed duplicate scroll logic from Features.tsx

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-13 01:29:53 +01:00
parent 005526d5af
commit fc27786c88
2 changed files with 18 additions and 21 deletions

View File

@@ -168,8 +168,22 @@ const TabsScroller = forwardRef<HTMLDivElement, TabsScrollerProps>(
}, 150); }, 150);
}; };
const handleScrollEnd = () => updateOverflow(); const handleScrollEnd = () => updateOverflow();
// Scroll clicked tab to center
const handleTabClick = (event: Event) => {
const target = event.target as HTMLElement;
const tabButton = target.closest('.page-tab-btn, .admin-tab-btn') as HTMLElement;
if (!tabButton) return;
const nodeWidth = node.clientWidth;
const buttonLeft = tabButton.offsetLeft;
const buttonWidth = tabButton.offsetWidth;
const scrollLeft = buttonLeft - (nodeWidth / 2) + (buttonWidth / 2);
node.scrollTo({ left: Math.max(0, scrollLeft), behavior: 'smooth' });
};
node.addEventListener('scroll', handleScroll, { passive: true }); node.addEventListener('scroll', handleScroll, { passive: true });
node.addEventListener('scrollend', handleScrollEnd, { passive: true }); node.addEventListener('scrollend', handleScrollEnd, { passive: true });
node.addEventListener('click', handleTabClick);
let resizeObserver: ResizeObserver | null = null; let resizeObserver: ResizeObserver | null = null;
if (typeof ResizeObserver !== 'undefined') { if (typeof ResizeObserver !== 'undefined') {
resizeObserver = new ResizeObserver(() => scheduleOverflowUpdate()); resizeObserver = new ResizeObserver(() => scheduleOverflowUpdate());
@@ -189,6 +203,7 @@ const TabsScroller = forwardRef<HTMLDivElement, TabsScrollerProps>(
return () => { return () => {
node.removeEventListener('scroll', handleScroll); node.removeEventListener('scroll', handleScroll);
node.removeEventListener('scrollend', handleScrollEnd); node.removeEventListener('scrollend', handleScrollEnd);
node.removeEventListener('click', handleTabClick);
if (scrollEndTimeoutRef.current) { if (scrollEndTimeoutRef.current) {
clearTimeout(scrollEndTimeoutRef.current); clearTimeout(scrollEndTimeoutRef.current);
} }

View File

@@ -51,7 +51,6 @@ export default function Features() {
const [localOrder, setLocalOrder] = useState<string[]>([]); const [localOrder, setLocalOrder] = useState<string[]>([]);
const [localPositions, setLocalPositions] = useState<Record<string, 'top' | 'bottom'>>({}); const [localPositions, setLocalPositions] = useState<Record<string, 'top' | 'bottom'>>({});
const [hasOrderChanges, setHasOrderChanges] = useState(false); const [hasOrderChanges, setHasOrderChanges] = useState(false);
const tabsContainerRef = useRef<HTMLDivElement>(null);
const isUserEditing = useRef(false); // Track if user is actively editing const isUserEditing = useRef(false); // Track if user is actively editing
const initialOrderRef = useRef<string[]>([]); const initialOrderRef = useRef<string[]>([]);
const initialPositionsRef = useRef<Record<string, 'top' | 'bottom'>>({}); const initialPositionsRef = useRef<Record<string, 'top' | 'bottom'>>({});
@@ -155,27 +154,10 @@ export default function Features() {
localPositionsRef.current = localPositions; localPositionsRef.current = localPositions;
}, [localPositions]); }, [localPositions]);
// Scroll active tab to center of container // Handle tab change (centering is handled by TabsScroller)
const scrollActiveTabIntoView = useCallback((tabId: string) => {
if (tabsContainerRef.current) {
const container = tabsContainerRef.current;
const activeButton = container.querySelector(`[data-tab-id="${tabId}"]`) as HTMLElement;
if (activeButton) {
const containerWidth = container.clientWidth;
const buttonLeft = activeButton.offsetLeft;
const buttonWidth = activeButton.offsetWidth;
// Calculate scroll position to center the button
const scrollLeft = buttonLeft - (containerWidth / 2) + (buttonWidth / 2);
container.scrollTo({ left: Math.max(0, scrollLeft), behavior: 'smooth' });
}
}
}, []);
// Handle tab change with scroll
const handleTabChange = useCallback((tabId: TabId) => { const handleTabChange = useCallback((tabId: TabId) => {
setActiveTab(tabId); setActiveTab(tabId);
setTimeout(() => scrollActiveTabIntoView(tabId), 50); }, []);
}, [scrollActiveTabIntoView]);
// Keep saveRef updated with latest function // Keep saveRef updated with latest function
useEffect(() => { useEffect(() => {
saveRef.current = saveModulesToBackend; saveRef.current = saveModulesToBackend;
@@ -787,7 +769,7 @@ export default function Features() {
onMenuClick={toggleMobileMenu} onMenuClick={toggleMobileMenu}
/> />
<div className="page-tabs-container"> <div className="page-tabs-container">
<TabsScroller className="page-tabs-slider" ref={tabsContainerRef} showArrows> <TabsScroller className="page-tabs-slider" showArrows>
<div className="page-title-section"> <div className="page-title-section">
<span className="page-title-text">{t.featuresPage.title}</span> <span className="page-title-text">{t.featuresPage.title}</span>
</div> </div>