glam/packages/hooks/src/useCollapsibleHeader.ts
2025-12-21 00:01:54 +01:00

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;