useDebouncedVisibility.js 1.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. "use client";
  2. import React from "react";
  3. /**
  4. * useDebouncedVisibility
  5. *
  6. * Delays the "show" transition and optionally enforces a minimum visible time.
  7. *
  8. * Typical use cases:
  9. * - Avoid flicker for fast loading indicators (e.g. show only after 300ms).
  10. * - Keep the indicator visible for at least X ms once shown (avoid blink).
  11. *
  12. * @param {boolean} isActive
  13. * @param {{ delayMs?: number, minVisibleMs?: number }} options
  14. * @returns {boolean}
  15. */
  16. export function useDebouncedVisibility(
  17. isActive,
  18. { delayMs = 200, minVisibleMs = 0 } = {},
  19. ) {
  20. const [visible, setVisible] = React.useState(false);
  21. const showTimerRef = React.useRef(null);
  22. const hideTimerRef = React.useRef(null);
  23. const visibleSinceRef = React.useRef(0);
  24. React.useEffect(() => {
  25. if (showTimerRef.current) {
  26. clearTimeout(showTimerRef.current);
  27. showTimerRef.current = null;
  28. }
  29. if (hideTimerRef.current) {
  30. clearTimeout(hideTimerRef.current);
  31. hideTimerRef.current = null;
  32. }
  33. if (isActive) {
  34. if (!visible) {
  35. showTimerRef.current = setTimeout(
  36. () => {
  37. visibleSinceRef.current = Date.now();
  38. setVisible(true);
  39. },
  40. Math.max(0, Number(delayMs) || 0),
  41. );
  42. }
  43. return;
  44. }
  45. if (!visible) return;
  46. const now = Date.now();
  47. const elapsed = now - (visibleSinceRef.current || now);
  48. const minMs = Math.max(0, Number(minVisibleMs) || 0);
  49. if (elapsed >= minMs) {
  50. setVisible(false);
  51. return;
  52. }
  53. hideTimerRef.current = setTimeout(() => {
  54. setVisible(false);
  55. }, minMs - elapsed);
  56. }, [isActive, delayMs, minVisibleMs, visible]);
  57. React.useEffect(() => {
  58. return () => {
  59. if (showTimerRef.current) clearTimeout(showTimerRef.current);
  60. if (hideTimerRef.current) clearTimeout(hideTimerRef.current);
  61. };
  62. }, []);
  63. return visible;
  64. }