import clsx from 'clsx'; import throttle from 'lodash/throttle'; import React, { useState, useEffect, useCallback } from 'react'; import { useIntl, MessageDescriptor } from 'react-intl'; import Icon from '@/components/ui/icon'; import { useSettings } from '@/stores/settings'; 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; /** Message to announce in the live region (should contain a `{count}` value). If not provided, `message` will be used. */ liveRegionMessage?: 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 = 240, autoloadThreshold = 50, liveRegionMessage = message, }) => { const intl = useIntl(); const { autoloadTimelines } = 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 visible = count > 0 && (!autoloadTimelines || scrolled); const buttonMessage = intl.formatMessage(message, { count }); /** Number of pixels scrolled down from the top of the page. */ const getScrollTop = (): number => (document.scrollingElement ?? document.documentElement).scrollTop; /** Unload feed items if scrolled to the top. */ const maybeUnload = useCallback(() => { if (autoloadTimelines && scrolledTop && count) { onClick(); } }, [autoloadTimelines, scrolledTop, count, onClick]); /** Set state while scrolling. */ const handleScroll = useCallback( throttle( () => { const scrollTop = getScrollTop(); setScrolled(scrollTop > threshold); setScrolledTop(scrollTop <= autoloadThreshold); }, 40, { 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]); return ( <> {visible ? intl.formatMessage(liveRegionMessage, { count }) : ''}
); }; export { ScrollTopButton as default };