- because of making the root's child div styles as override: auto it won't be a ref for the sticky category logic
107 lines
3.2 KiB
TypeScript
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;
|
|
}
|