92 lines
2.5 KiB
TypeScript
92 lines
2.5 KiB
TypeScript
import { useState, useEffect, useCallback, type RefObject } from 'react';
|
|
|
|
interface UseCollapsibleHeaderOptions {
|
|
/** Scroll threshold in pixels before header collapses (default: 50) */
|
|
threshold?: number;
|
|
/** Debounce delay in ms for scroll events (default: 10) */
|
|
debounceMs?: number;
|
|
}
|
|
|
|
interface UseCollapsibleHeaderReturn {
|
|
/** Whether the header is currently collapsed */
|
|
isCollapsed: boolean;
|
|
/** Manually set collapsed state */
|
|
setIsCollapsed: (collapsed: boolean) => void;
|
|
/** Toggle collapsed state */
|
|
toggleCollapsed: () => void;
|
|
/** Current scroll position */
|
|
scrollTop: number;
|
|
}
|
|
|
|
/**
|
|
* Hook to manage collapsible header behavior based on scroll position.
|
|
*
|
|
* @param scrollContainerRef - Ref to the scrollable container element
|
|
* @param options - Configuration options
|
|
* @returns Object with collapsed state and control functions
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* const containerRef = useRef<HTMLDivElement>(null);
|
|
* const { isCollapsed } = useCollapsibleHeader(containerRef);
|
|
*
|
|
* return (
|
|
* <div className={`header ${isCollapsed ? 'header--collapsed' : ''}`}>
|
|
* <h1>Title</h1>
|
|
* </div>
|
|
* <div ref={containerRef} className="content">
|
|
* {content}
|
|
* </div>
|
|
* );
|
|
* ```
|
|
*/
|
|
export function useCollapsibleHeader(
|
|
scrollContainerRef: RefObject<HTMLElement | null>,
|
|
options: UseCollapsibleHeaderOptions = {}
|
|
): UseCollapsibleHeaderReturn {
|
|
const { threshold = 50, debounceMs = 10 } = options;
|
|
|
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
|
const [scrollTop, setScrollTop] = useState(0);
|
|
|
|
const toggleCollapsed = useCallback(() => {
|
|
setIsCollapsed(prev => !prev);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const container = scrollContainerRef.current;
|
|
if (!container) return;
|
|
|
|
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
|
|
const handleScroll = () => {
|
|
if (timeoutId) return; // Debounce
|
|
|
|
timeoutId = setTimeout(() => {
|
|
const currentScrollTop = container.scrollTop;
|
|
setScrollTop(currentScrollTop);
|
|
setIsCollapsed(currentScrollTop > threshold);
|
|
timeoutId = null;
|
|
}, debounceMs);
|
|
};
|
|
|
|
container.addEventListener('scroll', handleScroll, { passive: true });
|
|
|
|
// Check initial scroll position
|
|
handleScroll();
|
|
|
|
return () => {
|
|
container.removeEventListener('scroll', handleScroll);
|
|
if (timeoutId) clearTimeout(timeoutId);
|
|
};
|
|
}, [scrollContainerRef, threshold, debounceMs]);
|
|
|
|
return {
|
|
isCollapsed,
|
|
setIsCollapsed,
|
|
toggleCollapsed,
|
|
scrollTop,
|
|
};
|
|
}
|
|
|
|
export default useCollapsibleHeader;
|