Files
web-menu-react-version-/src/contexts/ScrollHandlerContext.tsx
Mohammed Al-yaseen da9d7f8eb3 refactor sticky scroll handler
- because of making the root's child div styles as override: auto it won't be a ref for the sticky category logic
2025-10-07 12:57:05 +03:00

107 lines
3.2 KiB
TypeScript

import {
createContext,
Dispatch,
ReactNode,
SetStateAction,
useCallback,
useContext,
useRef,
useState,
} from "react";
/**
*
*/
interface ScrollHandlerContextType {
categoryRefs: React.RefObject<{ [key: number]: HTMLDivElement | null }>;
showScrollTop: boolean;
setShowScrollTop: Dispatch<SetStateAction<boolean>>;
isCategoriesSticky: boolean;
setIsCategoriesSticky: Dispatch<SetStateAction<boolean>>;
categoriesContainerRef: React.RefObject<HTMLDivElement>;
scrollToCategory: (categoryId: number) => void;
activeCategory: number | null;
setActiveCategory: Dispatch<SetStateAction<number | null>>;
}
const ScrollHandlerContext = createContext<
ScrollHandlerContextType | undefined
>(undefined);
export function ScrollHandlerProvider({ children }: { children: ReactNode }) {
const categoryRefs = useRef<{ [key: number]: HTMLDivElement | null }>({});
const [showScrollTop, setShowScrollTop] = useState(false);
// Handle scroll to show/hide scroll-to-top button and sticky categories
const [isCategoriesSticky, setIsCategoriesSticky] = useState(false);
const categoriesContainerRef = useRef<HTMLDivElement>(null);
// Active category state
const [activeCategory, setActiveCategory] = useState<number | null>(null);
// Function to scroll to a specific category
const scrollToCategory = useCallback((categoryId: number) => {
const categoryRef = categoryRefs.current?.[categoryId];
// Use a more stable approach to find the scroll container
const findScrollContainer = () => {
const antApp = document.querySelector('.ant-app') as HTMLElement;
if (antApp && antApp.scrollHeight > antApp.clientHeight) {
return antApp;
}
const appContainer = document.querySelector('[class*="App"]') as HTMLElement;
if (appContainer && appContainer.scrollHeight > appContainer.clientHeight) {
return appContainer;
}
return document.documentElement;
};
const scrollContainer = findScrollContainer();
if (categoryRef && scrollContainer) {
// Get the position of the category relative to the scroll container
const categoryRect = categoryRef.getBoundingClientRect();
const containerRect = scrollContainer.getBoundingClientRect();
// Calculate the target scroll position
const targetScrollTop = scrollContainer.scrollTop + categoryRect.top - containerRect.top;
// Smooth scroll to the target position
scrollContainer.scrollTo({
top: targetScrollTop,
behavior: "smooth"
});
}
}, []);
return (
<ScrollHandlerContext.Provider
value={{
categoryRefs,
categoriesContainerRef,
showScrollTop,
setShowScrollTop,
isCategoriesSticky,
setIsCategoriesSticky,
scrollToCategory,
activeCategory,
setActiveCategory,
}}
>
{children}
</ScrollHandlerContext.Provider>
);
}
// eslint-disable-next-line react-refresh/only-export-components
export function useScrollHandler() {
const context = useContext(ScrollHandlerContext);
if (context === undefined) {
throw new Error(
"useScrollHandler must be used within a ScrollHandlerProvider"
);
}
return context;
}