diff --git a/src/app/components/LeftSidebar/index.tsx b/src/app/components/LeftSidebar/index.tsx index ecc124ee..cfb85484 100644 --- a/src/app/components/LeftSidebar/index.tsx +++ b/src/app/components/LeftSidebar/index.tsx @@ -1,6 +1,7 @@ import Button from "@app/components/ui/Button"; import Icon from "@app/components/ui/Icon"; import { useAppLayout } from "@app/providers/AppLayout"; +import { useMobileScrollReveal } from "@app/providers/AppLayout/useMobileScrollReveal"; import { useSidebarSwipe } from "@app/providers/AppLayout/useSidebarSwipe"; import { cn } from "@app/utils/cn"; import React from "react"; @@ -20,6 +21,7 @@ const LeftSidebar: React.FC = ({ hideContentWhenClosed = false, }) => { const { appLayout, updateAppLayout } = useAppLayout(); + const scrollVisible = useMobileScrollReveal(); const sidebarOpen = appLayout.leftSidebarOpen; @@ -70,10 +72,12 @@ const LeftSidebar: React.FC = ({ onPress={toggleSidebar} aria-label={sidebarOpen ? "Collapse sidebar" : "Expand sidebar"} className={cn( - "flex-none m-2 z-10", + "flex-none m-2 z-10 transition-opacity duration-500", sidebarOpen ? "" - : "fixed left-0 bottom-[env(safe-area-inset-bottom)] md:bottom-0 md:relative", + : "fixed left-0 bottom-[env(safe-area-inset-bottom)] md:bottom-0 md:relative md:opacity-100", + !sidebarOpen && + (scrollVisible ? "opacity-100" : "opacity-0 md:opacity-100"), )} > = ({ } = useAppLayout(); const sidebarOpen = rightSidebarOpen; + const scrollVisible = useMobileScrollReveal(); const bind = useSidebarSwipe({ isOpen: sidebarOpen, @@ -68,10 +70,12 @@ const RightSidebar: React.FC = ({ onPress={toggleSidebar} aria-label={rightSidebarOpen ? "Collapse sidebar" : "Expand sidebar"} className={cn( - "flex-none m-2 z-10 pointer-events-auto", + "flex-none m-2 z-10 pointer-events-auto transition-opacity duration-500", rightSidebarOpen ? "" - : "fixed right-0 bottom-[env(safe-area-inset-bottom)] lg:bottom-0 lg:relative lg:self-center", + : "fixed right-0 bottom-[env(safe-area-inset-bottom)] lg:bottom-0 lg:relative lg:self-center lg:opacity-100", + !rightSidebarOpen && + (scrollVisible ? "opacity-100" : "opacity-0 lg:opacity-100"), )} > { + const [visible, setVisible] = useState(true); + const timeoutRef = useRef | null>(null); + + const handleScroll = useCallback(() => { + setVisible(false); + if (timeoutRef.current !== null) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = setTimeout(() => { + setVisible(true); + }, FADE_DELAY_MS); + }, []); + + useEffect(() => { + // Use capture so we catch scroll events from any scrollable child element + window.addEventListener("scroll", handleScroll, { + capture: true, + passive: true, + }); + return () => { + window.removeEventListener("scroll", handleScroll, { capture: true }); + if (timeoutRef.current !== null) { + clearTimeout(timeoutRef.current); + } + }; + }, [handleScroll]); + + return visible; +}; diff --git a/src/app/providers/AppPage/provider.tsx b/src/app/providers/AppPage/provider.tsx index 2c1f35ba..25d01244 100644 --- a/src/app/providers/AppPage/provider.tsx +++ b/src/app/providers/AppPage/provider.tsx @@ -1,5 +1,6 @@ import Button from "@app/components/ui/Button"; import { useAppLayout } from "@app/providers/AppLayout"; +import { useMobileScrollReveal } from "@app/providers/AppLayout/useMobileScrollReveal"; import { SWIPE_THRESHOLD_DISTANCE, SWIPE_THRESHOLD_VELOCITY, @@ -21,6 +22,7 @@ export const AppPageProvider: React.FC<{ const [hasChildren, setHasChildren] = React.useState(false); const [showArticleFixedInfo, setShowArticleFixedInfo] = React.useState(false); const { pathname } = useLocation(); + const scrollVisible = useMobileScrollReveal(); const { appLayout: { leftSidebarOpen, rightSidebarOpen }, updateAppLayout, @@ -110,7 +112,10 @@ export const AppPageProvider: React.FC<{ isIconOnly size="sm" variant="flat" - className="absolute top-2 right-3" + className={cn( + "absolute top-2 right-3 transition-opacity duration-500", + scrollVisible ? "opacity-100" : "opacity-0 md:opacity-100", + )} onPress={() => setShowArticleFixedInfo(true)} onMouseEnter={() => setShowArticleFixedInfo(true)} >