1+ import { useEffect , useState } from "react" ;
2+ import { ChevronDown , ChevronUp } from "lucide-react" ;
3+
4+ const BOTTOM_THRESHOLD = 24 ;
5+
6+ const ScrollNavigator = ( ) => {
7+ const [ showUpButton , setShowUpButton ] = useState ( false ) ;
8+ const [ showDownButton , setShowDownButton ] = useState ( false ) ;
9+
10+ const updateVisibility = ( ) => {
11+ const { documentElement } = document ;
12+ const scrollTop = window . scrollY ;
13+ const scrollableHeight =
14+ documentElement . scrollHeight - documentElement . clientHeight ;
15+ const nearBottom = scrollTop >= scrollableHeight - BOTTOM_THRESHOLD ;
16+
17+ setShowUpButton ( scrollTop > 300 ) ;
18+ setShowDownButton ( scrollableHeight > 0 && ! nearBottom ) ;
19+ } ;
20+
21+ const scrollToTop = ( ) => {
22+ window . scrollTo ( { top : 0 , behavior : "smooth" } ) ;
23+ } ;
24+
25+ const scrollToBottom = ( ) => {
26+ window . scrollTo ( {
27+ top : document . documentElement . scrollHeight ,
28+ behavior : "smooth" ,
29+ } ) ;
30+ } ;
31+
32+ useEffect ( ( ) => {
33+ window . addEventListener ( "scroll" , updateVisibility ) ;
34+ window . addEventListener ( "resize" , updateVisibility ) ;
35+ updateVisibility ( ) ;
36+
37+ return ( ) => {
38+ window . removeEventListener ( "scroll" , updateVisibility ) ;
39+ window . removeEventListener ( "resize" , updateVisibility ) ;
40+ } ;
41+ } , [ ] ) ;
42+
43+ if ( ! showUpButton && ! showDownButton ) {
44+ return null ;
45+ }
46+
47+ return (
48+ < div className = "fixed bottom-5 right-5 z-50 flex flex-col items-center gap-3" >
49+ { showUpButton && (
50+ < button
51+ type = "button"
52+ onClick = { scrollToTop }
53+ aria-label = "Scroll to top"
54+ className = "flex h-12 w-12 items-center justify-center rounded-full bg-blue-600 text-white shadow-lg shadow-blue-600/30 transition-all duration-200 hover:-translate-y-1 hover:bg-blue-700 hover:shadow-xl focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-offset-2 dark:focus:ring-offset-slate-900"
55+ >
56+ < ChevronUp className = "h-6 w-6" />
57+ </ button >
58+ ) }
59+
60+ { showDownButton && (
61+ < button
62+ type = "button"
63+ onClick = { scrollToBottom }
64+ aria-label = "Scroll to bottom"
65+ className = "flex h-12 w-12 items-center justify-center rounded-full bg-slate-900 text-white shadow-lg shadow-slate-900/30 transition-all duration-200 hover:translate-y-1 hover:bg-slate-700 hover:shadow-xl focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-white dark:focus:ring-offset-slate-900"
66+ >
67+ < ChevronDown className = "h-6 w-6" />
68+ </ button >
69+ ) }
70+ </ div >
71+ ) ;
72+ } ;
73+
74+ export default ScrollNavigator ;
0 commit comments