From fea3649dd56e646bced0af5f406855fd5362516e Mon Sep 17 00:00:00 2001 From: alejandrobailo Date: Fri, 20 Feb 2026 17:08:51 +0100 Subject: [PATCH] feat(ui): add useScrollHint hook for overflow containers --- ui/hooks/index.ts | 1 + ui/hooks/use-scroll-hint.ts | 63 +++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 ui/hooks/use-scroll-hint.ts diff --git a/ui/hooks/index.ts b/ui/hooks/index.ts index f03f9b4ba0..75ff6da61d 100644 --- a/ui/hooks/index.ts +++ b/ui/hooks/index.ts @@ -3,6 +3,7 @@ export * from "./use-credentials-form"; export * from "./use-form-server-errors"; export * from "./use-local-storage"; export * from "./use-related-filters"; +export * from "./use-scroll-hint"; export * from "./use-sidebar"; export * from "./use-store"; export * from "./use-url-filters"; diff --git a/ui/hooks/use-scroll-hint.ts b/ui/hooks/use-scroll-hint.ts new file mode 100644 index 0000000000..ba50f629b8 --- /dev/null +++ b/ui/hooks/use-scroll-hint.ts @@ -0,0 +1,63 @@ +"use client"; + +import { UIEvent, useEffect, useRef, useState } from "react"; + +interface UseScrollHintOptions { + enabled?: boolean; + refreshToken?: string | number; +} + +const SCROLL_THRESHOLD_PX = 2; + +function shouldShowScrollHint(element: HTMLDivElement) { + const hasOverflow = + element.scrollHeight - element.clientHeight > SCROLL_THRESHOLD_PX; + const isAtBottom = + element.scrollTop + element.clientHeight >= + element.scrollHeight - SCROLL_THRESHOLD_PX; + + return hasOverflow && !isAtBottom; +} + +export function useScrollHint({ + enabled = true, + refreshToken, +}: UseScrollHintOptions = {}) { + const containerRef = useRef(null); + const [showScrollHint, setShowScrollHint] = useState(false); + + useEffect(() => { + if (!enabled) { + setShowScrollHint(false); + return; + } + + const frame = requestAnimationFrame(() => { + const element = containerRef.current; + if (!element) return; + setShowScrollHint(shouldShowScrollHint(element)); + }); + + const handleResize = () => { + const element = containerRef.current; + if (!element) return; + setShowScrollHint(shouldShowScrollHint(element)); + }; + + window.addEventListener("resize", handleResize); + return () => { + cancelAnimationFrame(frame); + window.removeEventListener("resize", handleResize); + }; + }, [enabled, refreshToken]); + + const handleScroll = (event: UIEvent) => { + setShowScrollHint(shouldShowScrollHint(event.currentTarget)); + }; + + return { + containerRef, + showScrollHint, + handleScroll, + }; +}