import * as React from 'react'; import { FunctionComponent, useEffect, useRef, useMemo } from 'react'; import { useConst } from '../../hooks'; import './HorizontalScrollbar.css'; type Props = { scrollbarSize: number; visibleSize: number; scrollSize: number; scrollPosition: number; onScroll: (scroll: number) => void; isInverse?: boolean; }; /** * The minimal size of the slider (such that it can still be clickable) -- it is artificially enlarged. */ const MINIMUM_SLIDER_SIZE = 20; export const HorizontalScrollbar: FunctionComponent = (props) => { const { scrollbarSize, visibleSize, scrollSize, scrollPosition, onScroll, isInverse = false, } = props; const scrollbarRef = useRef(null); const sliderSize = useMemo(() => { return Math.round( Math.max( MINIMUM_SLIDER_SIZE, Math.floor((visibleSize * visibleSize) / scrollSize) ) ); }, [scrollSize, visibleSize]); const availableSize = useMemo(() => { return visibleSize - sliderSize; }, [sliderSize, visibleSize]); const computedSliderRatio = useMemo(() => { return availableSize / (scrollSize - visibleSize); }, [availableSize, scrollSize, visibleSize]); const scrollbarStyle = useMemo(() => { return { width: `${visibleSize}px`, height: `${scrollbarSize}px` }; }, [visibleSize, scrollbarSize]); const sliderStyle = useMemo(() => { const computedSliderRatio = (visibleSize - sliderSize) / (scrollSize - visibleSize); return { width: `${sliderSize}px`, left: `${ isInverse ? visibleSize - sliderSize - scrollPosition * computedSliderRatio : scrollPosition * computedSliderRatio }px`, }; }, [sliderSize, scrollPosition, visibleSize, scrollSize, isInverse]); const onMouseDownAndMove = ( evt: MouseEvent | React.MouseEvent ) => { evt.preventDefault(); evt.stopPropagation(); if (!(evt.button === 0)) { return; } const { clientX } = evt; if (!scrollbarRef.current) { return; } const scrollBar = scrollbarRef.current; const dx = clientX - scrollBar.getBoundingClientRect().x - sliderSize / 2; const sliderOffsetLeft = Math.min( Math.max(0, dx), visibleSize - sliderSize ); const computedSliderRatio = (visibleSize - sliderSize) / (scrollSize - visibleSize); onScroll( isInverse ? (visibleSize - sliderSize - sliderOffsetLeft) / computedSliderRatio : sliderOffsetLeft / computedSliderRatio ); }; const onMouseUp = (evt: MouseEvent) => { evt.stopPropagation(); window.removeEventListener('mousemove', onMouseDownAndMove); window.removeEventListener('mousemove', onMouseUp); }; return (
{ evt.stopPropagation(); window.addEventListener('mousemove', onMouseDownAndMove); window.addEventListener('mouseup', onMouseUp); window.addEventListener('mouseup', () => { window.removeEventListener('mousemove', onMouseDownAndMove); }); }} >
); };