import throttle from 'lodash/throttle'; import React, { useState, useEffect, useCallback } from 'react'; import { useIntl, MessageDescriptor } from 'react-intl'; import { Icon, Text } from 'soapbox/components/ui'; import { useSettings } from 'soapbox/hooks'; interface IScrollTopButton { /** Callback when clicked, and also when scrolled to the top. */ onClick: () => void; /** Number of unread items. */ count: number; /** Message to display in the button (should contain a `{count}` value). */ message: MessageDescriptor; /** Distance from the top of the screen (scrolling down) before the button appears. */ threshold?: number; /** Distance from the top of the screen (scrolling up) before the action is triggered. */ autoloadThreshold?: number; } /** Floating new post counter above timelines, clicked to scroll to top. */ const ScrollTopButton: React.FC = ({ onClick, count, message, threshold = 400, autoloadThreshold = 50, }) => { const intl = useIntl(); const settings = useSettings(); // Whether we are scrolled past the `threshold`. const [scrolled, setScrolled] = useState(false); // Whether we are scrolled above the `autoloadThreshold`. const [scrolledTop, setScrolledTop] = useState(false); const autoload = settings.get('autoloadTimelines') === true; const visible = count > 0 && scrolled; /** Number of pixels scrolled down from the top of the page. */ const getScrollTop = (): number => { return (document.scrollingElement || document.documentElement).scrollTop; }; /** Unload feed items if scrolled to the top. */ const maybeUnload = useCallback(() => { if (autoload && scrolledTop) { onClick(); } }, [autoload, scrolledTop, onClick]); /** Set state while scrolling. */ const handleScroll = useCallback(throttle(() => { const scrollTop = getScrollTop(); setScrolled(scrollTop > threshold); setScrolledTop(scrollTop <= autoloadThreshold); }, 150, { trailing: true }), [threshold, autoloadThreshold]); /** Scroll to top and trigger `onClick`. */ const handleClick: React.MouseEventHandler = useCallback(() => { window.scrollTo({ top: 0 }); onClick(); }, [onClick]); useEffect(() => { // Delay adding the scroll listener so navigating back doesn't // unload feed items before the feed is rendered. setTimeout(() => { window.addEventListener('scroll', handleScroll); handleScroll(); }, 250); return () => { window.removeEventListener('scroll', handleScroll); }; }, [handleScroll]); useEffect(() => { maybeUnload(); }, [maybeUnload]); if (!visible) { return null; } return (
); }; export default ScrollTopButton;