()
| 13 | }; |
| 14 | |
| 15 | export default function ScrollToTopButton(): ReactElement | null { |
| 16 | const [show, setShow] = useState(false); |
| 17 | const [hasShown, setHasShown] = useState(false); |
| 18 | const isLaptop = useViewSize(ViewSize.Laptop); |
| 19 | const isTablet = useViewSize(ViewSize.Tablet); |
| 20 | const { showFeedbackButton } = useSettingsContext(); |
| 21 | const size = (() => { |
| 22 | if (isLaptop) { |
| 23 | return ButtonSize.Large; |
| 24 | } |
| 25 | |
| 26 | return isTablet ? ButtonSize.Medium : ButtonSize.Small; |
| 27 | })(); |
| 28 | |
| 29 | useEffect(() => { |
| 30 | const callback = () => { |
| 31 | const shouldShow = |
| 32 | document.documentElement.scrollTop >= window.innerHeight / 2; |
| 33 | setShow(shouldShow); |
| 34 | if (shouldShow) { |
| 35 | setHasShown(true); |
| 36 | } |
| 37 | }; |
| 38 | callback(); |
| 39 | window.addEventListener('scroll', callback, { passive: true }); |
| 40 | return () => window.removeEventListener('scroll', callback); |
| 41 | }, []); |
| 42 | |
| 43 | if (!hasShown) { |
| 44 | return null; |
| 45 | } |
| 46 | |
| 47 | const props: ButtonProps<'button'> = { |
| 48 | icon: <ArrowIcon />, |
| 49 | onClick: () => window.scrollTo({ top: 0, behavior: 'smooth' }), |
| 50 | }; |
| 51 | |
| 52 | const style: CSSProperties = { |
| 53 | ...baseStyle, |
| 54 | transform: show ? undefined : 'translateY(1rem)', |
| 55 | opacity: show ? undefined : 0, |
| 56 | pointerEvents: show ? undefined : 'none', |
| 57 | }; |
| 58 | |
| 59 | return ( |
| 60 | <Button |
| 61 | aria-label="scroll to top" |
| 62 | aria-hidden={!show} |
| 63 | tabIndex={show ? 0 : -1} |
| 64 | {...props} |
| 65 | className={classNames( |
| 66 | 'absolute right-4 z-2', |
| 67 | showFeedbackButton |
| 68 | ? '-top-26 tablet:-top-32' |
| 69 | : '-top-12 tablet:-top-18 laptop:-top-24', |
| 70 | )} |
| 71 | variant={ButtonVariant.Primary} |
| 72 | size={size} |
nothing calls this directly
no test coverage detected