soapbox/app/soapbox/components/scroll-top-button.tsx

100 wiersze
2.8 KiB
TypeScript

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 'soapbox/components/icon';
import { 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<IScrollTopButton> = ({
onClick,
count,
message,
threshold = 400,
autoloadThreshold = 50,
}) => {
const intl = useIntl();
const settings = useSettings();
const [scrolled, setScrolled] = useState<boolean>(false);
const autoload = settings.get('autoloadTimelines') === true;
const visible = count > 0 && scrolled;
const classes = clsx('fixed left-1/2 top-20 z-50 -translate-x-1/2', {
'hidden': !visible,
});
const getScrollTop = (): number => {
return (document.scrollingElement || document.documentElement).scrollTop;
};
const maybeUnload = () => {
if (autoload && getScrollTop() <= autoloadThreshold) {
onClick();
}
};
const handleScroll = useCallback(throttle(() => {
maybeUnload();
if (getScrollTop() > threshold) {
setScrolled(true);
} else {
setScrolled(false);
}
}, 150, { trailing: true }), [autoload, threshold, autoloadThreshold, onClick]);
const scrollUp = () => {
window.scrollTo({ top: 0 });
};
const handleClick: React.MouseEventHandler = () => {
setTimeout(scrollUp, 10);
onClick();
};
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, [onClick]);
useEffect(() => {
maybeUnload();
}, [count]);
return (
<div className={classes}>
<a className='flex cursor-pointer items-center space-x-1.5 whitespace-nowrap rounded-full bg-primary-600 px-4 py-2 text-white transition-transform hover:scale-105 hover:bg-primary-700 active:scale-100' onClick={handleClick}>
<Icon src={require('@tabler/icons/arrow-bar-to-up.svg')} />
{(count > 0) && (
<Text theme='inherit' size='sm'>
{intl.formatMessage(message, { count })}
</Text>
)}
</a>
</div>
);
};
export default ScrollTopButton;