"use client"; import React from "react"; /** * useDebouncedVisibility * * Delays the "show" transition and optionally enforces a minimum visible time. * * Typical use cases: * - Avoid flicker for fast loading indicators (e.g. show only after 300ms). * - Keep the indicator visible for at least X ms once shown (avoid blink). * * @param {boolean} isActive * @param {{ delayMs?: number, minVisibleMs?: number }} options * @returns {boolean} */ export function useDebouncedVisibility( isActive, { delayMs = 200, minVisibleMs = 0 } = {}, ) { const [visible, setVisible] = React.useState(false); const showTimerRef = React.useRef(null); const hideTimerRef = React.useRef(null); const visibleSinceRef = React.useRef(0); React.useEffect(() => { if (showTimerRef.current) { clearTimeout(showTimerRef.current); showTimerRef.current = null; } if (hideTimerRef.current) { clearTimeout(hideTimerRef.current); hideTimerRef.current = null; } if (isActive) { if (!visible) { showTimerRef.current = setTimeout( () => { visibleSinceRef.current = Date.now(); setVisible(true); }, Math.max(0, Number(delayMs) || 0), ); } return; } if (!visible) return; const now = Date.now(); const elapsed = now - (visibleSinceRef.current || now); const minMs = Math.max(0, Number(minVisibleMs) || 0); if (elapsed >= minMs) { setVisible(false); return; } hideTimerRef.current = setTimeout(() => { setVisible(false); }, minMs - elapsed); }, [isActive, delayMs, minVisibleMs, visible]); React.useEffect(() => { return () => { if (showTimerRef.current) clearTimeout(showTimerRef.current); if (hideTimerRef.current) clearTimeout(hideTimerRef.current); }; }, []); return visible; }