2021-01-27 11:38:21 +00:00
|
|
|
import React, { useEffect, useState, useRef } from "react";
|
|
|
|
|
import styled from "styled-components";
|
|
|
|
|
import _ from "lodash";
|
|
|
|
|
import { useSpring, animated, interpolate } from "react-spring";
|
|
|
|
|
|
|
|
|
|
const ScrollTrack = styled.div<{
|
|
|
|
|
isVisible: boolean;
|
2021-03-15 12:17:56 +00:00
|
|
|
top?: string;
|
|
|
|
|
bottom?: string;
|
|
|
|
|
right?: string;
|
|
|
|
|
mode?: "DARK" | "LIGHT";
|
2021-01-27 11:38:21 +00:00
|
|
|
}>`
|
|
|
|
|
position: absolute;
|
|
|
|
|
z-index: 100;
|
2021-03-15 12:17:56 +00:00
|
|
|
top: ${(props) => (props.top ? props.top : "0px")};
|
|
|
|
|
bottom: ${(props) => (props.bottom ? props.bottom : "0px")};
|
|
|
|
|
right: ${(props) => (props.right ? props.right : "2px")};
|
2021-01-27 11:38:21 +00:00
|
|
|
width: 4px;
|
2021-03-15 12:17:56 +00:00
|
|
|
box-shadow: inset 0 0 6px
|
|
|
|
|
${(props) =>
|
|
|
|
|
props.mode
|
|
|
|
|
? props.mode === "LIGHT"
|
|
|
|
|
? props.theme.colors.scrollbarLightBG
|
|
|
|
|
: props.theme.colors.scrollbarDarkBG
|
|
|
|
|
: props.theme.colors.scrollbarBG};
|
2021-01-27 11:38:21 +00:00
|
|
|
overflow: hidden;
|
|
|
|
|
opacity: ${(props) => (props.isVisible ? 1 : 0)};
|
|
|
|
|
transition: opacity 0.15s ease-in;
|
|
|
|
|
`;
|
|
|
|
|
|
2021-03-15 12:17:56 +00:00
|
|
|
const ScrollThumb = styled(animated.div)<{ mode?: "DARK" | "LIGHT" }>`
|
2021-01-27 11:38:21 +00:00
|
|
|
width: 4px;
|
2021-03-15 12:17:56 +00:00
|
|
|
background-color: ${(props) =>
|
|
|
|
|
props.mode
|
|
|
|
|
? props.mode === "LIGHT"
|
|
|
|
|
? props.theme.colors.scrollbarLight
|
|
|
|
|
: props.theme.colors.scrollbarDark
|
|
|
|
|
: props.theme.colors.scrollbar};
|
|
|
|
|
border-radius: ${(props) => props.theme.radii[3]}px;
|
2021-01-27 11:38:21 +00:00
|
|
|
transform: translate3d(0, 0, 0);
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
interface Props {
|
2021-03-15 12:17:56 +00:00
|
|
|
containerRef: React.RefObject<HTMLElement>;
|
2021-03-18 19:09:13 +00:00
|
|
|
horizontalScrollContainerRef?: React.RefObject<HTMLElement>;
|
2021-03-15 12:17:56 +00:00
|
|
|
top?: string;
|
|
|
|
|
bottom?: string;
|
|
|
|
|
right?: string;
|
2021-03-18 19:09:13 +00:00
|
|
|
alwaysShowScrollbar?: boolean;
|
2021-03-15 12:17:56 +00:00
|
|
|
mode?: "DARK" | "LIGHT";
|
2021-01-27 11:38:21 +00:00
|
|
|
}
|
2021-03-18 19:09:13 +00:00
|
|
|
const ScrollIndicator = ({
|
|
|
|
|
containerRef,
|
|
|
|
|
top,
|
|
|
|
|
bottom,
|
|
|
|
|
right,
|
|
|
|
|
alwaysShowScrollbar,
|
|
|
|
|
horizontalScrollContainerRef,
|
|
|
|
|
}: Props) => {
|
2021-02-26 05:28:31 +00:00
|
|
|
const [{ thumbPosition }, setThumbPosition] = useSpring<{
|
|
|
|
|
thumbPosition: number;
|
|
|
|
|
config: {
|
|
|
|
|
clamp: boolean;
|
|
|
|
|
friction: number;
|
|
|
|
|
precision: number;
|
|
|
|
|
tension: number;
|
|
|
|
|
};
|
|
|
|
|
}>(() => ({
|
2021-01-27 11:38:21 +00:00
|
|
|
thumbPosition: 0,
|
|
|
|
|
config: {
|
|
|
|
|
clamp: true,
|
|
|
|
|
friction: 10,
|
|
|
|
|
precision: 0.1,
|
|
|
|
|
tension: 800,
|
|
|
|
|
},
|
|
|
|
|
}));
|
2021-03-18 19:09:13 +00:00
|
|
|
const [isScrollVisible, setIsScrollVisible] = useState(
|
|
|
|
|
alwaysShowScrollbar || false,
|
|
|
|
|
);
|
2021-01-27 11:38:21 +00:00
|
|
|
const thumbRef = useRef<HTMLDivElement>(null);
|
2021-03-18 19:09:13 +00:00
|
|
|
const trackRef = useRef<HTMLDivElement>(null);
|
2021-01-27 11:38:21 +00:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const handleContainerScroll = (e: any): void => {
|
|
|
|
|
setIsScrollVisible(true);
|
|
|
|
|
const thumbHeight =
|
|
|
|
|
e.target.offsetHeight / (e.target.scrollHeight / e.target.offsetHeight);
|
|
|
|
|
const thumbPosition = (e.target.scrollTop / e.target.offsetHeight) * 100;
|
|
|
|
|
/* set scroll thumb height */
|
|
|
|
|
if (thumbRef.current) {
|
|
|
|
|
thumbRef.current.style.height = thumbHeight + "px";
|
|
|
|
|
}
|
|
|
|
|
setThumbPosition({
|
|
|
|
|
thumbPosition,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2021-03-18 19:09:13 +00:00
|
|
|
const handlehorizontalScroll = (e: any): void => {
|
|
|
|
|
const trackPosition = e.target.scrollLeft;
|
|
|
|
|
if (trackRef.current) {
|
|
|
|
|
trackRef.current.style.right = `${-trackPosition + 2}px`;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2021-01-27 11:38:21 +00:00
|
|
|
containerRef.current?.addEventListener("scroll", handleContainerScroll);
|
|
|
|
|
|
2021-03-18 19:09:13 +00:00
|
|
|
if (horizontalScrollContainerRef) {
|
|
|
|
|
horizontalScrollContainerRef.current?.addEventListener(
|
|
|
|
|
"scroll",
|
|
|
|
|
handlehorizontalScroll,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-27 11:38:21 +00:00
|
|
|
return () => {
|
|
|
|
|
containerRef.current?.removeEventListener(
|
|
|
|
|
"scroll",
|
|
|
|
|
handleContainerScroll,
|
|
|
|
|
);
|
2021-03-18 19:09:13 +00:00
|
|
|
if (horizontalScrollContainerRef) {
|
|
|
|
|
horizontalScrollContainerRef.current?.removeEventListener(
|
|
|
|
|
"scroll",
|
|
|
|
|
handlehorizontalScroll,
|
|
|
|
|
);
|
|
|
|
|
}
|
2021-01-27 11:38:21 +00:00
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (isScrollVisible) {
|
|
|
|
|
hideScrollbar();
|
|
|
|
|
}
|
|
|
|
|
}, [isScrollVisible]);
|
|
|
|
|
|
|
|
|
|
const hideScrollbar = _.debounce(() => {
|
2021-03-18 19:09:13 +00:00
|
|
|
setIsScrollVisible(alwaysShowScrollbar || false);
|
2021-01-27 11:38:21 +00:00
|
|
|
}, 1500);
|
|
|
|
|
|
|
|
|
|
return (
|
2021-03-15 12:17:56 +00:00
|
|
|
<ScrollTrack
|
|
|
|
|
isVisible={isScrollVisible}
|
|
|
|
|
top={top}
|
|
|
|
|
bottom={bottom}
|
|
|
|
|
right={right}
|
2021-03-18 19:09:13 +00:00
|
|
|
ref={trackRef}
|
2021-03-15 12:17:56 +00:00
|
|
|
>
|
2021-01-27 11:38:21 +00:00
|
|
|
<ScrollThumb
|
|
|
|
|
ref={thumbRef}
|
|
|
|
|
style={{
|
|
|
|
|
transform: interpolate(
|
|
|
|
|
[thumbPosition],
|
|
|
|
|
(top: number) => `translate3d(0px, ${top}%, 0)`,
|
|
|
|
|
),
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</ScrollTrack>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default ScrollIndicator;
|