Skip to content

Commit 4f2af08

Browse files
committed
feat: auto scroll to bottom component
1 parent 501ae6c commit 4f2af08

File tree

2 files changed

+86
-0
lines changed

2 files changed

+86
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './scrollable';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { merge } from 'lodash';
2+
import type { CSSProperties, ReactNode } from 'react';
3+
import { useEffect, useRef, useState } from 'react';
4+
5+
interface ScrollableProps {
6+
/**
7+
* How to style the scrollable div element.
8+
*/
9+
style?: CSSProperties;
10+
/**
11+
* Class to apply to the scrollable div element.
12+
*/
13+
className?: string;
14+
/**
15+
* Any children to nest within the scrollable div element.
16+
*/
17+
children?: ReactNode;
18+
}
19+
20+
const defaultStyle: CSSProperties = {
21+
height: 500,
22+
overflow: 'scroll',
23+
};
24+
25+
/**
26+
* A component that enables vertical scrolling and will auto-scroll
27+
* to the bottom as the content grows if the user is already scrolled
28+
* to the bottom.
29+
*
30+
* To stop the auto scrolling, the user simply scrolls up to previous content.
31+
*
32+
* The primary driver of this component is for the game stream windows
33+
* to keep the latest content visible to the user, with the option to
34+
* scroll up to see previous content.
35+
*
36+
* https://stackoverflow.com/questions/37620694/how-to-scroll-to-bottom-in-react
37+
*/
38+
const Scrollable: React.FC<ScrollableProps> = (
39+
props: ScrollableProps
40+
): ReactNode => {
41+
const { className, children } = props;
42+
const style = merge({}, defaultStyle, props.style);
43+
44+
const scrollableRef = useRef<HTMLDivElement>(null);
45+
const scrollBottomRef = useRef<HTMLSpanElement>(null);
46+
47+
const [autoScroll, setAutoScroll] = useState(true);
48+
49+
useEffect(() => {
50+
const scrollableElmt = scrollableRef.current;
51+
52+
const onScroll = () => {
53+
if (!scrollableElmt) {
54+
return;
55+
}
56+
57+
const { scrollTop, scrollHeight, clientHeight } = scrollableElmt;
58+
const difference = scrollHeight - clientHeight - scrollTop;
59+
const enableAutoScroll = difference <= clientHeight;
60+
61+
setAutoScroll(enableAutoScroll);
62+
};
63+
64+
scrollableElmt?.addEventListener('scroll', onScroll);
65+
66+
return () => {
67+
scrollableElmt?.removeEventListener('scroll', onScroll);
68+
};
69+
}, []);
70+
71+
if (autoScroll) {
72+
scrollBottomRef.current?.scrollIntoView({ behavior: 'instant' });
73+
}
74+
75+
return (
76+
<div ref={scrollableRef} style={style} className={className}>
77+
{children}
78+
<span ref={scrollBottomRef} />
79+
</div>
80+
);
81+
};
82+
83+
Scrollable.displayName = 'Scrollable';
84+
85+
export { Scrollable };

0 commit comments

Comments
 (0)